当前位置:网站首页>【Liunx】进程控制和父子进程

【Liunx】进程控制和父子进程

2022-07-07 03:39:00 影中人lx

1.进程和程序

1.1进程和程序的概念

程序的概念:程序是一种文件,编译好的二进制文件

进程:运行中的程序。

  • 站在程序员的角度,是运行一系列指令的过程

  • 站在操作系统的角度:分配系统资源的基本单位
    区别:

  • 程序占用的是磁盘资源,不占用系统的资源

  • 内存占用系统资源

  • 一个程序可以对应多个进程,而一个进程对应一个程序。

  • 因此程序没有生命周期,但是进程有生命周期。

1.2单道和多道程序设计

单道程序设计:一次只有一个处于程序在运行中;其他程序处于等待状态。(DOS)

多道程序设计:设计出时间片,在一个时间片中执行一个程序,在下一个时间片立马然后切换为下一个程序,从而让出cpu的资源给其他程序。

由于一个时间片的时间很短,属于是毫秒级别的。所以在人的感知上,几个程序是并发进行的;但是在微观上,在一个时间片上,只有一个程序在运行。微观上串行,宏观上并行

image-20220705013224488

1.3进程状态的转换

cpu和MMU(内存管理单元)

image-20220705014033454

  • 寄存器的运算速度最快
  • 算术逻辑单元只能继续+计算和<<运算
1.3.1进程的状态切换

image-20220705015259759

1.3.2MMU(内存管理单元的作用)

**在32位机器下,一个进程可以管理的虚拟内存空间大小为4G。**而实际上的物理空间大小不是4G。而物理内存和虚拟内存自己的对应和管理就是通过MMU和地址转换记录表进行。

image-20220705021709810

比如在.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);

image-20220705144326880

#include<stdio.h>
#include<unistd.h>
int main(){
    
    printf("begin.....\n");
    pid_t pid=fork();
    printf("end.......\n");
    return 0;
}

image-20220705145934709

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;
}

image-20220705153903166

/* 解释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;
}

image-20220705154653774

延缓父进程的结束时间,让子进程先结束。

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;  
}  

image-20220705164717139

执行顺序

image-20220705171143904

 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。

image-20220705172409738

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;
}

image-20220705173140201

可以看出子进程和父进程是并发进行的;

ps aux   #查看进程信息
ps ajx   #查看进程和进程的血缘关系
kill  -9  pid    #杀死进程ID为pid的进程 -9表示强制杀死进程

image-20220705174439292

kill -9 11549

image-20220705174523925

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;
}

image-20220705212605101

3.父子进程

3.1父子进程可以共享的内容

看下面的代码

int main(int argc,char*argv){
    
    printf("begin......");
    fork();
    printf("end........\n");
    return 0;
}

image-20220705214020742

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的用户空间,父子进程之间满足读时共享写时复制的原则,从而减少空间的花销。

image-20220705221021113

 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;                           
 }

image-20220705222845023

可以看出,父子进程分别修改全局变量的值互不影响,说明满足读时共享写时复制。

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;
}

执行结果

image-20220705230102402

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;
}

image-20220706012352853

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;
}

image-20220706014234399

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;
}

image-20220706015037142

原网站

版权声明
本文为[影中人lx]所创,转载请带上原文链接,感谢
https://blog.csdn.net/qq_53893431/article/details/125631397