一:PCB进程控制块
在/usr/src/kernels/3.10.0-1160.11.1.el7.x86_64/include/linux 目下的 vim sched.h 查看 struct task_struct 结构体
PCB 的本质就是一个结构体:
struct task_struct {
...
进程 id
文件描述符
进程的状态:初始状态,就绪状态,运行态,挂起态,终止态
进程工作目录
信号相关信息资源
用户 id 组 id
}
PCB 是操作系统进行调度经常会被读取的信息,PCB 进程控制块是常驻内存的,存放在系统专门开辟的PCB区域内的。
二:内存映射
用户空间映射时,会映射到不同的区域,内核空间映射时,会映射到同一区域的不同地方 (没有隔离)
三:Fork 创建子进程
man 2 fork 查看fork() 系统函数的使用
//fork_learn.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
int main(int argc, char* argv[]){
printf("fork before\n");
printf("fork before\n");
printf("fork before\n");
printf("fork before\n");
pid_t pid = fork();
if (pid==-1){
printf("fork error!\n");
}else if(pid==0){
printf("I'am child process! pid=%d, ppid=%d\n", getpid(), getppid());
}else{
printf("I'am parent procss! child pid=%d, pid=%d, ppid=%d\n", pid, getpid(), getppid());
}
return 0;
}
[root@localhost c_learn]# vim fork_learn.c
[root@localhost c_learn]# gcc fork_learn.c -o fork_learn
[root@localhost c_learn]# ./fork_learn
fork before
fork before
fork before
fork before
I'am parent procss! child pid=75936, pid=75935, ppid=107096
I'am child process! pid=75936, ppid=1
[root@localhost c_learn]# ps aux |grep 107096
root 75942 0.0 0.0 112824 984 pts/0 S+ 07:57 0:00 grep --color=auto 107096
root 107096 0.0 0.2 115756 2268 pts/0 Ss 4月18 0:00 -bash
[root@localhost c_learn]#
getpid() 用于获取当前进程的进程 id号, getppid() 用于获取当前进程的父进程 id 号。
在上面的结果中,我们可以看到先是父进程打印,后是子进程打印,父进程中子进程的pid child pid = 75936 和 子进程中pid=75936 是一致的,但是在父进程的pid=75935,而子进程中的父进程id 是 1,二者不一致,为什么呢?后面的小节会讲到。还有一个问题,我们看到通过 ps aux 命令 搜索进程号是 107096 的进程,结果发现时 -bash 进程,那么就说在bash进程中启动了父进程。
四:父子进程共享
1. 父子进程相同
刚fork后,data段,text代码段,堆,栈,环境变量,全局变量,进程的工作目录,信号处理处理方式是相同的
2. 父子进程不同的
进程id,返回值,各自的进程,进程的创建时间,闹钟,未决信号集
3. 父子进程共享的
map 映射区(读时共享,写时复制,写时复制的意思就是在进行写操作的时候,对原有的数据进行一份拷贝,然后进行修改,对原有的一份数据不做影响)
//fork_share_learn.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
int main(int argc, char* argv[]){
printf("fork before\n");
printf("fork before\n");
printf("fork before\n");
printf("fork before\n");
pid_t pid = fork();
int num = 100;
if (pid==-1){
printf("fork error!\n");
}else if(pid==0){
printf("I'am child process! pid=%d, ppid=%d\n", getpid(), getppid());
printf("child num %d\n", num);
}else{
num = 200;
printf("I'am parent procss! child pid=%d, pid=%d, ppid=%d\n", pid, getpid(), getppid());
printf("parent num %d\n", num);
}
return 0;
}
[root@localhost c_learn]# vim fork_share_learn.c
[root@localhost c_learn]# ./fork_share_learn
fork before
fork before
fork before
fork before
I'am parent procss! child pid=78215, pid=78214, ppid=107096
parent num 200
[root@localhost c_learn]# I'am child process! pid=78215, ppid=1
child num 100
由上面的结果看出,虽然在父进程中对数字num 进行了修改,但是子进程中的num依然是100,没有改变,说明在父进程中对变量的修改是不会影响到子进程中的数据,因为写时复制。
五:进程回收
1. 孤儿进程:父进程先于子进程结束,则子进程会成为孤儿进程,子进程的父进程就会是init进程,由init进程(进程孤儿院)来完成进程回收。
2. 僵尸进程:子进程结束,但是父进程尚未被回收,子进程就会成为僵尸进程。
进程回收:回收的就是残留在内核中3~4G的数据,包括CPU控制的PCB进程控制块,各个进程的PCB会已以链表的形式被CPU进行管理,CPU通过对PCB的控制来达到进程来回之间运行。
孤儿进程
//orphan_process.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
int main(int argc, char* argv[]){
printf("fork before\n");
printf("fork before\n");
printf("fork before\n");
printf("fork before\n");
pid_t pid = fork();
if (pid==-1){
printf("fork error!\n");
}else if(pid==0){
sleep(2);
printf("I'am child process! pid=%d, ppid=%d\n", getpid(), getppid());
}else{
printf("I'am parent procss! child pid=%d, pid=%d, ppid=%d\n", pid, getpid(), getppid());
}
return 0;
}
[root@localhost c_learn]# vim orphan_process.c
[root@localhost c_learn]# gcc orphan_process.c -o orphan_process
[root@localhost c_learn]# ./orphan_process
fork before
fork before
fork before
fork before
I'am parent procss! child pid=79353, pid=79352, ppid=107096
[root@localhost c_learn]# I'am child process! pid=79353, ppid=1
子进程在休息2秒之后才会结束,而父进程早已经死亡,所以子进程就会变成孤儿进程,查看孤儿进程的父进程:
发现父进程是 /usr/lib/systemd/systemd
僵尸进程
// zombies_process.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdbool.h>
int main(int argc, char* argv[]){
printf("fork before\n");
printf("fork before\n");
printf("fork before\n");
printf("fork before\n");
pid_t pid = fork();
if (pid==-1){
printf("fork error!\n");
}else if(pid==0){
printf("I'am child process! pid=%d, ppid=%d\n", getpid(), getppid());
}else{
printf("I'am parent procss! child pid=%d, pid=%d, ppid=%d\n", pid, getpid(), getppid());
while(true){
}
}
return 0;
}
[root@localhost c_learn]# vim zombies_process.c
[root@localhost c_learn]# gcc zombies_process.c -o zombies_process
[root@localhost c_learn]# ./zombies_process
fork before
fork before
fork before
fork before
I'am parent procss! child pid=79977, pid=79976, ppid=107096
I'am child process! pid=79977, ppid=79976
^C
在执行的过程中,我们会发现主进程一直就卡在那里不动,然而子进程早已经死亡,成为了僵尸进程,我们尝试杀死僵尸进程,但是没有用,杀不死,只有当主进程结束之后,僵尸进程才会结束。
[root@localhost ~]# ps aux|grep 79977
root 79977 0.0 0.0 0 0 pts/0 Z+ 09:18 0:00 [zombies_process] <defunct>
root 80017 0.0 0.0 112824 984 pts/1 R+ 09:19 0:00 grep --color=auto 79977
[root@localhost ~]# kill -9 79977
[root@localhost ~]# ps aux|grep 79977
root 79977 0.0 0.0 0 0 pts/0 Z+ 09:18 0:00 [zombies_process] <defunct>
root 80033 0.0 0.0 112824 984 pts/1 R+ 09:19 0:00 grep --color=auto 79977
[root@localhost ~]# kill -9 79977
[root@localhost ~]# ps aux|grep 79977
root 79977 0.0 0.0 0 0 pts/0 Z+ 09:18 0:00 [zombies_process] <defunct>
root 80039 0.0 0.0 112824 984 pts/1 S+ 09:20 0:00 grep --color=auto 79977
[root@localhost ~]# ps aux|grep 79977
root 80055 0.0 0.0 112824 980 pts/1 S+ 09:20 0:00 grep --color=auto 79977
[root@localhost ~]#
这里可以看到尝试执行 kill -9 命令两次,但是僵尸进程依然存在,最后在我们执行了 Ctrl+C 之后,再次查看僵尸进程消失。
有没有一种可以在子进程结束之后,主进程自己然后主动结束?wait 函数就可以实现该功能。
man 2 wait 查看 wait 函数使用
//wait_learn.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main(int argc, char* argv[]){
printf("fork before\n");
printf("fork before\n");
printf("fork before\n");
printf("fork before\n");
pid_t pid = fork();
if (pid==-1){
printf("fork error!\n");
}else if(pid==0){
printf("I'am child process! pid=%d, ppid=%d\n", getpid(), getppid());
sleep(3);
printf("I'am child process! I'am finished!\n");
}else{
printf("I'am parent procss! child pid=%d, pid=%d, ppid=%d\n", pid, getpid(), getppid());
int status;
wait(&status);
printf("I'am parent process! I'am finished!\n");
}
return 0;
}
[root@localhost c_learn]# gcc wait_learn.c -o wait_learn
[root@localhost c_learn]# ./wait_learn
fork before
fork before
fork before
fork before
I'am parent procss! child pid=80912, pid=80911, ppid=107096
I'am child process! pid=80912, ppid=80911
I'am child process! I'am finished!
I'am parent process! I'am finished!
[root@localhost c_learn]#
在执行的过程中,可以看到在子进程结束之后,主进程才会结束。
wait 函数的功能:
1. 阻塞主进程等待子进程的结束
2. 回收子进程残留的资源
3. 获取子进程退出的状态(通过参数 status 来获取)
六:exec 函数族
我们经常会遇到这种情况,在卸载完某个桌面应用的时候,会自己打开浏览器蹦出一个页面,让你填写一些用户反馈信息,这是怎么实现的呢?我们可以对卸载的过程进行监听,如果发生了卸载,那么就会在当前的进程中启动另外一个程序就可以了,将参数传递给其他程序即可,这样其他的程序就可以运行起来。注意,有个关键点就是在当前进程中如何启动另外一个进程,exec函数族可以实现该功能。
man 3 exec 查看 exec 函数族的使用
int execl(const char *pathname, const char *arg, ... /* (char *) NULL */);
int execlp(const char *file, const char *arg, .../* (char *) NULL */);
execl 和 execlp 都可以启动其他程序,不过 execl 一般用于启动自己写的程序,而execlp 一般用于执行系统的命令,pathname 是文件名称,file是命令名称,arg是参数
execl() 函数
//execute_process.c
#include<stdio.h>
#include<stdlib.h>
int main(int argc, char* argv[]){
if (argv[1]==NULL || argv[2]==NULL){
printf("please intput enought parameters!\n");
return -1;
}
printf("Nice to meet you! %s, %s\n", argv[1], argv[2]);
return 0;
}
//execl_learn.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
int main(int argc, char* argv[]){
pid_t pid = fork();
if (pid==-1){
printf("fork error!\n");
}else if(pid==0){
printf("I'am child process! pid=%d, ppid=%d\n", getpid(), getppid());
execl("execute_process", "execute_process", "Jack", "Rose");
}else{
printf("I'am parent procss! child pid=%d, pid=%d, ppid=%d\n", pid, getpid(), getppid());
}
return 0;
}
[root@localhost c_learn]# vim execute_process.c
[root@localhost c_learn]# vim execl_learn.c
[root@localhost c_learn]# gcc execute_process.c -o execute_process
[root@localhost c_learn]# gcc execl_learn.c -o execl_learn
[root@localhost c_learn]# ./execl_learn
I'am parent procss! child pid=82615, pid=82614, ppid=107096
[root@localhost c_learn]# I'am child process! pid=82615, ppid=1
Nice to meet you! Jack, Rose
[root@localhost c_learn]#
execlp() 函数
//execlp_learn.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
int main(int argc, char* argv[]){
pid_t pid = fork();
if (pid==-1){
printf("fork error!\n");
}else if(pid==0){
printf("I'am child process! pid=%d, ppid=%d\n", getpid(), getppid());
execlp("ls", "ls", "-l", NULL);
}else{
printf("I'am parent procss! child pid=%d, pid=%d, ppid=%d\n", pid, getpid(), getppid());
}
return 0;
}
[root@localhost c_learn]# vim execlp_learn.c
[root@localhost c_learn]# gcc execlp_learn.c -o execlp_learn
[root@localhost c_learn]# ./execlp_learn
I'am parent procss! child pid=82878, pid=82877, ppid=107096
I'am child process! pid=82878, ppid=82877
[root@localhost c_learn]# 总用量 200
-rw-r--r--. 1 root root 12 4月 19 20:36 a.txt
-rw-r--r--. 1 root root 12 4月 19 20:36 b.txt
-rwxr-xr-x. 1 root root 8496 4月 20 10:10 execl_learn
-rw-r--r--. 1 root root 465 4月 20 10:08 execl_learn.c
-rwxr-xr-x. 1 root root 8496 4月 20 10:15 execlp_learn
-rw-r--r--. 1 root root 436 4月 20 10:14 execlp_learn.c
-rwxr-xr-x. 1 root root 8496 4月 20 10:14 execlp_path
-rwxr-xr-x. 1 root root 8296 4月 20 10:10 execute_process
-rw-r--r--. 1 root root 256 4月 20 10:01 execute_process.c
-rwxr-xr-x. 1 root root 8496 4月 19 19:05 f_fget_fput
-rw-r--r--. 1 root root 802 4月 19 19:31 f_fget_fput.c
-rwxr-xr-x. 1 root root 8440 4月 20 07:57 fork_learn
-rw-r--r--. 1 root root 511 4月 20 07:53 fork_learn.c
-rwxr-xr-x. 1 root root 8448 4月 20 08:42 fork_share_learn
-rw-r--r--. 1 root root 619 4月 20 08:44 fork_share_learn.c
-rwxr-xr-x. 1 root root 8528 4月 19 22:37 opendir
-rw-r--r--. 1 root root 602 4月 19 22:37 opendir.c
-rwxr-xr-x. 1 root root 8496 4月 20 09:06 orphan_process
-rw-r--r--. 1 root root 526 4月 20 08:58 orphan_process.c
-rwxr-xr-x. 1 root root 8400 4月 19 21:07 stat_learn
-rw-r--r--. 1 root root 1033 4月 19 21:05 stat_learn.c
-rwxr-xr-x. 1 root root 8544 4月 20 09:36 wait_learn
-rw-r--r--. 1 root root 692 4月 20 09:35 wait_learn.c
drwxr-xr-x. 2 root root 4096 4月 19 22:20 xuexi
-rwxr-xr-x. 1 root root 8448 4月 20 09:18 zombies_process
-rw-r--r--. 1 root root 566 4月 20 09:18 zombies_process.c
[root@localhost c_learn]#