一.过程

1.1.程序和过程的关系

简略来说,程序是静止的,就是咱们的可执行文件,过程是动静的,就是运行起来的程序。

1.2.并行和并发

1)并行,parallel 强调同一时刻同时执行
2)并发,concurrency 则指的一个时间段内去一起执行

1.3.过程的状态

在五态模型中,过程分为新建态、终止态,运行态,就绪态,阻塞态,如下图

1.4.过程各个状态的切换机会

①TASK_RUNNING(运行态):过程正在被CPU执行。当一个过程刚被创立时会处于TASK_RUNNABLE,示意己经准备就绪,正等待被调度。②TASK_INTERRUPTIBLE(可中断):过程正在睡眠(被阻塞)期待某些条件的达成。当条件达成,内核就会把过程状态设置为运行。处于此状态的过程会因为接管到信号而提前被唤醒,比方给一个TASK_INTERRUPTIBLE(可中断)状态的过程发送SIGKILL信号,这个过程将先被唤醒(进入TASK_RUNNABLE运行状态),而后再响应SIGKILL信号而退出(变为TASK_ZOMBIE进行状态),而不是从TASK_INTERRUPTIBLE状态(可中断)间接退出。③TASK_UNINTERRUPTIBLE(不可中断):处于期待中的过程,待资源满足时被唤醒,但不能够由其它过程通过信号或中断唤醒。因为不承受外来的任何信号,因而无奈用kill杀掉这些处于该状态的过程,这个状态存在的作用就是因为内核的某些解决流程是不能被打断的。如果响应异步信号,程序的执行流程中就会被插入一段用于解决异步信号的流程,于是原有的流程就被中断了,这可能使某些设施陷入不可控的状态。另外处于TASK_UNINTERRUPTIBLE状态个别状况下,是十分短暂的,很难通过ps命令捕捉到。④TASK_ZOMBIE(僵死):示意过程曾经完结了,然而其父过程还没有调用wait4或waitpid()来开释过程描述符。为了父过程可能获知它的音讯,子过程的过程描述符依然被保留着。一旦父过程调用了wait4(),过程描述符就会被开释。⑤TASK_STOPPED(进行):过程进行执行。当过程接管到SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU等信号的时候,还有在调试的时候接管到任何信号,也会使过程进入这种状态。当接管到SIGCONT信号,会从新回到TASK_RUNNABLE。

1.5 过程号

过程号PID:每个过程都由一个过程号来标识,其类型为 pid_t(整型),过程号的范畴:0~32767。
父过程号PPID:任何过程( 除 init 过程)都是由另一个过程创立,该过程称为被创立过程的父过程,对应的过程号称为父过程号(PPID)。
过程组号PGID:过程组是一个或多个过程的汇合。他们之间互相关联,过程组能够接管同一终端的各种信号,关联的过程有一个过程组号(PGID)

1.6 过程相干函数

getpid函数

#include <sys/types.h>#include <unistd.h>pid_t getpid(void);性能:    获取本过程号(PID)参数:    无返回值:    本过程号

getppid函数

#include <sys/types.h>#include <unistd.h>pid_t getppid(void);性能:    获取调用此函数的过程的父过程号(PPID)参数:    无返回值:    调用此函数的过程的父过程号(PPID)

getpgid函数

#include <sys/types.h>#include <unistd.h>pid_t getpgid(pid_t pid);性能:    获取过程组号(PGID)参数:    pid:过程号返回值:    参数为 0 时返回以后过程组号,否则返回参数指定的过程的过程组号

二.创立过程

2.1 fork函数

#include <sys/types.h>#include <unistd.h>pid_t fork(void);性能:    用于从一个已存在的过程中创立一个新过程,新过程称为子过程,原过程称为父过程。参数:    无返回值:    胜利:子过程中返回 0,父过程中返回子过程 ID。pid_t,为整型。    失败:返回-1。    失败的两个次要起因是:        1)以后的过程数曾经达到了零碎规定的下限,这时 errno 的值被设置为 EAGAIN。        2)零碎内存不足,这时 errno 的值被设置为 ENOMEM。

应用 fork() 函数失去的子过程会将父过程复制一份(包含过程上下文、过程堆栈、关上的文件描述符、信号管制设定、过程优先级、过程组号等),
所以应用fork()创立过程开销是很大的。

那么如何辨别父过程和子过程呢?那就是依据fork()返回的pid值。
fork() 函数被调用一次,但返回两次,
子过程的返回值是 0,而父过程的返回值则是新子过程的过程 pid。

pid_t pid = fork(); // 依据这个返回值来辨别父子过程,如同这个值小于0,那就是示意创立过程失败。

