Linux系统编程-进程创建与管理

一: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]#

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值