----在上一篇文章中,我们已经对进程这个概念有了一个基本的认识,今天我们来继续学习进程的实战操作----父子进程对文件的操作,以及什么是僵尸进程和孤儿进程?下面我们就开始来揭开它们神秘的面纱!
一、父子进程对文件的操作:
1、子进程继承父进程中打开的文件:
父进程先open打开一个文件得到fd,然后再用fork函数来创建子进程。之后在父子进程中各自write向fd中写入内容,代码如下:
1#include
2#include
3#include
4#include
5#include
6#include
7int main() 8{
9
10// 首先打开一个文件
11int fd = -1;
12pid_t pid = -1;
13
14fd = open("1.txt", O_RDWR | O_TRUNC);
15if (fd 0)
16{
17 perror("open");
18 return -1;
19}
20
21// fork创建子进程
22pid = fork();
23if (pid > 0)
24{
25 // 父进程中
26 printf("parent.\n");
27 write(fd, "IOTNB", 5);
28 sleep(1);
29}
30else if (pid == 0)
31{
32 // 子进程
33 printf("child.\n");
34 write(fd, "MCU", 3);
35 sleep(1);
36}
37else
38{
39 perror("fork");
40 exit(-1);
41}
42close(fd);
43return 0;
44 }
输出结果:
说明:这里我们可以看到,子进程继承父进程中打开的文件结果是接续(什么是接续写,简单理解就是对文件写操作完后,另外一个操作接着前面的那个操作继续往文件里面写东西)写的。而且还要注意的是,实际测试时有时候会看到只有一个,有点像分别写。但是实际不是。原因是父进程写完后直接把文件关闭了,关闭后子进程就写不进去内容了
2、父子进程各自独立打开同一文件实现共享:
现在我们来把文件打开操作分别放到父进程和子进程当中去,看看会有什么效果,代码如下:
1#include
2#include
3#include
4#include
5#include
6#include
7 int main(void) 8{
9// 首先打开一个文件
10int fd = -1;
11pid_t pid = -1;
12
13// fork创建子进程
14pid = fork();
15if (pid > 0)
16{
17 // 父进程中
18 fd = open("1.txt", O_RDWR | O_TRUNC );
19 if (fd 0)
20 {
21 perror("open");
22 return -1;
23 }
24
25 printf("parent.\n");
26 write(fd, "hello", 5);
27 sleep(1);
28}
29else if (pid == 0)
30{
31 // 子进程
32 fd = open("1.txt", O_RDWR | O_TRUNC);
33 if (fd 0)
34 {
35 perror("open");
36 return -1;
37 }
38
39 printf("child.\n");
40 write(fd, "world", 5);
41 sleep(1);
42}
43else
44{
45 perror("fork");
46 exit(-1);
47}
48close(fd);
49 return 0;
50 }
输出结果:
说明:最终结果是分别写。原因是父子进程分离后才各自打开的1.txt,这时候这两个进程的PCB已经独立了,文件表也独立了,因此2次读写是完全独立的,当然我们在打开文件操作时,改变open函数里面的参数,使用O_APPEND,还是上面这个操作,最终它的结果把父子进程各自独立打开的fd的文件指针给关联起来,实现接续写,这里我就不举例了,很简单,只要把上面的参数O_TRUNC改成O_APPEND就可以了,这两个参数的意思,在我前面的文章里面有写过,非常详细。
3、小结:其实父进程在没有fork之前自己做的事情对子进程有很大影响,但是父进程fork之后在自己的if里做的事情就对子进程没有影响了。本质原因就是因为fork内部实际上已经复制父进程的PCB生成了一个新的子进程,并且fork返回时子进程已经完全和父进程脱离并且独立被OS调度执行。
二、僵尸进程和孤儿进程解析:
1、什么是僵尸进程?
哈哈哈,听到僵尸两个字是不是有点小害怕,言归正传,在Linux系统中,我们要明白:进程在运行时是需要消耗系统资源(内存、IO),进程终止时理应完全释放这些资源(如果进程消亡后仍然没有释放相应资源则这些资源就丢失了),所以linux系统设计时规定:每一个进程退出时,操作系统会自动回收这个进程涉及到的所有的资源(譬如malloc申请的内容没有free时,当前进程结束时这个内存会被释放,譬如open打开的文件没有close的在程序终止时也会被关闭)。但是操作系统只是回收了这个进程工作时消耗的内存和IO,而并没有回收这个进程本身占用的内存(8KB,主要是task_struct和栈内存),因为进程本身的8KB内存操作系统不能回收需要别人来辅助回收,因此我们每个进程都需要一个帮助它收尸的人,这个人就是这个进程的父进程。所以我们僵尸就是-----子进程先于父进程结束。子进程结束后父进程此时并不一定立即就能帮子进程“收尸”,在这一段(子进程已经结束且父进程尚未帮其收尸)子进程就被成为僵尸进程。下面我们来看演示:
1 #include
2 #include
3 #include
4
5 #define ERR_EXIT(m) \ 6 do \ 7 { \ 8 perror(m); \ 9 exit(EXIT_FAILURE); \10 } while(0)
11
12 int main()13 {
14 pid_t pid;
15
16 if((pid = fork()) == -1)
17 ERR_EXIT("fork");
18else if (pid == 0)
19 {
20 printf("I am the kid,my pid : %d,my father's pid : %d!\n",getpid(),getppid());
21 }
22 else
23{
24 while(1)
25 {
26 printf("I am the father,my pid : %d!\n",getpid());
27 sleep(2);
28 }
29 }
30
31 return 0;
32}
输出结果:
2、什么是孤儿进程?
子进程死亡需要父进程来处理,那么意味着正常的进程应该是子进程先于父进程死亡。当父进程先于子进程死亡时,子进程死亡时没父进程处理,这个死亡的子进程就是孤儿进程。同时,linux系统规定:所有的孤儿进程都自动成为一个特殊进程(进程1,也就是init进程)的子进程。下面我来演示一下:
1#include
2#include
3#include
4int main(void) 5{
6pid_t p1 = -1;
7
8p1 = fork(); // 返回2次
9
10if (p1 == 0)
11{
12 // 这里一定是子进程
13
14 // 先sleep一下让父进程先运行,先死
15 sleep(1);
16
17 printf("子进程, pid = %d.\n", getpid());
18 printf("hello world.\n");
19 printf("子进程, 父进程ID = %d.\n", getppid());
20}
21
22if (p1 > 0)
23{
24 // 这里一定是父进程
25 printf("父进程, pid = %d.\n", getpid());
26 printf("父进程, p1 = %d.\n", p1);
27}
28
29if (p1 0)
30{
31 // 这里一定是fork出错了
32}
33
34// 在这里所做的操作
35//printf("hello world, pid = %d.\n", getpid());
36
37return 0;
38 }
输出结果:
说明:这里父进程先运行死掉了,但是我们后面并没有发现特殊进程init为1,而是908,这其实是跟ubuntu系统有关系的,真实是为1的。
三、总结:
后面还会接着继续更新进程的文章,今天的学习加深了对进程的进一步理解,后面会写回收进程的具体实现方法,欢迎关注!
---欢迎关注公众号,可以查看往期的文章:
加我个人微信,然后拉进交流群(对文章中写有不对的地方,可以批评指出,虚心你向您学,一起进步。群里只能讨论技术方面的,发广告,立刻飞机):