一. 过程
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;
}