Linux 多进程开发(1)
进程相关命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| # 1 # man ps # ps - report a snapshot of the current processes. ps [-option] # aux/ajx a: all u: j: job x:
# 2 tty # 查看当前终端信息
# 3 实时查看进行信息 top [-option]
# 4 向进程发送信号 kill kill -l # 查看所有信号
# 后台运行 [命令] &
|
进程的创建
fork函数:
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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
|
#include <sys/types.h> #include <unistd.h> #include <stdio.h>
int main() {
int num = 10;
pid_t pid = fork();
if(pid > 0) { printf("i am parent process, pid : %d, ppid : %d\n", getpid(), getppid());
printf("parent num : %d\n", num); num += 10; printf("parent num += 10 : %d\n", num);
} else if(pid == 0) { printf("i am child process, pid : %d, ppid : %d\n", getpid(),getppid()); printf("child num : %d\n", num); num += 100; printf("child num += 100 : %d\n", num); }
for(int i = 0; i < 3; i++) { printf("i : %d , pid : %d\n", i , getpid()); sleep(1); }
return 0; }
|
GDB 多进程调试
gdb默认只跟踪一个进程,默认调试父进程,子进程代码直接运行。设置调试默认调试的进程:
1
| (gdb) set follow-fork-mode [parent (default) | child]
|
设置调试的模式:
1
| (gdb) set detach-on-fork [on (default) | off]
|
on:调试时其他进程继续运行;
off:调试时其他进程被 gdb 挂起。
1 2 3 4 5 6 7 8
| # 查看调试的进程 info inferiors
# 切换当前调试的进程 inferior id
# 使进程脱离 gdb 调试 detach inferiors id
|
exce 函数族
函数名称不同,功能相似的函数叫函数族。
- exec 函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。
- exec 函数族的函数执行成功后不会返回,因为调用进程的实体,包括代码段,数据段和堆栈等都已经被新的内容取代,只留下进程 ID 等一些表面上的信息仍保持原样。只有调用失败了,它们才会返回 -1,从原程序的调用点接着往下执行。
1 2 3 4 5 6 7 8 9 10
| 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[]);
int execve(const char *filename, char *const argv[], char *const envp[]);
|
- l(list):参数地址列表,以空指针结尾
- v(vector) :存有各参数地址的指针数组的地址
- p(path) :按 PATH 环境变量指定的目录搜索可执行文件
- e(environment) :存有环境变量字符串地址的指针数组的地址
int execl(const char *path, const char *arg, .../* (char *) NULL */);
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
|
#include <unistd.h> #include <stdio.h> #include <sys/types.h>
int main() {
pid_t pid = fork();
if(pid > 0) { printf("i am parent process, pid : %d\n",getpid()); sleep(1); }else if(pid == 0) { execl("hello","hello",NULL); perror("execl"); printf("i am child process, pid : %d\n", getpid()); }
for(int i = 0; i < 3; i++) { printf("i = %d, pid = %d\n", i, getpid()); }
return 0; }
|
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
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 63 64 65
|
#include <unistd.h> #include <stdio.h> #include <sys/types.h>
int main() {
pid_t pid = fork();
if(pid > 0) { printf("i am parent process, pid : %d\n",getpid()); sleep(1); }else if(pid == 0) { execlp("ps", "ps", "j", NULL);
perror("execl");
printf("i am child process, pid : %d\n", getpid()); }
for(int i = 0; i < 3; i++) { printf("i = %d, pid = %d\n", i, getpid()); }
return 0; }
|
进程控制
进程退出
1 2 3 4 5 6 7
| #include <stdlib.h> void exit(int status);
#include <unistd.h> void _exit(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
|
#include <stdio.h> #include <stdlib.h> #include <unistd.h>
int main() {
printf("hello\n"); printf("world");
exit(0);
return 0; }
|
exit(0) 执行的结果(会刷新缓冲区):
_exit(0) 执行的结果(不会刷新缓冲区):
孤儿进程
父进程运行结束,但子进程还在运行(未运行结束),这样的子进程就称为孤儿进程(Orphan
Process)。
每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为init,而 init 进程会循环地 wait() 它
的已经退出的子进程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include <sys/types.h> #include <unistd.h> #include <stdio.h>
int main() {
pid_t pid = fork();
if(pid > 0) {
printf("i am parent process, pid : %d, ppid : %d\n", getpid(), getppid());
} else if(pid == 0) { sleep(1); printf("i am child process, pid : %d, ppid : %d\n", getpid(),getppid()); } return 0; }
|
执行结果:
子进程的文件描述符表里的012复制于父进程,所以输出到同一个终端。
僵尸进程
- 每个进程结束之后, 都会释放自己地址空间中的用户区数据,内核区的 PCB 没有办法自己释放掉,需要父进程去释放。
- 子进程终止时,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。
- 僵尸进程不能被 kill -9 杀死,这样就会导致一个问题,如果父进程不调用 wait() 或 waitpid() 的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程,此即为僵尸进程的危害,应当避免。
输出:
1 2 3 4 5 6 7 8 9
| i am parent process, pid : 17195, ppid : 13038 i am child process, pid : 17196, ppid : 17195 i : 0 , pid : 17196 i : 1 , pid : 17196 i : 2 , pid : 17196 i am parent process, pid : 17195, ppid : 13038 i am parent process, pid : 17195, ppid : 13038 i am parent process, pid : 17195, ppid : 13038 ...
|
新建终端使用 ps命令查看,子进程变为僵尸进程:
1 2 3
| PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 13038 17195 17195 13038 pts/0 17195 S+ 500 0:00 ./zombie 17195 17196 17195 13038 pts/0 17195 Z+ 500 0:00 [zombie] <defunct>
|
进程回收
在每个进程退出的时候,内核释放该进程所有的资源、包括打开的文件、占用的内存等。但是仍然为其保留一定的信息,这些信息主要主要指进程控制块PCB的信息(包括进程号、退出状态、运行时间等)。
父进程可以通过调用 wait 或 waitpid 得到它的退出状态同时彻底清除掉这个进程。
wait() 和 waitpid() 函数的功能一样,区别在于,wait() 函数会阻塞,waitpid() 可以设置不阻塞,
waitpid() 还可以指定等待哪个子进程结束。
注意:一次 wait 或 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 63 64 65 66 67 68 69 70 71 72 73 74 75
|
#include <sys/types.h> #include <sys/wait.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h>
int main() {
pid_t pid;
for(int i = 0; i < 5; i++) { pid = fork(); if(pid == 0) { break; } }
if(pid > 0) { while(1) { printf("parent, pid = %d\n", getpid()); int st; int ret = wait(&st); if(ret == -1) { break; } if(WIFEXITED(st)) { printf("退出的状态码:%d\n", WEXITSTATUS(st)); } if(WIFSIGNALED(st)) { printf("被哪个信号干掉了:%d\n", WTERMSIG(st)); } printf("child die, pid = %d\n", ret); sleep(1); }
} else if (pid == 0){ while(1) { printf("child, pid = %d\n",getpid()); sleep(1); }
exit(0); }
return 0; }
|
退出信息相关宏函数:
1 2 3 4 5 6 7
| WIFEXITED(status) WEXITSTATUS(status) WIFSIGNALED(status) WTERMSIG(status) WIFSTOPPED(status) WSTOPSIG(status) WIFCONTINUED(status)
|
waitpid() 函数
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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
|
#include <sys/types.h> #include <sys/wait.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h>
int main() {
pid_t pid;
for(int i = 0; i < 5; i++) { pid = fork(); if(pid == 0) { break; } }
if(pid > 0) { while(1) { printf("parent, pid = %d\n", getpid()); sleep(1);
int st; int ret = waitpid(-1, &st, WNOHANG);
if(ret == -1) { break; } else if(ret == 0) { continue; } else if(ret > 0) {
if(WIFEXITED(st)) { printf("退出的状态码:%d\n", WEXITSTATUS(st)); } if(WIFSIGNALED(st)) { printf("被哪个信号干掉了:%d\n", WTERMSIG(st)); }
printf("child die, pid = %d\n", ret); } }
} else if (pid == 0){ while(1) { printf("child, pid = %d\n",getpid()); sleep(1); } exit(0); }
return 0; }
|