fork函数
返回值:子进程中返回0,父进程返回子进程id,出错返回-1。
进程调用fork,当控制转移到内核中,内核做:
#include#include #include int main() { printf("before fork pid is :%d\n",getpid()); pid_t id = fork(); if(id < 0){ perror("fork"); } printf("after fork pid id :%d\n",getpid()); printf("fork return value is :%d\n",id); return 0; }
上面程序运行结果
可以看出,12行和14行中的打印信息被执行了两次,而且两次的值也不一样。
因为fork之前父进程单独执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器 决定。
fork之后,父子进程共享代码,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。
进程退出的场景:
再平时写C/C++程序时,一般都i会这样写
#includeint main() { //... return 0; }
都会在main函数最后一行写一句return 0;
return 0;就是进程的退出码,一般0表示成功,非0 表示失败。
一但程序执行失败,需要知道原因,用不同的数字来表示不同的失败原因。
使用echo $?可以查看最近一次进程的退出码
比如系统中没有lll命令,执行,然后查看进程退出码,可以看到退出码为127
在C标准库函数中,有一个strerror函数,用于将错误码(通常是由系统调用或库函数返回的错误码)转换为对应的错误消息字符串。
#include#include int main() { for (size_t i = 0; i < 255; i++) { printf("error code %d:%s\n",i,strerror(i)); } return 0; }
Linux下错误码一共有133个。
参数:status 定义了进程的终止状态,父进程通过wait来获取该值
_exit在程序的任意位置都可以终止进程
#include#include void test() { printf("Hello World\n"); _exit(1); } int main() { test(); return 0; }
运行结果:
main函数中的return 0;还没有执行到进程就终止了。
exit和_exit 的区别,exit是c语言的进程终止的函数,而_exit是Linux系统调用接口的函数,c语言在实现exit函数时会封装_exit。
#include#include void test() { printf("Hello World\n"); exit(1); } int main() { test(); return 0; }
进程退出码和上面一样,都是1,main函数中的return 0;不会执行,进程就终止了。
同样的代码,分别使用eixt 和 _exit终止进程,exit会刷新缓冲区。(printf语句没有加\n),而_exit则不会刷新缓冲区。
进程等待的必要性:
man 2 wait 认识wait
返回值:
等待成功,返回子进程的pid
等待失败,返回-1。
函数参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
wait:它等待任意子进程退出,而不需要指定特定的子进程ID。如果没有子进程退出,wait 会阻塞当前进程直到有子进程退出。(阻塞等待简单理解就是父进程啥也不干,就只等子进程退出进行回收)
#include#include #include #include #include int main() { pid_t id = fork(); if(id < 0 ) { perror("fork\n"); } else if(id == 0){ //子进程 printf("i am child process.pid = %d\n",getpid()); //子进程退出 退出码为1 exit(1); } else{ //父进程 printf("i am Parent process waiting...\n"); sleep(3);//父进程等待三秒后回收子进程 int status = 0; pid_t waitid = wait(&status);//不想获取子进程退出码可以设置为NULL //等待失败处理 if(waitid == -1) { perror("wait fail\n"); exit(-1); } printf("wait sucess, child excit code = %d,waitid = %d\n",status,waitid); } return 0; }
运行结果:
这里父进程获取到的子进程退出码并不是1,而是256
这是因为status占4个字节,32个比特位。前十六位不用关心,后是6位前8位表示进程正常的退出码,最后七位表示进程异常收到的信号,退出码和信号中间的一位是core dump标志位,和信号有关,这里不用深究。
exit(1) 则是进程正常退出,也没有收到异常信号,status则是
这就是256的原因。
如果想直接拿到进程的退出码,而不是status,(status>>8)& 0xff即可
上面代码31行修改为
printf("wait sucess, child excit code = %d,waitid = %d\n",(status>>8)&0xFF,waitid);
运行结果:
等待指定进程或者任意子进程,相比wait更灵活
man 2 waitpid 认识waitpid
参数:
返回值:
阻塞等待示例:
#include#include #include #include #include int main() { //waitpid阻塞等待 pid_t id = fork(); if(id < 0 ){ perror("fork\n"); exit(-1); } else if(id == 0){ //子进程 printf("i am child process. pid = %d\n",getpid()); sleep(3); exit(1); } else { //父进程 printf("parent process waiting ...\n"); int status = 0 ; pid_t ret = waitpid(-1,&status,0);//阻塞等待任意子进程 if(WIFEXITED(status) && ret == id){//等待成功 printf("wait sucess,child return code = %d,ret = %d\n",WIFEXITED(status),ret); } else{//等待失败 printf("wait fail\n"); } } return 0; }
运行结果:
等待成功,子进程退出码为1,waitpid返回值为子进程的pid
WIFEXITED 和 WEXITSTATUS 是在
非阻塞等待示例:
#include#include #include #include #include int main() { //waitpid非阻塞等待 pid_t id = fork(); if(id < 0 ){//失败处理 perror("fork\n"); exit(-1); } else if(id == 0){//子进程 printf("i am child process. pid = %d\n",getpid()); sleep(3); exit(1); } else {//父进程 printf("parent process waiting ...\n"); int status = 0; pid_t ret = 0; do { ret = waitpid(-1,&status,WNOHANG);//非阻塞等待任意子进程 if(0 == ret){//子进程还没退出,父进程非阻塞等待可以做其他事情 printf("child process is running,parent do other things\n"); sleep(1); } }while(0 == ret); //等待成功 if(WIFEXITED(status) && ret == id){ //打印子进程退出码和waitpid返回值 printf("wait sucess,child return code = %d,ret = %d\n",WEXITSTATUS(status),ret); } //等待失败 else{ printf("wait fail\n"); } } return 0; }
运行结果:
父进程非阻塞等待子进程时还可以做其他的事情, wiatpid返回值时子进程的id
阻塞等待:
阻塞等待会导致进程无法执行其他任务,直到等待的事件发生。
waitpid函数设置为阻塞等待,只需将options参数设置为0
阻塞等待的优点:
阻塞等待的缺点:
非阻塞等待:
waitpid函数设置为阻塞等待,需要将options参数设置为WNOHANG。(一个宏)
非阻塞等待的优点:
非阻塞等待的缺点:
进程程序替换是指一个正在运行的进程将自己的地址空间、代码、数据和堆栈等信息替换为另一个程序的内容。
有六种以exec开头的函数,统称exec函数:
int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(const char *path, const char *arg, ..., 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 execl(const char *path, const char *arg, ...);
示例:
#include#include #include #include #include int main() { //进程程序替换 pid_t id = fork(); if(id < 0){ perror("fork fail\n"); } else if(id == 0){ //子进程 printf("i am child process, pid = %d",getpid()); //子进程执行ls -a 命令 execl("/usr/bin/ls","ls","-a",NULL); printf("exec end\n"); exit(1); } else{ //父进程 int status = 0; pid_t ret = waitpid(id,&status,0);//阻塞等待指定子进程 if(WIFEXITED(status) && ret == id)//等待成功 { printf("wait sucess,child return code = %d,ret = %d, id = %d\n",WEXITSTATUS(status),ret,id); } } return 0; }
运行结果:
可以发现 当进程程序替换完成后,exec后面的代码将不再执行。
一旦exec替换失败,才会只执行后面的代码
比如将上面17行要替换的进程改为一个不存在的,
execl("/usr/bin/lsl","ls","-l",NULL);
执行结果:子进程后面的代码被执行了。
int execlp(const char *file, const char *arg, ...);
示例:子进程执行ls命令
execlp("ls","ls","-l",NULL);
int execle(const char *path, const char *arg, ..., char * const envp[]);
参数1:需要执行程序的路径,
参数2:是可变参数列表,表示你要如何执行这个程序,并以NULL结尾。
参数3:envp:是一个以 NULL 结尾的字符串数组,用于设置新程序的环境变量。
// 设置新程序的环境变量 char *envp[] = {"MY_VARIABLE=value", NULL}; // 使用 execle 函数替换当前进程的程序 execle("/bin/echo", "echo", "Hello, execle!", (char *)NULL, envp);
运行结果:
int execv(const char *path, char *const argv[]);
char *const argv[] = {"ls","-l",NULL}; execv("/usr/bin/ls",argv);
运行结果:
int execvp(const char *file, char *const argv[]);
不用指定路径即可。
int execvpe(const char *file, char *const argv[], char *const envp[]);
第一个参数是要执行程序的路径,第二个参数是一个指针数组,数组当中的内容表示你要如何执行这个程序,数组以NULL结尾,第三个参数是你自己设置的环境变量。