当前位置:   article > 正文

Linux进程的fork、exit、wait等函数;区分父子进程;GDB调试多进程_fork(), lockf(),exit(), wait() , sleep()等的用法

fork(), lockf(),exit(), wait() , sleep()等的用法

Linux系统中进程可以创建子进程。

1. fork函数:创建新进程

  1. #include<sys/types.h>
  2. #include<unistd.h>
  3. pid_t fork(void);
  4. /*
  5. 功能:
  6. 一个进程创建新进程。原进程为父进程,新进程为子进程。
  7. 返回值:
  8. 成功:
  9. 子进程中返回0,父进程中返回子进程的pid。
  10. 失败:-1
  11. 失败原因:
  12. a) 进程总数达到系统上限,此时errno被设置为EAGAIN
  13. b) 系统内存不足,此时errno被设置为ENOMEM
  14. */

fork示例:

  1. #include<stdio.h>
  2. #include<sys/types.h>
  3. #include<unistd.h>
  4. int test01() {
  5. fork(); // fork成功的话,给子进程返回0,给父进程返回子进程的pid
  6. printf("Hello world\n");
  7. }

运行结果:

pc指针:指向当前运行指令的下一条指令。

fork时,父进程的pc指针也会复制,因此子进程会从fork后的指令开始执行。


2. exit和_exit函数:结束进程

  1. #include<stdlib.h>
  2. void exit(int status); // 标准库函数
  3. /*******************************************/
  4. #include<unistd.h>
  5. void _exit(int status); // 系统调用
  6. /*
  7. 功能:
  8. 结束调用此函数的进程,并将函数的退出状态放在status中。
  9. 参数:
  10. status:返回给父进程的参数(低8位有效),
  11. 此参数根据需求填写;
  12. 例如,写123,则正常退出时会传递状态码123;
  13. 若被信号终止,则传递的退出码就是信号的编号,而不再是123.
  14. */

exit和_exit的区别:

exit会刷新缓冲区、关闭文件描述符:

  exit和_exit区别示例:

  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<stdlib.h>
  4. int main(int argc, const char* argv[]) {
  5. printf("Hello.");
  6. _exit(0); // 什么也不输出。
  7. //exit(0); // 输出Hello. exit会刷新缓冲区
  8. //return 0; // 输出Hello. return 0也会刷新缓冲区
  9. }
  10. /*
  11. 注:若printf中有\n,则_exit(0)也会输出Hello.
  12. 因为\n在标准输出中具有刷新缓冲区的作用.
  13. */

return也可结束进程,return和exit的区别是:

(1)若一个进程先后调用funcA,funcB。

         若funcA中使用exit结束,则该进程立即结束,不会再调用funcB;

         若funcA中使用return结束,则表示该funcA函数结束,仍会继续调用funcB.

(2)return退出进程时,不会关闭文件描述符、刷新缓冲区等,而exit可以。


3. wait和waitpid

进程退出时,内核释放该进程大部分资源,包括打开的文件、占用的内存等。但仍保留了该进程的PCB信息,因此需要父进程通过wait和waitpid函数来进一步回收,否则这些进程会变成僵尸进程,消耗系统资源。

(1)wait函数:

  阻塞等待子进程退出,回收子进程资源。

  1. #include<sys/types.h>
  2. #include<sys/wait.h>
  3. pid_t wait(int* status);
  4. /*
  5. 功能:
  6. 等待任意一个子进程结束,回收该子进程资源,并传出子进程退出的状态到status。
  7. 参数:
  8. status:存储进程退出时的状态信息;NULL表示不关心子进程退出状态。
  9. 返回值:
  10. 成功:被回收的子进程号
  11. 失败:-1
  12. */

设置退出时的状态信息status使用方式:

  1. 1)若WIFEXITED(status)非0
  2. 表示进程正常退出,可使用WEXITSTATUS(status)获取进程退出状态码(即exit的参数)。
  3. 2)若WIFSIGNALED(status)非0
  4. 表示进程异常终止(被信号杀死),可使用WTERMSIG(status)获取终止信号的编号。
  5. 3)若WIFSTOPPED(status)非0
  6. 表示进程被暂停,可使用WSTOPSIG(status)获取暂停信号的编号。
  7. 4)若WIFCONTINUED(status)非0
  8. 表示进程暂停后被继续运行。

调用wait函数的进程会被阻塞,直到有一个子进程退出或收到一个不能被忽视的信号。

若调用wait的进程无子进程,wait函数会立即返回;若子进程早就结束,则wait函数也会立即返回,并且回收该早就结束的子进程。

若参数status的值不为NULL,wai函数会把子进程退出时的状态(int型数值)存入status,指出子进程是否为正常结束。该退出的状态信息。