代码示例

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <unistd.h>int main(){    // 打印创立过程的过程号    printf("创立子过程前的pid:[%d]\n",getpid());    // 创立子过程    pid_t pid = fork();        if(pid < 0){// 创立过程失败        perror("fork ");        return -1;    }else if(pid >0){ // 父过程执行内容        printf("父过程执行[pid %d][ppid:%d\n]",getpid(),getppid());            }else if(pid == 0){ //子过程执行        printf("子过程执行[pid %d][ppid:%d\n]",getpid(),getppid());    }    printf("创立子过程后执行 pid[%d]\n",getpid());    return 0;}

输入后果

创立子过程前的pid:[29871]父过程执行[pid 29871][ppid:29848]创立子过程后执行 pid[29871]子过程执行[pid 29872][ppid:29871]创立子过程后执行 pid[29872]

父子过程中各自的空间是独立的,假设全局变量a,在子过程中批改a的值时,并不影响父过程。

2.2 exec 函数族

在运行的过程中,启动一个内部程序,由内核将这个内部程序读入内存,使其执行起来成为一个过程,例如,咱们执行一个程序,须要调用内部的ls命令,获取一个目录的资源信息,这里应用的就是exec函数族。
exec 函数族,就是一簇函数,在 Linux 中,并不存在 exec() 函数,exec 指的是一组函数,共有6个,如下:

#include <unistd.h>extern char **environ;int execl(const char *path, const char *arg, .../* (char  *) NULL */);int execlp(const char *file, const char *arg, ... /* (char  *) NULL */);int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);int execv(const char *path, char *const argv[]);int execvp(const char *file, char *const argv[]);int execvpe(const char *file, char *const argv[], char *const envp[]);int execve(const char *filename, char *const argv[], char *const envp[]);

个别罕用的有两个,execl和excelp。
execl函数个别执行本人写的程序

int execl(const char *path, const char *arg, ... /* (char  *) NULL */);path: 要执行的程序的门路变参arg: 要执行的程序的须要的参数,个别先是程序的名字,之后是程序的执行参数参数写完之后: NULL返回值:若是胜利,则不返回,不会再执行exec函数前面的代码;若是失败,会执行execl前面的代码,能够用perror打印谬误起因。

参考示例

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <unistd.h>int main(){    printf("===============start ls=================");    /*调用零碎的ls命令*/    execl("/bin/ls","ls","-l",NULL);    perror("execl");     return 0;}

咱们也能够本人写一个程序文件,来进行调用
例如咱们写一个示例程序test.c,打印承受的参数

#include <stdio.h>#include <unistd.h>int man(int argc, char*argv[]){    for(int i=0;i<argc;i++){        printf("[%s] ",argv[i]);    }    return 0;}

主程序

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <unistd.h>int main(){    printf("===============start ls=================");    execl("./test","test","hello","world",NULL);    perror("execl");     return 0;}

execlp很是类似,execlp函数个别是执行零碎自带的程序或者是命令.

int execlp(const char *file, const char *arg, .../* (char  *) NULL */);参数阐明:   file: 执行命令的名字, 依据PATH环境变量来搜寻该命令,   其余和execl雷同。

代码示例

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <unistd.h>int main(){    printf("===============start ls=================");    /*调用零碎的ls命令*/    execlp("ls","ls","-l",NULL);    perror("execl");     return 0;}

3 退出过程

3.1 资源回收

在过程退出的时候,内核会开释过程所有的资源,但依然有一些资源不能被开释,次要指过程管制块PCB(过程号、退出状态、运行工夫等)。
父过程能够通过调用wait或waitpid失去它的退出状态同时彻底清除掉这个过程,防止资源的节约。

3.2 孤儿过程和僵尸过程

1)孤儿过程
父过程运行完结,但它的子过程却还未完结运行,这个子过程就成了孤儿过程。
内核会把孤儿过程的父过程设置为init ,而init 过程会循环地 wait() 它的曾经退出的子过程。
所以孤儿过程并不会造成什么危害。

2)僵尸过程
若子过程完结运行,父过程还在持续运行,然而父过程没有调用wait或waitpid函数实现对子过程的资源回收,那么这个子过程就成了僵尸过程。

僵尸过程会占用零碎的资源,霸占过程号,而过程号是无限的,过多的僵尸过程呈现,会导致可用过程号缩小,最初无奈创立新的过程,所以要防止产生僵尸过程。

3.3 回收过程资源的函数wait或waitpid

1)wait

