最近在学习 APUE,所以顺便将每日所学记录下来,一方面为了巩固学习的知识,另一方面也为同样在学习APUE的童鞋们提供一份参考。
本系列博文均根据学习《UNIX环境高级编程》一书总结而来;
运行环境:
- 操作系统: ubutnu 16.04
- 编译器:QtCreator CLion 2020.3
进程控制
进程概念
同一个剧本可以在多个舞台同时上演。同样,同一个程序也可以加载为不同的进程(彼此之间互不影响)
并行和并发
- 并发,在一个时间段内, 是在同一个cpu上, 同时运行多个程序。
如:若将CPU的1S的时间分成1000个时间片,每个进程执行完一个时间片必须无条件让出CPU的使用权,这样1S中就可以执行1000个进程。
PCB
每个进程在内核中都有一个进程控制块(PCB)来维护进程相关的信息,Linux内核的进程控制块是task_struct结构体。
/usr/src/linux-headers-4.4.0-96/include/linux/sched.h文件的1390行处可以查看struct task_struct 结构体定义。其内部成员有很多,我们重点掌握以下部分即可:
进程id。系统中每个进程有唯一的id,在C语言中用pid_t类型表示,其实就是一个非负整数。
进程的状态,有就绪、运行、挂起、停止等状态。
进程切换时需要保存和恢复的一些CPU寄存器。
描述虚拟地址空间的信息。
描述控制终端的信息。
当前工作目录(Current Working Directory)。
umask掩码。
文件描述符表,包含很多指向file结构体的指针。
和信号相关的信息。
用户id和组id。
会话(Session)和进程组。
进程可以使用的资源上限(Resource Limit)。
进程状态
创建进程
fork
fork返回值,父进程返回的是子进程的PID,这个值大于0,子进程返回0;
父进程执行PID大于0,子进程执行PID等于0;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
#include <unistd.h> #include <stdlib.h> #include <string.h> #include <stdio.h> #include <sys/types.h> int main() { printf("before fork, PID == %d\n",getpid()); pid_t pid = fork(); if(pid < 0) { perror("fork error"); return -1; } else if(pid > 0) { printf("Father Process PID == %d\n",getpid()); sleep(1); } else if(pid == 0) { printf("child Process PID == %d\n",getpid()); } printf("after fork PID == %d\n",getpid()); return 0; }
|
执行图如下:
循环创建子进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
|
#include <unistd.h> #include <stdlib.h> #include <string.h> #include <stdio.h> #include <sys/types.h>
int main(int argc,char *argv[]) { int i = 0; for (; i < 3; i++) { pid_t pid = fork(); if(pid < 0) { perror("fork error"); return -1; } else if(pid > 0) { printf("Father Process PID == %d Process PPID = %d\n",getpid(),getppid()); sleep(1); } else if(pid == 0) { printf("child Process PID == %d Process PPID = %d\n",getpid(),getppid()); break; } }
if(i == 0) { printf("[%d]:child---pid:%d\n",i,getpid()); } if(i == 1) { printf("[%d]:child---pid:%d\n",i,getpid()); } if(i == 2) { printf("[%d]:child---pid:%d\n",i,getpid()); } if(i == 3) { printf("[%d]:child---pid:%d\n",i,getpid()); } sleep(10); return 0; }
|
父子进程共享全局变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
|
#include <unistd.h> #include <stdlib.h> #include <string.h> #include <stdio.h> #include <sys/types.h>
int g_var = 99;
int main() { pid_t pid = fork(); if(pid < 0) { perror("fork error"); return -1; } else if(pid > 0) { printf("Father Process PID == %d Process PPID = %d\n",getpid(),getppid()); g_var--; printf("g_var = %p\n",&g_var); printf("g_var = %d\n",g_var); sleep(1); } else if(pid == 0) { printf("child Process PID == %d Process PPID = %d\n",getpid(),getppid()); printf("g_var = %d\n",g_var); printf("[g_var] = %p\n",&g_var); } return 0;
}
|
读时共享,写时复制
PS 和 KILL
1 2 3
| ps -ajx ps -ef | grep root kill -9 进程名
|
exec函数族
有的时候需要在一个进程里面执行其他的命令或者是用户自定义的应用程序,此时就用到了exec函数族当中的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ... ); int execlp(const char *file, const char *arg, ... ); int execle(const char *path, const char *arg, ... ); 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[]);
|
execl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
#include <unistd.h> #include <stdlib.h> #include <string.h> #include <stdio.h> #include <sys/types.h>
int main(int argc,char *argv[]) { pid_t pid = fork(); if(pid < 0) { perror("fork error"); return -1; } else if(pid > 0) { printf("Father Process PID == %d Process PPID = %d\n",getpid(),getppid()); sleep(1); } else if(pid == 0) { printf("child Process PID == %d Process PPID = %d\n",getpid(),getppid()); execlp("./main","main","Hello","World","sYstemkl1t",NULL); perror("main Error"); } return 0;
}
|
进程回收
孤儿进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
#include <unistd.h> #include <stdlib.h> #include <string.h> #include <stdio.h> #include <sys/types.h>
int main() { pid_t pid = fork(); if(pid < 0) { perror("fork error"); return -1; } else if(pid > 0) { sleep(5); printf("Father Process PID == %d Process PPID = %d\n",getpid(),getppid());
} else if(pid == 0) { printf("child Process PID == %d Process PPID = %d\n",getpid(),getppid()); sleep(20); printf("child Process PID == %d Process PPID = %d\n",getpid(),getppid()); } return 0;
}
|
父进程先退出,子进程就变成了孤儿进程,此时被init领养;当孤儿进程退出后,就会被init进程回收
僵尸进程
子进程先退出,父进程没有完成对子进程的回收,此时子进程就称为僵尸进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
|
#include <unistd.h> #include <stdlib.h> #include <string.h> #include <stdio.h> #include <sys/types.h>
int main() { pid_t pid = fork(); if(pid < 0) { perror("fork error"); return -1; } else if(pid > 0) { sleep(200); printf("Father Process PID == %d Process PPID = %d\n",getpid(),getppid());
} else if(pid == 0) { printf("child Process PID == %d Process PPID = %d\n",getpid(),getppid());
} return 0;
}
|
如果要杀死僵尸进程,那么应该使用杀死僵尸进程的父进程的方法来解决僵尸进程;
回收函数
wait
1
| pid_t wait(int *status);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
|
#include <unistd.h> #include <stdlib.h> #include <string.h> #include <stdio.h> #include <sys/types.h> #include <sys/wait.h>
int main(int argc,char *argv[]) { int i = 0; int status; pid_t pid = fork(); if(pid < 0) { perror("fork error"); return -1; } else if(pid > 0) { printf("Father [%d] Process PID == %d Process PPID = %d\n",pid,getpid(),getppid()); pid_t wpid = wait(&status);
printf("wpid == %d\n",wpid); if (WIFEXITED(status)) { printf("child normal exit status == [%d]\n",WEXITSTATUS(status)); } else if(WIFSIGNALED(status)) { printf("child killed by signal, signo == [%d]\n",WTERMSIG(status)); }
} else if(pid == 0) { printf("child Process PID == %d Process PPID = %d\n",getpid(),getppid()); sleep(20); return 9; } return 0; }
|
waitpid
1 2 3
| #include <sys/types.h> #include <sys/wait.h> pid_t waitpid(pid_t pid,int *status,int options)
|
pid
从参数的名字pid和类型pid_t中就可以看出,这里需要的是一个进程ID。但当pid取不同的值时,在这里有不同的意义。
- pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
- pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
- \pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。**
- pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。
\options**
options提供了一些额外的选项来控制waitpid,目前在Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用”|”运算符把它们连接起来使用;
如果使用了WNOHANG(wait no hung)参数调用waitpid,即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
|
#include <unistd.h> #include <stdlib.h> #include <string.h> #include <stdio.h> #include <sys/types.h> #include <sys/wait.h>
int main(int argc,char *argv[]) { int i = 0; int status; pid_t pid = fork(); if(pid < 0) { perror("fork error"); return -1; } else if(pid > 0) { printf("Father [%d] Process PID == %d Process PPID = %d\n", pid, getpid(), getppid());
while (1) { pid_t wpid = waitpid(-1, &status, WNOHANG); printf("wpid == %d\n", wpid); if (wpid > 0) { if (WIFEXITED(status)) { printf("child normal exit status == [%d]\n", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { printf("child killed by signal, signo == [%d]\n", WTERMSIG(status)); } } else if (wpid == 0) { printf("child is living = %d\n", wpid); } else if (wpid == -1) { printf("No child is livint = %d\n", wpid); break; } } }
else if(pid == 0) { printf("child Process PID == %d Process PPID = %d\n",getpid(),getppid()); sleep(2); return 9; } return 0; }
|