赞
踩
Linux系统中进程可以创建子进程。
- #include<sys/types.h>
- #include<unistd.h>
-
- pid_t fork(void);
- /*
- 功能:
- 一个进程创建新进程。原进程为父进程,新进程为子进程。
- 返回值:
- 成功:
- 子进程中返回0,父进程中返回子进程的pid。
- 失败:-1
- 失败原因:
- a) 进程总数达到系统上限,此时errno被设置为EAGAIN
- b) 系统内存不足,此时errno被设置为ENOMEM
- */
fork示例:
- #include<stdio.h>
- #include<sys/types.h>
- #include<unistd.h>
-
-
- int test01() {
- fork(); // fork成功的话,给子进程返回0,给父进程返回子进程的pid
- printf("Hello world\n");
- }
运行结果:
pc指针:指向当前运行指令的下一条指令。
fork时,父进程的pc指针也会复制,因此子进程会从fork后的指令开始执行。
- #include<stdlib.h>
-
- void exit(int status); // 标准库函数
-
- /*******************************************/
-
- #include<unistd.h>
-
- void _exit(int status); // 系统调用
- /*
- 功能:
- 结束调用此函数的进程,并将函数的退出状态放在status中。
- 参数:
- status:返回给父进程的参数(低8位有效),
- 此参数根据需求填写;
- 例如,写123,则正常退出时会传递状态码123;
- 若被信号终止,则传递的退出码就是信号的编号,而不再是123.
- */
exit和_exit的区别:
exit会刷新缓冲区、关闭文件描述符:
exit和_exit区别示例:
- #include<stdio.h>
- #include<unistd.h>
- #include<stdlib.h>
-
-
- int main(int argc, const char* argv[]) {
- printf("Hello.");
-
- _exit(0); // 什么也不输出。
- //exit(0); // 输出Hello. exit会刷新缓冲区
- //return 0; // 输出Hello. return 0也会刷新缓冲区
- }
- /*
- 注:若printf中有\n,则_exit(0)也会输出Hello.
- 因为\n在标准输出中具有刷新缓冲区的作用.
- */
return也可结束进程,return和exit的区别是:
(1)若一个进程先后调用funcA,funcB。
若funcA中使用exit结束,则该进程立即结束,不会再调用funcB;
若funcA中使用return结束,则表示该funcA函数结束,仍会继续调用funcB.
(2)return退出进程时,不会关闭文件描述符、刷新缓冲区等,而exit可以。
进程退出时,内核释放该进程大部分资源,包括打开的文件、占用的内存等。但仍保留了该进程的PCB信息,因此需要父进程通过wait和waitpid函数来进一步回收,否则这些进程会变成僵尸进程,消耗系统资源。
阻塞等待子进程退出,回收子进程资源。
- #include<sys/types.h>
- #include<sys/wait.h>
-
- pid_t wait(int* status);
- /*
- 功能:
- 等待任意一个子进程结束,回收该子进程资源,并传出子进程退出的状态到status。
- 参数:
- status:存储进程退出时的状态信息;NULL表示不关心子进程退出状态。
- 返回值:
- 成功:被回收的子进程号
- 失败:-1
- */
- (1)若WIFEXITED(status)非0:
- 表示进程正常退出,可使用WEXITSTATUS(status)获取进程退出状态码(即exit的参数)。
- (2)若WIFSIGNALED(status)非0:
- 表示进程异常终止(被信号杀死),可使用WTERMSIG(status)获取终止信号的编号。
- (3)若WIFSTOPPED(status)非0:
- 表示进程被暂停,可使用WSTOPSIG(status)获取暂停信号的编号。
- (4)若WIFCONTINUED(status)非0:
- 表示进程暂停后被继续运行。
调用wait函数的进程会被阻塞,直到有一个子进程退出或收到一个不能被忽视的信号。
若调用wait的进程无子进程,wait函数会立即返回;若子进程早就结束,则wait函数也会立即返回,并且回收该早就结束的子进程。
若参数status的值不为NULL,wai函数会把子进程退出时的状态(int型数值)存入status,指出子进程是否为正常结束。该退出的状态信息。
wait和获取子进程退出状态信息示例:
- #include<stdio.h>
- #include<unistd.h>
- #include<sys/types.h>
- #include<sys/wait.h>
- #include<stdlib.h>
-
- int main(int argc, const char* argv[]) {
- pid_t pid = -1;
- int ret = -1;
- int status = 0;
-
- pid = fork();
- if (-1 == pid) {
- perror("fork");
- return 1;
- }
-
- if (0 == pid) { // 子进程
- printf("子进程%d运行...\n", getpid());
- sleep(10);
- exit(10); // 子进程终止。指定状态码为10.
- }
-
- // 父进程
- printf("父进程执行。等待子进程退出,回收其资源...\n");
-
- // 父进程阻塞,等待子进程退出
- ret = wait(&status);
- if (-1 == ret) {
- perror("wait");
- return 1;
- }
- printf("父进程回收子进程%d的资源...\n", ret);
-
- // 获取子进程退出的状态
- if (WIFEXITED(status)) { // 子进程正常退出
- printf("子进程正常退出,状态码:%d\n", WEXITSTATUS(status));
- } else if (WIFSIGNALED(status)) {
- printf("子进程被信号%d杀死了...\n", WTERMSIG(status));
- } else if (WIFSTOPPED(status)) {
- printf("子进程被信号%d暂停...\n", WSTOPSIG(status));
- }
- return 0;
- }
正常退出,显示指定的状态码10,运行结果:
在另一个终端使用kill -9杀死该进程,运行结果:
在另一个终端直接使用kill杀死该进程,运行结果:
在另一个终端直接使用kill -19暂停该进程,运行结果:
阻塞等待子进程退出,回收子进程资源;也可设置非阻塞,无子进程退出则立即返回。
- #include<sys/types.h>
- #include<sys/wait.h>
-
- pid_t waitpid(pid_t pid, int* status, int options);
- /*
- 功能:
- 等待子进程结束,回收该子进程资源,并传出子进程退出的状态到status。
- 参数:
- pid:
- > 0:等待进程号为pid的子进程退出;
- = 0:等待同一个进程组中的任何子进程退出;若子进程已加入其他进程组,则不会等待;
- = -1:等待任意一个子进程退出,此时和等价于wait函数;
- < -1:等待进程组号为pid绝对值的进程组中的任何子进程退出。
- status:存储进程退出时的状态信息;NULL表示不关心子进程退出状态。
- options:
- 0:阻塞父进程,等待子进程退出。此时同wait函数。
- WNOHANG:若无任何子进程退出,则立即返回。
- WUNTRACED:若子进程暂停,则立即返回。(少用)
- 返回值:
- a) 有子进程退出时,waitpid返回已收集到的退出子进程的进程号;
- b) 若options设为WNOHANG,调用waitpid时无子进程退出,则返回0;
- c) 若调用中出错,返回-1,同时设置errno.
- 如当pid对应的进程不存在,或pid对应的进程不是调用waitpid进程的子进程,就会出错,此时errno
- 被设为ECHILD。
- */
waitpid使用示例:
- #include<stdio.h>
- #include<unistd.h>
- #include<sys/types.h>
- #include<sys/wait.h>
- #include<stdlib.h>
-
- int main(int argc, const char* argv[]) {
- pid_t pid = -1;
- int ret = -1;
- int status = 0;
-
- pid = fork();
- if (-1 == pid) {
- perror("fork");
- return 1;
- }
-
- if (0 == pid) { // 子进程
- printf("子进程%d运行...\n", getpid());
- sleep(10);
- exit(10); // 子进程终止
- }
-
- // 父进程
- printf("父进程执行。等待子进程退出,回收其资源...\n");
-
- // 父进程阻塞,等待子进程退出
- // ret = waitpid(-1, &status, 0); // 此时等价于wait
- ret = waitpid(-1, &status, WNOHANG); // 设置非阻塞
- if (-1 == ret) {
- perror("wait");
- return 1;
- }
-
- if (0 == ret) {
- printf("暂无子进程退出,waitpid直接返回.\n");
- } else {
- printf("父进程回收子进程%d的资源...\n", ret);
- }
-
- return 0;
- }
运行结果:
26行添加sleep(11)后,运行结果:
通过fork的返回值区分:
若fork成功,则在子进程中返回0,父进程中返回子进程的pid。
示例:
- #include<stdio.h>
- #include<unistd.h>
- #include<sys/types.h>
- #include<stdlib.h>
- #include<sys/wait.h>
-
- int main(int argc, const char* argv[]) {
- pid_t pid = -1;
- int status = 0;
- int ret = -1;
-
- // 创建子进程。若创建成功,则在子进程中返回0,父进程中返回子进程的pid
- pid = fork();
- if (0 < pid) {
- perror("fork");
- return 1;
- }
-
- if (0 == pid) { // 子进程
- printf("这是子进程。进程号 = %d, 父进程号 = %d\n", getpid(), getppid());
- exit(0); // 退出子进程,或者return。
- } else { // 父进程
- printf("这是父进程。进程号 = %d, 子进程号 = %d\n", getpid(), pid);
- }
-
- ret = wait(&status); // 父进程等待回收子进程资源
- if(-1 == ret) {
- perror("wait");
- return 1;
- }
-
- return 0;
- }
运行结果:
(1)写时拷贝(copy-on-write)。读时共享、写时拷贝。父子进程未修改某变量时,无需拷贝;当父子进程之一修改该变量时,子进程就拷贝一份。
(2)在fork之前、之后open对文件描述信息的影响:
简单来说,就是先open再fork,文件描述信息是共享的;先fork再open,文件描述信息是独立的。
文件描述信息是内核为每个进程维护的一个文件描述符表,fork时文件描述信息不会被子进程复制,而是被共享(内核空间被所有进程共享)。因此fork之前open、read、write等操作改变了文件偏移,fork之后子进程会从改变了的文件偏移位置继续操作。
父子进程都要释放各自堆区的内存。
如下程序父子进程未释放堆区内存,有内存泄漏问题:
- #include<stdio.h>
- #include<unistd.h>
- #include<sys/types.h>
- #include<sys/wait.h>
- #include<stdlib.h>
-
- int main(int argc, const char* argv[]) {
- pid_t pid = -1;
- int ret = -1;
- int status = 0;
- int* p = malloc(sizeof(int));
- (*p) = 1;
-
- pid = fork();
- if (-1 == pid) {
- perror("fork");
- return 1;
- }
-
- if (pid > 0) {
- // 父进程
- (*p) = 3;
- printf("父进程(*p) = %d\n", (*p));
- } else {
- // 子进程
- sleep(1);
- printf("子进程(*p) = %d\n", (*p));
- exit(0);
- }
-
- ret = wait(&status); // 父进程回收子进程资源
- if(-1 == ret) {
- perror("wait");
- return 1;
- }
-
- return 0;
- }
运行结果:
但存在内存泄漏问题。
valgrind查看内存泄漏情况:
有内存泄漏。
修改:在父子进程退出前释放各自的堆区内存:
- free(p);
- p = NULL;
再次查看内存泄漏情况:无内存泄漏。
有如下的多进程程序,需要使用GDB分别调试父子进程:
- #include<stdio.h>
- #include<unistd.h>
- #include<sys/types.h>
- #include<sys/wait.h>
- #include<stdlib.h>
-
- int main(int argc, const char* argv[]) {
- pid_t pid = -1;
- int status = 0;
- int ret = -1;
-
- pid = fork();
- if (-1 == pid) {
- perror("fork");
- return 1;
- }
-
- if (pid > 0) {
- // 父进程
- printf("父进程说太强了");
- printf("父进程笑尿了");
- printf("父进程哈哈哈哈哈");
- } else {
- // 子进程
- printf("子进程说太强了");
- printf("子进程笑尿了");
- printf("子进程哈哈哈哈哈");
- exit(0);
- }
-
- ret = wait(&status); // 父进程等待回收子进程资源
- if(-1 == ret) {
- perror("wait");
- return 1;
- }
-
- return 0;
- }
GDB调试默认跟踪父进程,如下:
如何跟踪子进程呢?
在fork函数调用之前设置跟踪子进程:
set follow-fork-mode child
然后就会跟踪子进程,如下:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。