当前位置:   article > 正文

linux进程——父子进程(十三)_子进程pid

子进程pid

目录

一 什么是进程

1.1 进程概念

 1.2 获取进程PID号 —— getpid()

1.3 getppid()获取父进程PID号

二 fork()创建子进程

2.1 fork()函数原型:

2.2 使用示例 1使用 fork()创建子进程。

三 fork创建新进程时发生了什么事?

3.1 fork()函数之后,子进程会获得父进程所有文件描述符的副本

 3.2父、子进程中对应的文件描述符指向了相同的文件表 ——代码测试

四 linux 全拷贝和写实拷贝

4.1全拷贝:

4.2写实拷贝:

五 vfork()函数

5.1 vfork函数和fork函数的区别

5.2 vfork代码测试:

 5.3fork()代码测试


一 什么是进程

1.1 进程概念

进程其实就是一个可执行程序的实例,可执行程序就是一个可执行文件,文件是一个 静态的概念,存放磁盘中,如果可执行文件没有被运行,那它将不会产生什么作用,当它被运行之后,它将 会对系统环境产生一定的影响,所以可执行程序的实例就是可执行文件被运行。

进程是一个动态过程,而非静态文件,它是程序的一次运行过程,当应用程序被加载到内存中运行之后 它就称为了一个进程,当程序运行结束后也就意味着进程终止,这就是进程的一个生命周期。

 1.2 获取进程PID号 —— getpid()

Linux 系统下的每一个进程都有一个进程号(process ID,简称 PID),进程号是一个正数,用于唯一标 识系统中的某一个进程。

在应用程序中,可通过系统调用 getpid()来获取本进程的进程号,其函数原型如下所示:

  1. #include <sys/types.h>
  2. #include <unistd.h>
  3. pid_t getpid(void);

应用代码测试:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <sys/types.h>
  4. #include <unistd.h>
  5. int main(void)
  6. {
  7. pid_t pid = getpid();
  8. printf("本进程的 PID 为: %d\n", pid);
  9. exit(0);
  10. }

1.3 getppid()获取父进程PID号

除了 getpid()用于获取本进程的进程号之外,还可以使用 getppid()系统调用获取父进程的进程号,其函 数原型如下所示:

  1. #include <sys/types.h>
  2. #include <unistd.h>
  3. pid_t getppid(void);

返回值:返回父进程的PID号 

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <sys/types.h>
  4. #include <unistd.h>
  5. int main(void)
  6. {
  7. pid_t pid = getpid(); //获取本进程 pid
  8. printf("本进程的 PID 为: %d\n", pid);
  9. pid = getppid(); //获取父进程 pid
  10. printf("父进程的 PID 为: %d\n", pid);
  11. exit(0);
  12. }

二 fork()创建子进程

一个现有的进程可以调用 fork()函数创建一个新的进程,调用 fork()函数的进程称为父进程,由 fork()函 数创建出来的进程被称为子进程(child process),fork()函数原型如下所示(fork()为系统调用):

2.1 fork()函数原型:

  1. #include <unistd.h>
  2. pid_t fork(void);

返回值:

  • 失败返回-1 不创建子进程,并设置 errno 
  • 返回值为非负数为父进程
  • 返回值为0时是子进程 

在诸多的应用中,创建多个进程是任务分解时行之有效的方法,譬如,某一网络服务器进程可在监听客 户端请求的同时,为处理每一个请求事件而创建一个新的子进程,与此同时,服务器进程会继续监听更多的 客户端连接请求。在一个大型的应用程序任务中,创建子进程通常会简化应用程序的设计,同时提高了系统 的并发性(即同时能够处理更多的任务或请求,多个进程在宏观上实现同时运行)。

理解 fork()系统调用的关键在于,完成对其调用后将存在两个进程,一个是原进程(父进程)、另一个 则是创建出来的子进程,并且每个进程都会从 fork()函数的返回处继续执行,会导致调用 fork()返回两次值, 子进程返回一个值、父进程返回一个值。在程序代码中,可通过返回值来区分是子进程还是父进程。 fork()调用成功后,将会在父进程中返回子进程的 PID,而在子进程中返回值是 0;如果调用失败,父进 程返回值-1,不创建子进程,并设置 errno。 fork()调用成功后,子进程和父进程会继续执行 fork()调用之后的指令,子进程、父进程各自在自己的进 程空间中运行。事实上,子进程是父进程的一个副本,譬如子进程拷贝了父进程的数据段、堆、栈以及继承 了父进程打开的文件描述符,父进程与子进程并不共享这些存储空间,这是子进程对父进程相应部分存储 空间的完全复制,执行 fork()之后,每个进程均可修改各自的栈数据以及堆段中的变量,而并不影响另一个进程。

2.2 使用示例 1使用 fork()创建子进程。

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <sys/types.h>
  4. #include <unistd.h>
  5. int main()
  6. {
  7. int pid = fork(); //父进程的返回值pid为子进程的pid号,子进程的返回值为0
  8. switch (pid)
  9. {
  10. case -1:
  11. printf("fork error\n");
  12. break;
  13. case 0:
  14. printf("this is child ;PID = %d\n",getpid());
  15. break;
  16. default:
  17. printf("this is father;PID = %d\n",getpid());
  18. break;
  19. }
  20. return 0;
  21. }

 从打印结果可知,fork()之后的语句被执行了两次,所以 switch…case 语句被执行了两次

  • 第一次进入 到了"case 0"分支,表示进入子进程
  • 第二次进入到了 default 分支,表示当前处于父进程

父进程的返回值pid为子进程的id号,子进程的返回值为0 

