1.学习进程创建fork
2.学习进程等待
3.学习到进程程序替换,微型shell,重新认识shell运行原理
4.学习到进程终止
1.创建子进程
进程调用fork,当控制转移到内核中的fork代码后,内核做:
1.分配新的内存块和内存数据(pcb 页表等)结构给子进程。
2.将父进程部分数据结构内容拷贝至子进程。
3.添加子进程到系统进程列表当中。
4.fork返回,开始调调器调度。
思考:
1.如何理解fork函数有两个返回值问题?
2.如何理解fork返回之后,给父进程返回子进程pid,给子进程返回0。
3、如何理解同一个id值,怎么会保存两个不同的值,让if elseif 同时执行。
fork
函数在调用后会创建一个新的进程,这个新的进程几乎是原进程的完全复制,包括代码、数据和堆栈等。因此,fork
函数在父进程中返回子进程的进程ID,而在子进程中返回0。
当调用 fork
后,父进程将得到子进程的进程ID,而子进程将得到0。通过这种方式,程序可以根据返回值的不同来确定当前代码是在父进程中执行还是在子进程中执行。
在一个程序中,父进程和子进程有各自独立的内存空间,它们之间并不共享变量的值。因此,如果你在父进程中设置了某个变量的值为 x,那么在子进程中这个变量的值仍然是被复制过来的初始值。所以虽然父子进程有相同的变量名,但它们实际上指向了不同的内存地址,因此可以使得 if 和 elseif 在不同的进程中执行。
2.进程终止
代码运行完毕,结果正确
代码运行完毕,结果不正确
代码异常终止
进程常见退出方法
正常终止(可以通过echo $?查看进程退出码):
1.从main返回
2.调用exit
3.-exit
异常退出:
ctrl +c,信号终止。
退出码的意义:0:标识失败,!0具体是几,标识不同的错误。(退出码都必须有对应的退出码的文字描述)1.可以自定义,2.可以使用系统的映射关系(使用不太频繁);
我们可以使用一个函数char*strerror(int errnum)调用系统的映射
#include<stdio.h>
2 #include<string.h>
3 int main()
4 {
5 for(int i=0;i<=200;i++)
6 {
7 printf("%d:%s\n",i,strerror(i));
8
9 }
10
11 return 0;
12 }
结果:
3.进程等待
子进程退出,父进程如果不管不顾,就可能僵尸进程的问题,继而造成内存泄漏。
另外,进程一旦变成僵尸进程状态,kill -9也无能为力,因为谁也没有办法杀死一个已经死去的进程。
最后,父进程每次给子进程的让任务完成的如何,我们需要知道,如:子进程运行完成,结果对还是不对,或者有没有是否正常退出。
父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。
一个代码运行完,有三种情况,代码完成,结果对,代码完成,结果错,代码异常。
我们可以使用waitpid函数来获取子进程退出时候的pid和退出信息(退出码和终止信号)上面参数为pid为子进程的id,status为退出信息。status的内核结构又是什么样的呢
低7为为终止信号,如何是零则表示正常,如果是其他表示异常,退出状态如果正常则返回退出码,反之返回异常数字,我们来编写代码验证一下:
1 #include<stdio.h>
2 #include<string.h>
3 #include<unistd.h>
4 #include <sys/types.h>
5 #include <sys/time.h>
6 int main()
7 {
8 pid_t id =fork();
9 if(id==0)
10 {
11 int cnt=5;
12 while(cnt)
13 {
14 printf("我是子进程:%d,父进程:%d,cnt=%d\n",getpid(),getppid(),cnt--);
15 sleep(1);
16 }
17 exit(10);
18 }
19 // sleep(15)
20 int status=0;
21 pid_t ret=waitpid(id,&status,0);
22 if(id>0)
23 {
24 printf("wait success:%d, status:%d,sig number:%d,child exit code:%d\n",ret,status,(status&0x7F),(status>>8)&0xFF);
25
26
27 }
28
29 return 0;
30 }
~
运行结果:
我们代码中创建了一个子进程,然后让打印循坏五次,exit(10)退出循坏,正常退出码为10;
然后我们调用waitpid函数,将id status 和低七位和高八位的输出可以发现正是子进程的id,终止信号和退出码。
我们也可以故意将代码写错,看看返回的信息是什么。我们在代码中加入cnt=cntkil/0;
我们可以发现退出信号异常。
阻塞和非阻塞
阻塞就是父进程等待着子进程的过程,子进程不结束一直在等待,叫做阻塞等待。
非阻塞等待就是状态检测,子进程没有结束,我们直接返回 ,叫做非阻塞等待。我们可以运用使用代码演示一下非阻塞等待:
1 nclude<stdio.h>
2 #include<string.h>
3 #include<unistd.h>
4 #include<sys/types.h>
5 #include<sys/wait.h>
6 #include<assert.h>
7
8 int main()
9 {
10 pid_t id = fork();
11 assert(id != -1);
12 if (id == 0)
13 {
14 int cnt = 5;
15 while (cnt)
16 {
17 printf("我是子进程:%d,父进程:%d,cnt=%d\n", getpid(), getppid(), cnt--);
18 sleep(1);
19 }
20 _exit(10);
21 }
22
23 int status = 0;
24
25 while (1)
26 {
27 pid_t ret = waitpid(id, &status, WNOHANG);//WNOHANG表示非阻塞
28
29 if (ret == 0)
30 {
31 printf("wait done, but child is running---\n");
32 usleep(100000); // 延时100毫秒
33 }
34 else if (ret > 0)
35 {
36 printf("child process exited with status: %d\n", status);
37 break;
38 }
39 else
40 {
41 perror("Error in waitpid");
42 break;
43 }
44 }
45 return 0;
46 }
如上述代码,返回值ret如果为零,表示子进程没有结束,我们直接返回,返回值位为一,子进程结束,我们结束等待。返回值为其他则显示异常。
非阻塞等待的好处在于可以让父进程在等待子进程退出的同时继续执行其他任务,不会被阻塞住。这在某些场景下非常有用,特别是当父进程需要处理多个子进程或者需要并行地执行一些任务时。
非阻塞等待能够提高程序的并发性和响应速度,使得程序能够更加高效地处理多任务或者多进程的情况。