#include <sys/types.h>#include <sys/wait.h>pid_t wait(int *status);性能:    期待任意一个子过程完结,如果任意一个子过程完结了,此函数会回收该子过程的资源。参数:    status : 过程退出时的状态信息。返回值:    胜利:曾经完结子过程的过程号    失败: -1    具体阐明:wait()函数的次要性能为回收曾经完结子过程的资源,调用 wait() 函数的过程会处于阻塞状态。如果调用过程没有子过程,那函数立刻返回;如果它的子过程曾经完结,则函数会立刻返回,同时会回收那个该过程的资源。如果传入参数 status 的值不是 NULL,那么wait() 就会把子过程退出时的状态取出并存入一个整数值(int),表明子过程是失常退出还是异样终止。这个退出信息在一个 int 中蕴含了多个字段,咱们应用用宏定义取出其中的每个字段,次要有如下三组宏信息1) WIFEXITED(status) 为非0,示意过程失常完结WEXITSTATUS(status) 如上宏为真,应用此宏能够获取过程退出状态 (exit的参数)2) WIFSIGNALED(status)为非0,示意过程异样终止WTERMSIG(status)如上宏为真,应用此宏,获得使过程终止的那个信号的编号。3) WIFSTOPPED(status)为非0 → 过程处于暂停状态WSTOPSIG(status)如上宏为真,应用此宏获得使过程暂停的那个信号的编号。WIFCONTINUED(status)为非0,意味着过程暂停后曾经持续运行

代码示例

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <unistd.h>#include <sys/wait.h>int main(){    pid_t pid = fork();    if(pid<0){        perror("fork");        return -1;    }else if(pid >0){        // 父过程调用wait 一次wait只能终止一个子过程        int status;        pid_t cpid = wait(&status);        printf("child pid [%d]",cpid);        if(WIFEXITED(status)){            // 失常退出            printf("child process normal exit. status[%d]\n",WEXITSTATUS(status));        }else if(WIFSIGNALED(status)){            // 被信号杀死            printf("child process killed by signal, signo[%d]\n", WTERMSIG(status));        }    }else if(pid == 0){        // 子过程        for(int i=0;i<10;i++){            printf("helloworld\n");        }        return 10;    }    return 0;}

2)waitpid

#include <sys/types.h>#include <sys/wait.h>pid_t waitpid(pid_t pid, int *status, int options);性能:跟wait一样,期待子过程终止,回收子过程的资源。参数:    pid : 参数 pid 的值有以下几种类型:      pid > 0  期待过程 ID 等于 pid 的子过程。      pid = 0  期待同一个过程组中的任何子过程,如果子过程曾经退出了别的过程组,waitpid 不会期待它。      pid = -1 期待任一子过程,此时 waitpid 和 wait 作用一样。      pid < -1 期待指定过程组中的任何子过程,这个过程组的 ID 等于 pid 的绝对值,很少应用。    status : 过程退出时的状态信息。和 wait() 用法一样。    options : options 提供了一些额定的选项来管制 waitpid()。            0:同 wait(),阻塞父过程,期待子过程退出。            WNOHANG:没有任何曾经完结的子过程,则立刻返回。            WUNTRACED:如果子过程暂停了则此函数马上返回,并且不予以理睬子过程的完结状态。(因为波及到一些跟踪调试方面的常识,加之极少用到)                 返回值:    waitpid() 的返回值比 wait() 略微简单一些,一共有 3 种状况:        1) 当失常返回的时候,waitpid() 返回收集到的曾经回收子过程的过程号;        2) 如果设置了选项 WNOHANG,而调用中 waitpid() 发现没有已退出的子过程可期待,则返回 0;        3) 如果调用中出错,则返回-1,这时 errno 会被设置成相应的值以批示谬误所在,如:当 pid 所对应的子过程不存在,或此过程存在,但不是调用过程的子过程,waitpid() 就会出错返回,这时 errno 被设置为 ECHILD;

代码示例(下面代码简略批改一下)

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <unistd.h>#include <sys/wait.h>int main(){    pid_t pid = fork();    if(pid<0){        perror("fork");        return -1;    }else if(pid >0){        // 父过程调用wait 一次wait只能终止一个子过程        int status;        pid_t cpid = waitpid(-1,&status,0);        printf("child pid [%d]",cpid);        if(WIFEXITED(status)){            // 失常退出            printf("child process normal exit. status[%d]\n",WEXITSTATUS(status));        }else if(WIFSIGNALED(status)){            // 被信号杀死            printf("child process killed by signal, signo[%d]\n", WTERMSIG(status));        }    }else if(pid == 0){        // 子过程        for(int i=0;i<10;i++){            printf("helloworld\n");        }        return 10;    }    return 0;}