三 fork创建新进程时发生了什么事?

3.1 fork()函数之后,子进程会获得父进程所有文件描述符的副本

调用 fork()函数之后,子进程会获得父进程所有文件描述符的副本,这些副本的创建方式类似于 dup(), 这也意味着父、子进程对应的文件描述符均指向相同的文件表,如下图所示:

由此可知,子进程拷贝了父进程的文件描述符表,使得父、子进程中对应的文件描述符指向了相同的文件表,也意味着父、子进程中对应的文件描述符指向了磁盘中相同的文件,因而这些文件在父、子进程间实 现了共享,譬如,如果子进程更新了文件偏移量,那么这个改变也会影响到父进程中相应文件描述符的位置 偏移量。  

 3.2父、子进程中对应的文件描述符指向了相同的文件表 ——代码测试

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <sys/types.h>
  4. #include <unistd.h>
  5. #include <sys/types.h>
  6. #include <sys/stat.h>
  7. #include <fcntl.h>
  8. #include <string.h>
  9. #define MY_FILE "./file.txt"
  10. int main()
  11. {
  12. char buf [128];
  13. int fd = open(MY_FILE,O_RDWR);
  14. if(-1 == fd)
  15. {
  16. perror("open error? :");
  17. exit(-1);
  18. }
  19. int pid = fork();
  20. switch (pid)
  21. {
  22. case -1:
  23. printf("fork error\n");
  24. break;
  25. case 0:
  26. for(int i = 0;i<4;i++)
  27. {
  28. write(fd," child",strlen(" child"));
  29. }
  30. printf("子进程\n");
  31. close(fd);
  32. break;
  33. default:
  34. printf("父进程\n");
  35. for(int i = 0;i<4;i++)
  36. {
  37. write(fd," father",strlen(" father"));
  38. }
  39. close(fd);
  40. break;
  41. }
  42. read(fd,buf,128);
  43. printf("read :%s\n",buf);
  44. return 0;
  45. }

上述代码中,父进程 open 打开文件之后,才调用 fork()创建了子进程,所以子进程了继承了父进程打 开的文件描述符 fd,我们需要验证的便是两个进程对文件的写入操作是分别各自写入、还是每次都在文件 末尾接续写入

 

有上述测试结果可知,此种情况下,父、子进程分别对同一个文件进行写入操作,结果是接续写,不管 是父进程,还是子进程,在每次写入时都是从文件的末尾写入,很像使用了 O_APPEND 标志的效果。其原 因也非常简单,子进程继承了父进程的文件描述符,两个文件描述符都指向了一个相同的文件表,意味着它们的文件偏移量是同一个、绑定在了一起,相互影响,子进程改变了文件的位置 偏移量就会作用到父进程,同理,父进程改变了文件的位置偏移量就会作用到子进程

但是执行结果不一定是父进程先执行或者子进程先执行,这个不一定的,这个要看系统的调度了 

四 linux 全拷贝和写实拷贝

4.1全拷贝:

传统的fork ()系统调用直接把所有的资源复制给新创建的进程。 这种实现过于简单并且效率低下,因为它拷贝的数据也许并不共享,更糟的情况是,如果新进程打算立即执行一个新的映像,那么所有的拷贝都将前功尽弃。 

4.2写实拷贝:

Linux的fork ()使用写时拷贝(copy-on-write)页实现。 写时拷贝是一种可以推迟甚至免除拷贝数据的技术。 内核此时并不复制整个进程地址空间,而是让父进程和子进程共享同一个拷贝。 只有在需要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝。 

五 vfork()函数

5.1 vfork函数和fork函数的区别

  • 关键区别一:vfork直接使用父进程存储空间,不拷贝
  • 关键区别二:vfork保证子进程先运行,当子进程调用exit退出时,父进程才开始执行

5.2 vfork代码测试:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <sys/types.h>
  4. #include <unistd.h>
  5. int main()
  6. {
  7. int data =10; //测试数据
  8. int pid = vfork();
  9. switch (pid)
  10. {
  11. case -1:
  12. printf("fork error\n");
  13. break;
  14. case 0:
  15. printf("this is child ;PID = %d\n",getpid());
  16. printf("pid = %d\n",pid);
  17. data =data + 100;
  18. exit(0);
  19. break;
  20. default:
  21. printf("this is father;PID = %d\n",getpid());
  22. printf("pid = %d\n",pid);
  23. printf("data = %d \n",data);
  24. break;
  25. }
  26. return 0;
  27. }

根据上述代码,man函数进来,创建一个int型的变量data,在子进程加100,在父进程中打印,最data为110,证明公用了一块地址

 5.3fork()代码测试

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <sys/types.h>
  4. #include <unistd.h>
  5. int main()
  6. {
  7. int data = 10;
  8. int pid = fork();
  9. switch (pid)
  10. {
  11. case -1:
  12. printf("fork error\n");
  13. break;
  14. case 0:
  15. printf("this is child ;PID = %d\n",getpid());
  16. printf("pid = %d\n",pid);
  17. data += 100;
  18. printf("子进程的data = %d\n",data);
  19. break;
  20. default:
  21. printf("this is father;PID = %d\n",getpid());
  22. printf("pid = %d\n",pid);
  23. printf("父进程的data = %d\n",data);
  24. break;
  25. }
  26. return 0;
  27. }

 

而fork()代码中,data在子进程中进行自加100,在打印结果就是子进程打印的data就是100,而父进程的data为10,没有改变,因为在父子进程在写入新的数据的时候,数据会被复制,从而使各个进程拥有各自的拷贝

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

闽ICP备14008679号