Linux基础内容(13)—— 进程控制

目录

1.fork函数的进程创建

1.fork返回值

2.fork返回值

3.fork调用失败

 2.写时拷贝

3.退出码的知识

4.进程退出

1.退出的情况

2.正常退出

5.进程等待

1.调用系统等待函数杀死僵尸进程

2.僵尸状态与PCB的关系

3.进程阻塞等待与非阻塞等待方式

6.进程程序替换

1.替换程序的可行性

2.exec*函数 

3.exec函数执行原理

4.应用


接上面内容

Linux基础内容(12)—— 程序地址空间https://blog.csdn.net/m0_63488627/article/details/127909641?spm=1001.2014.3001.5501


1.fork函数的进程创建

1.fork返回值

fork调用后得到两个进程:父进程和子进程;父进程的数据结构内容复制给子进程。

2.fork返回值

首先操作系统开发时就设置了函数的返回值,规定父进程返回子进程的pid,子进程返回0。

此后fork函数调用,在用户层面我们调用了fork函数,其实fork的调用,是让操作系统调用系统中创建子进程的函数运行,该函数负责拷贝父进程的数据结构内容和页表,然后将子进程也并入进程中,使得fork函数调用中就将子进程创建。最后返回pid也就实现了父子进程的分离。此时父子进程正式分别运行。

意味着子进程的创立,返回两个pid的值也就是说fork后,子进程复制了父进程的数据结构内容,随后进行了写时拷贝。二者共享父进程的代码,但是由于fork的反回值的不同,可能会进行执行部分代码的实现。

3.fork调用失败

进程太多。已经超过用户进程管理的限制。


 2.写时拷贝

由于fork生成的子进程其实基底是复制拷贝父进程的数据结构内容,所以只要他们中的内容没有被修改,他们虚拟地址指向物理地址的单元是一样的。但是修改后,谁修改谁发生写时拷贝,也就是说修改的进程的页表映射的物理地址不再指向同一个单元,而是重新开辟一个空间,使得两个进程不互相影响。


3.退出码的知识

return 0:在操作系统看来,其实返回的值是退出码;用于标定进程执行结果是否正确。

$?:返回最近一次执行的进程返回的退出码。

所以,如果我们不关心main的返回值,我们直接返回0;如果关系进程的退出码,要返回特定的数据表明特定的错误。退出码意义:0成功;非0失败,不同的数表达不同的错误,需要被解释。


4.进程退出

1.退出的情况

进程结束无非三种情况:

1.运行结束,结果成功

2.运行结束,结果失败

3.运行异常,终止

2.正常退出

1.从main函数return返回退出

2.任意地方调用exit(code)

3._exit退出

exit与_exit的退出进程的操作基本一致。但是他们还是有区别的:

1.exit是C库中的函数调用;_exit是操作系统提供的调用。exit的底层实现其实是_exit;

2.exit终止进程,主动刷新缓冲区;_exit终止进程,不会刷新缓冲区

缓冲区在用户级的缓冲区。

5.进程等待

1.调用系统等待函数杀死僵尸进程

进程在运行完后,还不能直接结束。它进程是需要等待操作系统帮他杀掉的,因为操作系统要清楚进程的状态和执行任务的完成情况。所以进程等待的目的也是为了:1.回收子进程资源,以达到杀死进程的效果2.获取子进程的退出信息,得到任务的完成状态

wait()函数调用:

调用该函数,会让执行该进程的处在僵尸状态的子进程结束进程

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
    pid_t id = fork();
    if(id < 0)
    {              
        printf("fork error\n");  
        return 1;   
    }                  
    else if(id == 0)  
    {  
        int cnt = 10;  
        while(cnt)  
        {               
            printf("我是子进程,pid:%d,ppid:%d,cnt: %d\n",getpid(),getppid(),cnt--);  
            sleep(1);  
        }                        
        exit(0);   
    }  
    else              
    {  
        sleep(15);     
        pid_t ret = wait(NULL);  
        if(ret>0)
        {
            printf("wait suxxess:%d\n",ret);
        }
    }
    sleep(5);
    return 0;
}    

waitpid()函数调用:

输入进程pid,进程接受的退出码,状态

调用该函数,会让执行该进程的处在僵尸状态的子进程结束进程,同时status返回退出码。

此外status有自己的位图结构。status只看前面十六位确定状态。

 异常的信号可通过kill -l可查询 

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
    pid_t id = fork();
    if(id < 0)
    {              
        printf("fork error\n");  
        return 1;   
    }                  
    else if(id == 0)  
    {  
        int cnt = 5;  
        while(cnt)  
        {               
            printf("我是子进程,pid:%d,ppid:%d,cnt: %d\n",getpid(),getppid(),cnt--);  
            sleep(1);  
        }                        
        exit(10);   
    }  
    else              
    {   
        int status   
        pid_t ret = waitpid(id,&status,0);  
        if(ret>0)
        {
            printf("wait suxxess:%d,sig number: %d,child exit code: %d\n",ret,(status & 0x7F),(status>>8)&0xFF);
        }
    }
    sleep(5);
    return 0;
}    

 如果运行异常,sig number输出异常信号,child exit code输出0(0是没有意义的意思)

