当前位置:网站首页>【Liunx】进程控制和父子进程
【Liunx】进程控制和父子进程
2022-07-07 03:39:00 【影中人lx】
文章目录
1.进程和程序
1.1进程和程序的概念
程序的概念:程序是一种文件,编译好的二进制文件
进程:运行中的程序。
站在程序员的角度,是运行一系列指令的过程
站在操作系统的角度:分配系统资源的基本单位
区别:程序占用的是磁盘资源,不占用系统的资源
内存占用系统资源
一个程序可以对应多个进程,而一个进程对应一个程序。
因此程序没有生命周期,但是进程有生命周期。
1.2单道和多道程序设计
单道程序设计:一次只有一个处于程序在运行中;其他程序处于等待状态。(DOS)
多道程序设计:设计出时间片,在一个时间片中执行一个程序,在下一个时间片立马然后切换为下一个程序,从而让出cpu的资源给其他程序。
由于一个时间片的时间很短,属于是毫秒级别的。所以在人的感知上,几个程序是并发进行的;但是在微观上,在一个时间片上,只有一个程序在运行。微观上串行,宏观上并行
1.3进程状态的转换
cpu和MMU(内存管理单元)
- 寄存器的运算速度最快
- 算术逻辑单元只能继续+计算和<<运算
1.3.1进程的状态切换
1.3.2MMU(内存管理单元的作用)
**在32位机器下,一个进程可以管理的虚拟内存空间大小为4G。**而实际上的物理空间大小不是4G。而物理内存和虚拟内存自己的对应和管理就是通过MMU和地址转换记录表进行。
比如在.data中存放了a=10,那么就可以通过MMU(记录了虚拟内存和物理内存的映射关系),将他的虚拟地址转换为物理地址,从而访问内容10。
内存访问级别:
0是最高的级别,内核访问的权限
3是允许用户访问权限级别的权限
映射问题:
用户空间映射到物理空间内存是独立的(不同的进程,即使虚拟地址相同,对应的也是不同的物理空间)
内核空间映射到物理空间内存是相同的。(上图中,两个进程的kernal指向了同一块权限为0的MMU)
1.3.4PCB(进程控制块)的认识
每一个进程在内核中都有一个进程控制块PCB(如上图)来维护进程的相关信息,在linux下内核的进程控制块是struct task_struct 结构体。
task_struct的重点成员解析:
- 进程id,系统中每一个进程有唯一的id,在结构体中用pid_t类型表示,本质上是一个非负整数。(方便进程的管理)
- 进程的状态:就绪,运行,挂起,停止
- 进程切换时需要保存和恢复的一些CPU寄存器
- 描述虚拟地址空间的信息
- 描述控制终端的信息
- 当前的工作目录
- umask掩码
- 文件描述符表,包含很多指向file结构体的指针
- 和信号相关的信息
- 用户id和组id
- 会话和进程组
- 进程可以使用的资源上限
1.3.5获取环境变量
env #获取所有环境变量
#常用的环境变量:PATH,SHELL,TERM,LANG,HOME
PATH:可执行文件的搜索路径
SHELL:显示当前SHELL
echo $TERM:当前的终端类型,在图形界面终端下值通常为xterm,
echo $LANG:语言和locale,决定了字符串编码以及时间、货币等信息的显示格式
echo $HOME:当前用户的主目录路径,很多程序需要在自己的主目录下保存配置文件,使得每个用户在执行该程序时都有自己的一套配置。
getenv获取环境变量函数
//获取环境变量
char* getenv(const char* name);
/* 成功:返回环境变量的值,失败:返回NULL */
mygetenv.c
int main(){
const char*s="PATH";
char*print=getenv(s);
prinf("%s\n",print);
return 0;
}
gcc mygetenv.c
./a.out
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
setenv设置环境变量函数
int setenv(const char*name,const char*value,int overwrite);
/* 参数overwrite取值: 1.覆盖原环境变量 0.不覆盖,常用于设置新环境变量 */
/* 使用指令的方式添加环境变量 key为环境变量类型,value为环境变量取值 */
export key=value
unsetenv删除环境变量函数
int unsetenv(const char*name);
/* 成功:0 失败:-1 name不存在:0 */
2.控制进程
2.1进程控制函数fork
fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
#include<unistd.h>
pid_t fork(void);
/* pid_t是进程的ID 失败:返回-1 返回值,成功:有两次返回;父进程返回子进程的ID 子进程返回0; */
//进程获取函数
#include<sys/types.h>
#include<unistd.h>
//获得当前进程的ID
pid_t getpid(void);
//获得当前进程父进程的ID
pid_t getppid(void);
#include<stdio.h>
#include<unistd.h>
int main(){
printf("begin.....\n");
pid_t pid=fork();
printf("end.......\n");
return 0;
}
printf(“end…\n”);执行了两次,声明fork()执行之前只有一个进程,fork()执行之后出现了两个进程。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(){
printf("begin()...\n");
pid_t pid=fork();
//如果小于0,表示进程打开失败
if(pid<0){
perror("fork err");
exit(-1);
}
if(pid==0){
printf("I am child pid,pid=%d,ppid=%d",getpid(),getppid());
}
else if(pid>0){
printf("I am father pid,pid=%d,child=%d,ppid=%d",getpid(),pid,getppid());
}
printf("end......\n");
return 0;
}
/* 解释pid的值为什么在父子进程中不同。 其实就相当于链表,进程形成了链表,父进程的pid(p意味point)指向子进程的进程id, 因为子进程没有子进程,所以其pid为0。这里也解释了为什么父进程fork返回的是子进程的id,而子进程返回的是0; 可以证明的是:观察上面的父子进程id,父进程的id刚好比子进程少1 */
分析:
进程从fork()函数执行后开始变成两个进程,在linux下,子进程和父进程是并发进行的。当然我们应该让子进程先结束,然后父进程再结束,否则就会出现孤儿进程。
int main(){
printf("begin()...\n");
pid_t pid=fork();
//如果小于0,表示进程打开失败
if(pid<0){
perror("fork err");
exit(-1);
}
if(pid==0){
printf("I am child pid,pid=%d,ppid=%d",getpid(),getppid());
}
else if(pid>0){
printf("I am father pid,pid=%d,child=%d,ppid=%d",getpid(),pid,getppid());
sleep(1);
}
printf("end......\n");
return 0;
}
延缓父进程的结束时间,让子进程先结束。
2.1.2fork()函数进阶
//先看一份代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(void)
{
int i=0;
printf("i son/pa ppid pid cpid\n");
//ppid指当前进程的父进程pid
//pid指当前进程的pid,
//fpid指fork返回给当前进程的值
for(i=0;i<2;i++){
pid_t cpid=fork();
if(fpid==0)
printf("%d child %4d %4d %4d\n",i,getppid(),getpid(),cpid);
else
printf("%d parent %4d %4d %4d\n",i,getppid(),getpid(),cpid);
}
return 0;
}
执行顺序
for i=0 1
father father
son
son father
son
//问题是下面的进程一个创建了多少个进程(除去main进程)
#include <stdio.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
fork();
fork() && fork() || fork();
fork();
return 0;
}
//一共有20个进程,除去main还有19个进程。
A&&B,如果A=0,就没有必要继续执行&&B了;A非0,就需要继续执行&&B
A||B,如果A非0,就没有必要继续执行||B了,A=0,就需要继续执行||B。
2.2进程命令控制
int main(){
printf("begin()...\n");
pid_t pid=fork();
//如果小于0,表示进程打开失败
if(pid<0){
perror("fork err");
exit(-1);
}
if(pid==0){
printf("I am child pid,pid=%d,ppid=%d",getpid(),getppid());
while(1){
printf("I am child\n");
sleep(1);
}
}
else if(pid>0){
printf("I am father pid,pid=%d,child=%d,ppid=%d",getpid(),pid,getppid());
while(1){
printf("I am father\n");
sleep(1);
}
}
printf("end......\n");
return 0;
}
可以看出子进程和父进程是并发进行的;
ps aux #查看进程信息
ps ajx #查看进程和进程的血缘关系
kill -9 pid #杀死进程ID为pid的进程 -9表示强制杀死进程
kill -9 11549
2.3创建n个进程
让父进程创建多个子进程
int main(){
int n=5;
int i=0;
pid_t pid=0;
for(i;i<n;i++){
pid=fork();
if(pid==0){
printf("I am child,pid=%d,ppid=%d",getpid(),getppid());
break;//子进程跳出循环,不再创建子进程
}
else{
printf("I am father,pid=%d,ppid=%d",getpid(),getppid());
}
}
//保证进程不会死掉
while(1){
sleep(1);
}
}
2.4循环创建n个子进程控制顺序
//实现按顺序退出进程,main进程最后退出
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(){
int n=5;
int i=0;
pid_t pid=0;
for(i;i<n;i++){
pid=fork();
if(pid==0){
//不同的子进程i的值不同,main进程最后的值为n
printf("I am child%d,pid=%d,ppid=%d",i,getpid(),getppid());
break;//子进程跳出循环,不再创建子进程
}
else if(pid>0){
printf("I am father%d,pid=%d,ppid=%d",i,getpid(),getppid());
}
}
//通过i控制睡眠长度控制按顺序关掉进程
sleep(i);
if(i<n){
printf("I am child%d,will exit,pid=%d\n",i,getpid());
}else{
printf("I am father,will exit,pid=%d\n",getpid());
}
return 0;
}
3.父子进程
3.1父子进程可以共享的内容
看下面的代码
int main(int argc,char*argv){
printf("begin......");
fork();
printf("end........\n");
return 0;
}
begin…没有\n因此存放在缓冲区中。fork()之前的内容属于是共享区,因此end…\n的换行刷新缓冲区,所以打印出两行begin…end…
进程共享
父子进程之间在fork()分叉后,相同处和不同处之处。
刚fork之后:
父子相同处:全局变量,.data区域,.text区域,栈,堆,环境变量(0-3G的用户空间),用户ID,宿主目录,进程工作目录,信号处理方式…
父子不同处:1.进程ID,2.fork返回值,3.父进程id,4.进程运行时间,5.闹钟(定时器),6.未决信号集
子进程与父进程0-3G的用户空间内容相同,并复制了父进程PCB,但是pid不同。
但是子进程并不是复制父进程0-3G的用户空间,父子进程之间满足读时共享写时复制的原则,从而减少空间的花销。
int var=100;
int main()
{
pid_t pid=fork();
if(pid==0){
printf("var=%d,child,pid=%d\n",var,getpid());
var=1001;
printf("var=%d,child,pid=%d\n",var,getpid());
sleep(2);
printf("var=%d,child,pid=%d\n",var,getpid());
}else{
sleep(1);
printf("var=%d,parent,pid=%d\n",var,getpid());
var=1002;
sleep(5)
printf("var=%d,parent,pid=%d\n",var,getpid());
}
return 0;
}
可以看出,父子进程分别修改全局变量的值互不影响,说明满足读时共享写时复制。
3.2exec进程执行族函数
#include <unistd.h>
extern char **environ;
//程序中间嵌入执行其他程序
int execl(const char *path, const char *arg, ...);
//执行程序的时候,使用PATH环境变量,执行的程序可以不用加路径
int execlp(const char *file, const char *arg, ...);
/* 返回值:只有在发生err时才会返回值 参数解释: path/file:文件名或者路径 arg:可变参数列表 参数列表最后需要NULL结尾,作为哨兵位 执行原理:将当前进程的.text,.data替换为所要加载的程序的.text,.data,然后让进程从新的.text.的第一条指令开始执行。 */
实例
int main(){
execlp("ls","ls","-l",NULL);
execl("/bin/ls","ls","-l",NULL)
/* 参数解释: 第一个ls为文件名 第二个ls是可变参数列表的第一个参数,与前面的文件名保持一致用于占位。参数列表的最后一位要用NULL */
perror("exec err");
return 0;
}
执行结果
3.3孤儿和僵尸进程
孤儿进程:父亲进程结束的子进程,子进程的父进程为init进程
僵尸进程:子进程结束,父进程没有回收子进程的资源(进程管理块PCB);
#include<unistd.h>
//孤儿进程
int main(){
//返回子进程的ID
pid_t pid=fork();
if(pid==0){
printf("I am child,pid=%d\n",getpid());
while(1){
sleep(1);
}
}else{
printf("I am parent,pid=%d\n",getpid());
printf("parent end.....\n")
}
return 0;
}
//僵尸进程,没有回收子进程的资源
int main(){
//返回子进程的ID
pid_t pid=fork();
if(pid==0){
printf("I am child,pid=%d\n",getpid());
}
else{
while(1){
printf("I am father,pid=%d",getpid());
sleep(1);
}
}
return 0;
}
僵尸进程不能使用kill命令清除,因为kill命令只是用来终止进程,而僵尸进程已经终止,只是没有回收资源。
3.4wait回收子进程函数
#include <sys/types.h>
#include <sys/wait.h>
/* 函数作用: 1.阻塞等待进程死亡 2.回收子进程资源 3.调查子进程死亡原因 */
pid_t wait(int *status);
/* 参数说明: status是一个传出参数,可以得到进程死亡的原因 返回值: 成功:返回回收子进程的的ID 失败:返回-1 */
int main(){
pid_t pid=fork();
if(pid==0){
printf("I am child,will die,pid=%d\n",getpid());
sleep(3);
}else if(pid>0){
printf("I am parent,waiting for child die,pid=%d\n",getpid());
//阻塞等待子进程死亡
pid_t wpid=wait(NULL);
printf("pid=%d die",wpid);
}
return 0;
}
输出
I am parent,waiting for child die,pid=23746
I am child,will die,pid=23747
#父进程发生了阻塞,等待子进程死亡再进行后面的程序
pid=23747 die
3.5获得进程死亡原因
/* 进程死亡的原因: WIFEXITED(status) 如果WIFEXITED为真,即为正常死亡退出。使用WEXITSTATUS(status)得到退出状态。 WIFSIGNALED(status) 如果WIFSIGNALED为真,即为非正常死亡(被信号杀死),使用WTERMSIG(status)得到信号 */
int main(){
pid_t pid=fork();
if(pid==0){
printf("I am child,will die,pid=%d\n",getpid());
sleep(3);
return 101;
}
else if(pid>0){
printf("I am parent,waiting for child die,pid=%d\n",getpid());
//阻塞等待子进程死亡
int status;
pid_t wpid=wait(&status);
if(WIFEXITED(status)){
printf("child exit with %d\n",WIFEXITED(status));
}
if(WIFSIGNALED(status)){
printf("child killed by %d\n",WTERMSIG(status));
}
printf("pid=%d die",wpid);
}
return 0;
}
3.6waitpid回收子进程函数
pid_t waitpid(pid_t pid, int *status, int options);
/* 参数options: WNOHANG:如果子进程还活着就立即返回(不是阻塞等待) WUNTRACED:暂停状态 0和wait相同,会发生阻塞等待 参数pid: < -1 如果一个进程组ID为pid,传入-pid可以回收该进程组 -1 回收所有任意的进程 0 回收和调用进程组ID相同的组内子进程 > 0 回收ID为pid的进程 返回值: 如果设置了WNOHANG,那么如果没有子进程退出,返回0 如果有,返回子进程pid 如果失败返回-1 其他:失败返回-1 成功:返回子进程pid */
int main(){
pid_t pid=fork();
if(pid==0){
printf("I am child ,pid=%d\n",getpid());
sleep(2);
}
else if(pid>0){
printf("I am parent,pid=%d\n",getpid());
//回收任意的子进程,如果进程没有死亡,就返回0;如果有,返回进程ID
int ret=0;
while(ret==0)
{
ret=waitpid(-1,NULL,WNOHANG);
printf("ret=%d\n",ret);
}
if(ret<0){
perror("wait err");
}
printf("child die,child=%d",pid);
}
return 0;
}
3.6.1回收多个子进程
wait函数回收子进程
//用wait回收子进程
int main(){
int n=5;
int i=0;
pid_t pid;
for(;i<n;i++){
pid=fork();
//避免子进程再生成孙子进程
if(pid==0){
break;
}
else if(pid>0){
printf("I am child,pid=%d\n",getpid());
}
}
//按顺序展开进程
sleep(i);
//如果是main进程,就回收子进程
if(i==5){
//wait回收n次子进程
for(i=0;i<n;i++){
pid_t wpid=wit(NULL);
printf("child=%d,回收\n",wpid);
}
}
return 0;
}
waitpid回收子进程
int main(){
int n=5;
int i=0;
pid_t pid;
for(;i<n;i++){
pid=fork();
//避免子进程再生成孙子进程
if(pid==0){
break;
}
}
sleep(i);
//如果是main进程,就回收子进程
if(i==5){
int ret=0;
while(1){
ret=waitpid(-1,NULL,WNOHANG);
if(ret>0){
printf("child=%d回收\n",ret);
}
else if(ret==-1)
{
break;
}
}
}
if(i<n){
printf("I am child,pid=%d\n",getpid());
}
return 0;
}
边栏推荐
- Fullgc problem analysis and solution summary
- 抽絲剝繭C語言(高階)指針的進階
- Answer to the second stage of the assignment of "information security management and evaluation" of the higher vocational group of the 2018 Jiangsu Vocational College skills competition
- PostgreSQL source code (59) analysis of transaction ID allocation and overflow judgment methods
- Stack Title: nesting depth of valid parentheses
- FPGA course: application scenario of jesd204b (dry goods sharing)
- Composition API premise
- 软件验收测试
- Torefs API and toref API
- 外包干了四年,废了...
猜你喜欢
FPGA course: application scenario of jesd204b (dry goods sharing)
面试官:你都了解哪些开发模型?
Non empty verification of collection in SQL
Esxi attaching mobile (Mechanical) hard disk detailed tutorial
Implementation of AVL tree
Example of Pushlet using handle of Pushlet
1090: integer power (multi instance test)
Advanced level of C language (high level) pointer
Composition API 前提
Circulating tumor cells - here comes abnova's solution
随机推荐
考研失败,卷不进大厂,感觉没戏了
身边35岁程序员如何建立起技术护城河?
ViewModelProvider. Of obsolete solution
How DHCP router works
Non empty verification of collection in SQL
$refs: get the element object or sub component instance in the component:
面试官:你都了解哪些开发模型?
Wechat applet full stack development practice Chapter 3 Introduction and use of APIs commonly used in wechat applet development -- 3.10 tabbar component (I) how to open and use the default tabbar comp
Composition API premise
About binary cannot express decimals accurately
抽絲剝繭C語言(高階)數據的儲存+練習
Procedure in PostgreSQL supports transaction syntax (instance & Analysis)
组件的嵌套和拆分
记一个并发规则验证实现
弹性布局(二)
. Net core accesses uncommon static file types (MIME types)
Implementation of AVL tree
Multithreading and high concurrency (9) -- other synchronization components of AQS (semaphore, reentrantreadwritelock, exchanger)
MySQL service is missing from computer service
Kuboard can't send email and nail alarm problem is solved