6、进程控制
调用fork函数会创建一个子进程,创建子进程时,并没有将父进程的虚拟地址空间完全拷贝进来;
一开始子进程和父进程是共享虚拟地址空间的,主要共享用户区的数据,内核区的数据还是不一样的,进程id,父进程id都是不同的;(读时共享,写时复制)
6.1 进程退出
状态参数status,会将退出的信息传出;
在父子进程中,子进程退出了,父进程就会的到子进程的退出状态status;
子进程的回收是通过父进程来回收的;子进程退出后,能释放自己用户区的数据,内核区的数据自己是不能释放的。
父进程需要回收子进程内核中的PCB资源.
一般用时,用标准c库的exit()函数。
1、使用exit()函数
/*
#include <stdlib.h>
void exit(int status);
#include <unistd.h>
void _exit(int status);
status参数:是进程退出时的一个状态信息。父进程回收子进程资源的时候可以获取到。
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
printf("hello\n");
printf("world");
exit(0);
return 0; //exit(0)或者_exit(0)调用成功了,return 0就不会执行了
}
编译运行:
2、使用_exit()函数
/*
#include <stdlib.h>
void exit(int status);
#include <unistd.h>
void _exit(int status);
status参数:是进程退出时的一个状态信息。父进程回收子进程资源的时候可以获取到。
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
printf("hello\n");
printf("world");
_exit(0);
return 0; //exit(0)或者_exit(0)调用成功了,return 0就不会执行了
}
_exit(), 前面的hello打印出来了,因为\n刷新了I/O缓冲(将内容刷到屏幕上来)
后面的world没有有刷新I/O缓冲,所以world就丢失了。
6.2 孤儿进程
孤儿进程(Orphan Process): 父进程运行结束,但子进程还在运行(未运行结束),这样的子进程就称为孤儿进程。
- 每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为init(进程id为1) , 而init进程会循环地wait()它的已经退出的子进程。
- 这样, 当一个孤儿进程凄凉地结束了其生命周期的时候, init进程就会代表党和政府出面处理它的一切善后工作。
- 孤儿进程不会有危害。
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main() {
// 创建子进程
pid_t pid = fork();
// 判断是父进程还是子进程
if(pid > 0) {
printf("i am parent process, pid : %d, ppid : %d\n", getpid(), getppid());
} else if(pid == 0) {
sleep(1); //用来演示僵尸进程,睡一秒钟,父进程早就将自己运行完了
// 当前是子进程
printf("i am child process, pid : %d, ppid : %d\n", getpid(),getppid());
}
// for循环
for(int i = 0; i < 3; i++) {
printf("i : %d , pid : %d\n", i , getpid());
}
return 0;
}
运行结果:
在子进程中加入这个:
sleep(1); //用来演示僵尸进程,睡一秒钟,父进程早就将自己运行完了
此时再编译运行:
此时子进程的父进程为init进程(进程id为1)
为什么会在向红色那样,切换到前台继续输出?
运行一个程序,默认会切换到后台,当有输出时,会输出到终端;
当父进程死亡后,会切换到前台;(如下)
但是子进程还没有死,会接着输出。(因为父子进程共享文件描述符表,所以输出的终端是一样的)
6.3 僵尸进程
- 每个进程结束之后,都会释放自己地址空间中的用户区数据,内核区的 PCB没有办法自己释放掉,需要父进程去释放。
- 进程终止时,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。
- 僵尸进程不能被kill -9杀死,
- 这样就会导致一个问题,如果父进程不调用wait () 或waitpid() 的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程,此即为僵尸进程的危害,应当避免。
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main() {
// 创建子进程
pid_t pid = fork();
// 判断是父进程还是子进程
if(pid > 0) {
while(1) {
printf("i am parent process, pid : %d, ppid : %d\n", getpid(), getppid());
sleep(1);
}
} else if(pid == 0) {
// 当前是子进程
printf("i am child process, pid : %d, ppid : %d\n", getpid(),getppid());
}
// for循环
for(int i = 0; i < 3; i++) {
printf("i : %d , pid : %d\n", i , getpid());
}
return 0;
}
运行结果:
子进程运行完了,父进程一直在循环运行。
子进程id:14860,父进程id:14859
可以看到子进程为z,是僵尸进程。
kill -9杀不死僵尸进程;
这里,只能将父进程杀死,子进程会被init进程托管,才能解决,后续会有新的解决方法。