wait和获取子进程退出状态信息示例:

  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<sys/types.h>
  4. #include<sys/wait.h>
  5. #include<stdlib.h>
  6. int main(int argc, const char* argv[]) {
  7. pid_t pid = -1;
  8. int ret = -1;
  9. int status = 0;
  10. pid = fork();
  11. if (-1 == pid) {
  12. perror("fork");
  13. return 1;
  14. }
  15. if (0 == pid) { // 子进程
  16. printf("子进程%d运行...\n", getpid());
  17. sleep(10);
  18. exit(10); // 子进程终止。指定状态码为10.
  19. }
  20. // 父进程
  21. printf("父进程执行。等待子进程退出,回收其资源...\n");
  22. // 父进程阻塞,等待子进程退出
  23. ret = wait(&status);
  24. if (-1 == ret) {
  25. perror("wait");
  26. return 1;
  27. }
  28. printf("父进程回收子进程%d的资源...\n", ret);
  29. // 获取子进程退出的状态
  30. if (WIFEXITED(status)) { // 子进程正常退出
  31. printf("子进程正常退出,状态码:%d\n", WEXITSTATUS(status));
  32. } else if (WIFSIGNALED(status)) {
  33. printf("子进程被信号%d杀死了...\n", WTERMSIG(status));
  34. } else if (WIFSTOPPED(status)) {
  35. printf("子进程被信号%d暂停...\n", WSTOPSIG(status));
  36. }
  37. return 0;
  38. }

 正常退出,显示指定的状态码10,运行结果:

 在另一个终端使用kill -9杀死该进程,运行结果:

在另一个终端直接使用kill杀死该进程,运行结果:

在另一个终端直接使用kill -19暂停该进程,运行结果:

 (2)waitpid函数:

 阻塞等待子进程退出,回收子进程资源;也可设置非阻塞,无子进程退出则立即返回。

  1. #include<sys/types.h>
  2. #include<sys/wait.h>
  3. pid_t waitpid(pid_t pid, int* status, int options);
  4. /*
  5. 功能:
  6. 等待子进程结束,回收该子进程资源,并传出子进程退出的状态到status。
  7. 参数:
  8. pid:
  9. > 0:等待进程号为pid的子进程退出;
  10. = 0:等待同一个进程组中的任何子进程退出;若子进程已加入其他进程组,则不会等待;
  11. = -1:等待任意一个子进程退出,此时和等价于wait函数;
  12. < -1:等待进程组号为pid绝对值的进程组中的任何子进程退出。
  13. status:存储进程退出时的状态信息;NULL表示不关心子进程退出状态。
  14. options:
  15. 0:阻塞父进程,等待子进程退出。此时同wait函数。
  16. WNOHANG:若无任何子进程退出,则立即返回。
  17. WUNTRACED:若子进程暂停,则立即返回。(少用)
  18. 返回值:
  19. a) 有子进程退出时,waitpid返回已收集到的退出子进程的进程号;
  20. b) 若options设为WNOHANG,调用waitpid时无子进程退出,则返回0;
  21. c) 若调用中出错,返回-1,同时设置errno.
  22. 如当pid对应的进程不存在,或pid对应的进程不是调用waitpid进程的子进程,就会出错,此时errno
  23. 被设为ECHILD。
  24. */

 waitpid使用示例:

  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<sys/types.h>
  4. #include<sys/wait.h>
  5. #include<stdlib.h>
  6. int main(int argc, const char* argv[]) {
  7. pid_t pid = -1;
  8. int ret = -1;
  9. int status = 0;
  10. pid = fork();
  11. if (-1 == pid) {
  12. perror("fork");
  13. return 1;
  14. }
  15. if (0 == pid) { // 子进程
  16. printf("子进程%d运行...\n", getpid());
  17. sleep(10);
  18. exit(10); // 子进程终止
  19. }
  20. // 父进程
  21. printf("父进程执行。等待子进程退出,回收其资源...\n");
  22. // 父进程阻塞,等待子进程退出
  23. // ret = waitpid(-1, &status, 0); // 此时等价于wait
  24. ret = waitpid(-1, &status, WNOHANG); // 设置非阻塞
  25. if (-1 == ret) {
  26. perror("wait");
  27. return 1;
  28. }
  29. if (0 == ret) {
  30. printf("暂无子进程退出,waitpid直接返回.\n");
  31. } else {
  32. printf("父进程回收子进程%d的资源...\n", ret);
  33. }
  34. return 0;
  35. }

 运行结果:

 26行添加sleep(11)后,运行结果:


4. 区分父子进程

通过fork的返回值区分:

若fork成功,则在子进程中返回0,父进程中返回子进程的pid。

