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
    评论
进程间通信是指在不同进程之间进行数据交换和同步的一种技术。Linux提供了多种进程间通信的方式,包括管道、消息队列、共享内存和信号量等。在实验六中,我们将学习如何使用这些方式进行进程间通信。 1. 管道 管道是一种半双工的通信方式,它可以在两个进程之间传递数据。在Linux中,管道分为匿名管道和命名管道。匿名管道只能用于父子进程之间的通信,而命名管道可以用于任意两个进程之间的通信。 使用匿名管道进行进程间通信的步骤如下: -进程创建管道,并调用fork函数创建进程-进程通过管道接收数据。 -进程通过管道发送数据。 -进程接收到数据后进行处理。 使用命名管道进行进程间通信的步骤如下: - 创建命名管道。 - 打开命名管道并进行读写操作。 2. 消息队列 消息队列是一种进程间通信机制,它允许不同进程之间通过一个消息传递序列来进行通信。在Linux中,每个消息都有一个类型,接收进程可以选择接收某个特定类型的消息。 使用消息队列进行进程间通信的步骤如下: - 创建消息队列。 - 发送消息到消息队列。 - 接收消息并进行处理。 3. 共享内存 共享内存是一种进程间通信的方式,它允许不同进程之间共享同一个物理内存区域。这种方式比较高效,但需要考虑进程间的同步和互斥问题,否则会出现数据不一致的情况。 使用共享内存进行进程间通信的步骤如下: - 创建共享内存区域。 - 进程通过共享内存区域进行数据交换。 - 进程需要进行同步和互斥操作。 4. 信号量 信号量是一种进程间同步的机制,它可以用来保证不同进程之间的共享资源在同一时刻只能被一个进程访问。在Linux中,每个信号量都有一个计数器,当计数器为0时,进程需要等待;当计数器大于0时,进程可以继续执行。 使用信号量进行进程间通信的步骤如下: - 创建信号量。 - 进程对信号量进行P操作(等待)。 - 进程对信号量进行V操作(释放)。 总体来说,不同的进程间通信方式各有优缺点,应根据实际需求选择适合的方式。在实验六中,我们将通过编写代码来学习如何使用这些方式进行进程间通信。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值