2.僵尸状态与PCB的关系

僵尸状态下,进程几乎没有内存占着,它执行结束已经可以把创造的变量释放。但是唯独它的状态(退出信号)和任务完成与否信息(退出码)需要保存,而这些保存在PCB中,也就是说PCB在僵尸状态下不能被释放。而进程等待后时要把僵尸进程的PCB里的退出码和退出信号传给父进程中。

WIFEXITED(status):子进程正常终止,则返回真

WEXITSTATUS(status):若子进程正常终止,返回退出码

3.进程阻塞等待与非阻塞等待方式

阻塞等待:父进程等待子进程变为僵尸进程后,才将子进程PCB中的退出码等拿到,并且进行子进程的退出,在进行父进程的执行

非阻塞等待:确认子进程的状态,父进程不会等待子进程,如果子进程僵尸就释放子进程,没有,父进程依旧退出执行自己接下来的代码。

轮询检测:不断的进行非阻塞等待,确认子进程状态。

waitpid的第三个参数为WNOHANG便是非阻塞。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
    pid_t id = fork();
    if(id < 0)
    {              
        printf("fork error\n");  
        return 1;   
    }                  
    else if(id == 0)  
    {  
        int cnt = 5;  
        while(cnt)  
        {               
            printf("我是子进程,pid:%d,ppid:%d,cnt: %d\n",getpid(),getppid(),cnt--);  
            sleep(1);  
        }                        
        exit(10);   
    } 
    int status = 0;
    while(1)
    {
        pid_t ret = waitpid(id,&status,WNOHANG);
        if(ret==0)
        {
            //函数调用成功,子进程没有退出
            printf("wait done,but child is running");
        }
        else if(ret>0)
        {
            //函数调用成功,子进程退出                                                                                 
            printf("wait success\n");
            printf("wait suxxess:%d,sig number: %d,child exit code: %d\n",ret,(status & 0x7F),(status>>8)&0xFF);
            break;
        }
        else
        {
            //函数调用失败
            printf("waitpid code file\n");
            break;
        }
    }
    sleep(5);
    return 0;
}

使用非阻塞等待的好处:非阻塞等待时,父进程不会被占用所有的时间用于等待子进程,它可以处理其他的任务。

6.进程程序替换

1.替换程序的可行性

以上我们讨论的子进程都是用来处理父进程复制的代码。也就是说,该种子进程的功能是为了让子进程执行父进程的一部分。那么我们也可以用子进程执行另外一个程序。 

2.exec*函数 

execl:找指定的程序路径,将程序加载到内存中,并且执行

path:找到对应的进程路径;

arg:执行什么操作;

... :可变参数列表,不写要以NULL结尾;

通过调用该函数,进程可以运行另外的程序;如果出错,返回-1。

execlp:找指定的程序名加载到内存中,并且执行

file:在环境变量里找需要执行的程序名,系统会自动找;

execlv:将程序的所有的执行参数一起执行

arg[]:指令不需要分开写;

execle:传入自定义环境变量

envp[]:里面是自定义的环境变量,用于替换的程序之中;

如果输出错误,直接执行原本程序的下面代码

如果是操作系统的指令,只需符合上面的操作即可;

如果是自己写的可执行文件,就不能用execlp了因为没在环境变量里,使用execl用路径查找;什么语言都可以调用。

演示: 

  

3.exec函数执行原理

调用时,其实内存中就是对进程的数据和代码进行管理。那么execl函数便是使磁盘中对应的文件的数据和代码覆盖到内存中,必要时改变一下页表。以达到程序的替换。进程替换没有创建新的进程,只是把当前的进程内容取而代之,进行执行。

如果是父子进程的形式,子进程执行其他程序,那么子进程不是复制一份数据结构内容这么简单,它会使得整个映射失效,重新建立属于程序的映射。

4.模拟shell应用

#include<stdio.h>
#include<unistd.h>
#include<assert.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<string.h>

#define NUM 1024
#define OPT_NUM 64

char lineCommand[NUM];
char* myargv[OPT_NUM];
int main()
{
    while (1) {
        //打印输出提示符
        printf("用户名@主机名 当前路径# ");
        fflush(stdout);
        //获取用户输入,
        char* s = fgets(lineCommand, sizeof(lineCommand) - 1, stdin);
        assert(s != NULL);
       (void)s;
        //清楚最后一个/n;
        lineCommand[strlen(lineCommand) - 1] = 0;
        //切割字符串
        myargv[0] = strtok(lineCommand, " ");
        int i = 1;
        while (myargv[i++] = strtok(NULL, " "));
#ifdef DEBUG
        for (int j = 0; myargv[j]; j++)
        {
            printf("myargv[%d]:%s\n", j, myargv[j]);
        }
 #endif
        pid_t id = fork();
        assert(id != -1);
        if (id == 0)
        {
            execvp(myargv[0], myargv);
            exit(1);
        }
        waitpid(id, NULL, 0);
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

灼榆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值