示例:

  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<sys/types.h>
  4. #include<stdlib.h>
  5. #include<sys/wait.h>
  6. int main(int argc, const char* argv[]) {
  7. pid_t pid = -1;
  8. int status = 0;
  9. int ret = -1;
  10. // 创建子进程。若创建成功,则在子进程中返回0,父进程中返回子进程的pid
  11. pid = fork();
  12. if (0 < pid) {
  13. perror("fork");
  14. return 1;
  15. }
  16. if (0 == pid) { // 子进程
  17. printf("这是子进程。进程号 = %d, 父进程号 = %d\n", getpid(), getppid());
  18. exit(0); // 退出子进程,或者return。
  19. } else { // 父进程
  20. printf("这是父进程。进程号 = %d, 子进程号 = %d\n", getpid(), pid);
  21. }
  22. ret = wait(&status); // 父进程等待回收子进程资源
  23. if(-1 == ret) {
  24. perror("wait");
  25. return 1;
  26. }
  27. return 0;
  28. }

 运行结果:


5. 父子进程关系

(1)写时拷贝(copy-on-write)。读时共享、写时拷贝。父子进程未修改某变量时,无需拷贝;当父子进程之一修改该变量时,子进程就拷贝一份。

(2)在fork之前、之后open对文件描述信息的影响:

  • 在fork前open,父子进程共享一个文件描述信息,包括引用计数、文件偏移等等。子进程复制了父进程的文件表项指针,指向的是同一个文件表项。
  • 在fork后open,父子进程各自有自己的文件描述信息,互不影响。一个文件被打开了两次,即引用计数值为2,每个进程都有自己的一份,文件偏移也互不影响。

简单来说,就是先open再fork,文件描述信息是共享的;先fork再open,文件描述信息是独立的。

文件描述信息是内核为每个进程维护的一个文件描述符表,fork时文件描述信息不会被子进程复制,而是被共享(内核空间被所有进程共享)。因此fork之前open、read、write等操作改变了文件偏移,fork之后子进程会从改变了的文件偏移位置继续操作。


6. 父子进程堆区内存开辟释放问题

父子进程都要释放各自堆区的内存。

如下程序父子进程未释放堆区内存,有内存泄漏问题:

  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<sys/types.h>
  4. #include<sys/wait.h>
  5. #include<stdlib.h>
  6. int main(int argc, const char* argv[]) {
  7. pid_t pid = -1;
  8. int ret = -1;
  9. int status = 0;
  10. int* p = malloc(sizeof(int));
  11. (*p) = 1;
  12. pid = fork();
  13. if (-1 == pid) {
  14. perror("fork");
  15. return 1;
  16. }
  17. if (pid > 0) {
  18. // 父进程
  19. (*p) = 3;
  20. printf("父进程(*p) = %d\n", (*p));
  21. } else {
  22. // 子进程
  23. sleep(1);
  24. printf("子进程(*p) = %d\n", (*p));
  25. exit(0);
  26. }
  27. ret = wait(&status); // 父进程回收子进程资源
  28. if(-1 == ret) {
  29. perror("wait");
  30. return 1;
  31. }
  32. return 0;
  33. }

运行结果:

但存在内存泄漏问题。

valgrind查看内存泄漏情况:

有内存泄漏。 

 修改:在父子进程退出前释放各自的堆区内存

  1. free(p);
  2. p = NULL;

再次查看内存泄漏情况:无内存泄漏。


补充: GDB调试多进程

GDB调试

有如下的多进程程序,需要使用GDB分别调试父子进程:

  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<sys/types.h>
  4. #include<sys/wait.h>
  5. #include<stdlib.h>
  6. int main(int argc, const char* argv[]) {
  7. pid_t pid = -1;
  8. int status = 0;
  9. int ret = -1;
  10. pid = fork();
  11. if (-1 == pid) {
  12. perror("fork");
  13. return 1;
  14. }
  15. if (pid > 0) {
  16. // 父进程
  17. printf("父进程说太强了");
  18. printf("父进程笑尿了");
  19. printf("父进程哈哈哈哈哈");
  20. } else {
  21. // 子进程
  22. printf("子进程说太强了");
  23. printf("子进程笑尿了");
  24. printf("子进程哈哈哈哈哈");
  25. exit(0);
  26. }
  27. ret = wait(&status); // 父进程等待回收子进程资源
  28. if(-1 == ret) {
  29. perror("wait");
  30. return 1;
  31. }
  32. return 0;
  33. }

GDB调试默认跟踪父进程,如下:

如何跟踪子进程呢?

在fork函数调用之前设置跟踪子进程:

set follow-fork-mode child

然后就会跟踪子进程,如下:

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/210690
推荐阅读
相关标签
  

闽ICP备14008679号