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

2023-11-20

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Liunx】进程控制和父子进程 的相关文章

随机推荐

  • 大数据、数据分析和数据挖掘的区别

    大数据 数据分析 数据挖掘的区别是 大数据是互联网的海量数据挖掘 而数据挖掘更多是针对内部企业行业小众化的数据挖掘 数据分析就是进行做出针对性的分析和诊断 大数据需要分析的是趋势和发展 数据挖掘主要发现的是问题和诊断 1 大数据 big d
  • 软件项目管理的平衡原则和高效原则

    1 平衡原则 在我们讨论软件项目为什么会失败时 列出了很多的原因 答案有很多 如管理问题 技术问题 人员问题等等 但是 有一个根本的问题是最容易被忽视的 也是软件系统的用户 软件开发商 销售代理商最不愿证实的 那就是 需求 资源 工期 质量
  • 计算机网络 网络层——IP数据报 详记

    IP 数据报的格式 一个 IP 数据报由首部和数据两部分组成 首部的前一部分是固定长度 共 20 字节 是所有 IP 数据报必须具有的 在首部的固定部分的后面是一些可选字段 其长度是可变的 IP数据报首部的固定部分中的各字段 版本 占4位
  • 信号量机制

    简介 信号量是一种数据结构 信号量的值与相应资源的使用情况有关 信号量的值由P V操作改变 常用信号量 整型信号量 整型信号量S的等待 唤醒机制 P V操作 wait S while S lt 0 do no op s signal S S
  • python字符串与列表

    字符串 字符串定义 输入输出 定义 切片是指对操作的对象截取其中一部分的操作 适用范围 字符串 列表 元组都支持切片操作 切片的语法 起始下标 结束 步长 字符串中的索引是从 0 开始的 最后一个元素的索引是 1 字符串的常见操作 查找 f
  • centos7搭建ftp服务器及ftp配置讲解

    ftp 即文件传输 它是INTERNET上仍然常用的最老的网络协议之一 它为系统提供了通过网络与远程服务器传输的简单方法 FTP服务器包的名称为vsftpd 一 vsftpd安装 并简单配置启动 安装 很简单 一句话 yum install
  • Socket接收数据耗时

    1 遇到问题 首先说明一下我遇到的问题 服务端传递Byte数组 长度在900w 客户端接收时会耗时10s 我的代码是这样的 2 Socket缓冲区 http t zoukankan com bigberg p 7747419 html 每个
  • 即刻掌握python格式化输出的三种方式 (o゜▽゜)o☆

    目录 1 f 转化的格式化输出方式 2 格式化输出的方法 3 format 格式化输出的方法 1 f 转化的格式化输出方式 只需要在我们要格式化输出的内容开头引号的前面加上 f 在字符串内要转义的内容用 括起来即可 模板 print f x
  • 企业微信登录-前端实现

    企业微信登录 企业微信登录 前端具体实现 下面代码中配置项的字段具体用途说明可以阅读企业微信开发者说明文档 我们通过提供的企业微信登录组件来进行站内登录 下面是我封装的登录组件以及使用方法 weChatLogin vue 封装的组件
  • hudi-hive-sync

    hudi hive sync Syncing to Hive 有两种方式 在hudi 写时同步 使用run sync tool sh 脚本进行同步 1 代码同步 改方法最终会同步元数据 但是会抛出异常 val spark SparkSess
  • spring:AOP面向切面编程+事务管理

    目录 一 Aop Aspect Oriented Programming 二 springAOP实现 1 XML实现 2 注解实现 三 spring事务管理 一 Aop Aspect Oriented Programming 将程序中的非业
  • NLP中BERT在文本二分类中的应用

    最近参加了一次kaggle竞赛Jigsaw Unintended Bias in Toxicity Classification 经过一个多月的努力探索 从5月20日左右到6月26日提交最终的两个kernel 在public dataset
  • 单目标追踪——【Transformer】MixFormer: End-to-End Tracking with Iterative Mixed Attention

    目录 文章侧重点 网络结构 MAM Mixed Attention Module MixFormer 论文 代码 文章侧重点 本文的出发点是认为现有的多阶段Siamese追踪框架 特征提取 特征融合 边界框预测 的前两步 特征提取 特征融合
  • 搬运工~看到一个很有意思的python程序

    coding utf 8 import 二炮 Class 核武 二炮 二炮任务 默认小日本 def init self self 核武状态 二炮 NB status def status self return self 核武状态 def
  • Python编程中的for循环语句学习教程

    本文来源于公众号 csdn2299 喜欢可以关注公众号 程序员学府 这篇文章主要介绍了Python编程中的for循环语句学习教程 是Python入门学习中的基础知识 需要的朋友可以参考下 Python for循环可以遍历任何序列的项目 如一
  • 12306 图形验证码闲谈

    验证码是一个非常有意思的问题 它的目的是区分输入者是人还是机器 这个问题本质上是一个图灵测试 推荐电影 模仿游戏 验证码即是一种简单高效的验证方法 由CMU的教授于2000年左右创造 后来此牛人又将零星的验证码收集起来 转化为巨大的生产力
  • 养生产品如何进行线上推广?产品线上推广的渠道有哪些?

    随着时间的推移 中国人口老龄化越来越多 并且由于生活水平不断提高 居民收入富裕 越来越多的人对于自身健康问题越来越关注 健康养身行业将会迎来蓬勃发展 养生行业内的企业公司现在可以提前进行市场布局 抓住时机发展壮大 那么 健康养生产品如何进行
  • Virtual Box安装时出现严重错误的解决方法

    之前安装了Vitual Box 卸载后尝试了网上很多方法 查了很多资料 最后成功解决 记录一下方法 如下情况 1 我们先下载Mirosoft Visual C 2019 输入如下网址 选择第一个内容 这里我们向下划 找到如下图 下载对应的版
  • 关于conda使用环境未被激活问题

    若在安装Anaconda之后 出现 Warning This Python interpreter is in a conda environment but the environment has not been activated 那
  • 【Liunx】进程控制和父子进程

    文章目录 1 进程和程序 1 1进程和程序的概念 1 2单道和多道程序设计 1 3进程状态的转换 1 3 1进程的状态切换 1 3 2MMU 内存管理单元的作用 1 3 4PCB 进程控制块 的认识 1 3 5获取环境变量 2 控制进程 2