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

news2025/1/31 2:59:39

目录

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/39823.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【网络篇】第十八篇——IP协议相关技术

目录 DNS DNS背景 域名的层级关系 域名解析过程 使用dig工具分析DNS过程 ARP DHCP NAT NAT IP转换过程 NAPT NAT技术的缺陷 如何解决NAT潜在问题 ICMP ICMP功能 ICMP协议格式 ping命令 traceroute命令 IGMP 跟IP 协议相关的技术也不少&#xff0c;接下来说说与IP 协议相关的重…

Docker——Prometheus监控服务治理

摘要 Prometheus是继Kubernetes之后&#xff0c;第二个从云原生计算基金会&#xff08;CNCF&#xff09;毕业的项目。Prometheus是Google监控系统BorgMon类似实现的开源版&#xff0c;整套系统由监控服务、告警服务、时序数据库等几个部分&#xff0c;及周边生态的各种指标收集…

uniapp vuex正确的打开方式

uniapp vuex正确的打开方式一、vuex与全局变量globalData的区别二、uniapp vuex使用目录结构如下1. 根目录创建vuex目录&#xff0c;创建index.js文件2. 模块化代码3. 在 main.js 中导入store文件4. 调用一、vuex与全局变量globalData的区别 uni-app像小程序一样有globalData&…

项目开发——【流程图】软件工程程序流程图详解《如何正确绘制项目开发流程图》

程序流程图详解 介绍&#xff1a;通过图形符号形象的表示解决问题的步骤和程序。好的流程图&#xff0c;不仅能对我们的程序设计起到作用&#xff1b;在帮助理解时&#xff0c;往往能起到"一张图胜过千言万语"的效果。 一、程序流程图基本控制结构 顺序型&#xf…

如何实现RTS游戏中鼠标在屏幕边缘时移动视角

文章目录&#x1f9e8; Preface&#x1f38f; 判断鼠标是否处于屏幕边缘⚽ 获取鼠标处于屏幕边缘时的移动方向&#x1f3a8; 控制相机在x、z轴形成的平面上移动&#x1f3d3; 完整示例代码&#x1f9e8; Preface 本文简单介绍如何在Unity中实现即时战略游戏中鼠标在屏幕边缘的…

创新赋能合作伙伴,亚马逊云科技re:Invent科技盛宴

北京时间11月29号&#xff0c;亚马逊云科技年度峰会re:Invent 2022将在拉斯维加斯开幕。这场年度最重磅的云计算技术大会不仅是科技盛宴&#xff0c;也是亚马逊云科技与诸多客户交流互鉴的绝佳平台&#xff0c;今天带大家认识一下几位资深云计算用户&#xff0c;以及他们和re:I…

MyBatis ---- 搭建MyBatis

MyBatis ---- 搭建MyBatis1. 开发环境2. 创建maven工程a>打包方式&#xff1a;jarb>引入依赖3. 创建MyBatis的核心配置文件4. 创建mapper接口5. 创建MyBatis的映射文件6. 通过junit测试功能7. 加入log4j日志功能a>加入依赖b>加入log4j的配置文件1. 开发环境 IDE&a…

Linus 文件处理(四)

目录 一、前言 二、高级主题: fcntl和mmap 1、fcntl 2、mmap 3、Using mmap 一、前言 本文将简单介绍Linux文件和目录&#xff0c;以及如何操作它们&#xff08;如何创建文件、打开、读、写和关闭&#xff0c;程序如何操作目录&#xff0c;如创建、扫描和删除目录等&…

池风水利用工具

引用 这篇文章的目的是介绍一种基于内核态内存的越界写入通用利用技术和相关工具复现. 文章目录引用简介分析调试分析漏洞利用工具使用方法工具使用效果相关引用参与贡献简介 笔者的在原作者池风水利用工具(以下简称工具)基础上进行二次开发,新增了全自动获取内核调试模块符号…

QT-QTableWidget中的cell和item的区别

文章目录QTableWidget中单击一个单元格响应不同的函数&#xff1a;cell和item的区别&#xff1a;单击单元格响应自定义函数我的错误思路&#xff1a;已剪辑自: https://blog.csdn.net/CCLasdfg/article/details/114691478 QTableWidget中单击一个单元格响应不同的函数&#xf…

【服务器搭建】教程三:怎样购买域名并怎样进行域名解析 来啦

前言 购买一台服务器&#xff0c;再来个域名&#xff0c;搭建一个自己的个人博客网站&#xff0c;把一些教程、源码、想要分享的好玩的放到网站上&#xff0c;供小伙伴学习玩耍使用。我把这个过程记录下来&#xff0c;想要尝试的小伙伴&#xff0c;可以按照步骤&#xff0c;自己…

多云加速云原生数仓生态,华为与 HashData 联合打造方案

多云的兴起&#xff0c;源于用户应用对于基础设施、云服务功能、安全性等的差异化需求&#xff0c;用户希望根据需求将应用、数据因“云”制宜&#xff0c;实现业务的高度灵活性和高效性。这也直接驱动着云原生数据仓库等一批云原生应用的流行&#xff0c;以及存储等基础设施加…

为什么用公钥加密却不能用公钥解密?

一直以来我都在逃避写HTTPS。 毕竟。 HTTPS里名词太多。概念又巨繁琐。 实在是太难解释了&#xff0c;能不写我尽量不写。。。。 但为了让图解网络的知识体系尽量完整些。 今天&#xff0c;大家忍一忍。 我们就从对称加密和非对称加密聊起吧。 对称加密和非对称加密 小学…

Python学习笔记-语言基础

1.基础语法特点 1.1 注释 注释&#xff0c;是用于在代码中添加的标注性的文字&#xff0c;程序中并不会执行&#xff0c;知识用于告知程序员&#xff0c;该代码是干什么的&#xff0c;怎么用的等。 注释主要用于防止别人阅读代码是可以识别到代码的意图或者一段时间之后我们…

教程五 在Go中使用Energy创建跨平台GUI - 执行开发者工具方法

教程-示例-文档 介绍 本文介绍在Energy中如何执行开发者工具方法 开发者工具方法&#xff0c;可以设置浏览器头&#xff0c;模拟仿真设备等. 使用方式 字典对象创建 cef.NewCefDictionaryValue() 字典对象是keyvalue方式, chromium定义的字典名称和对应的值&#xff0c;通…

APOLLO UDACITY自动驾驶课程笔记——感知、预测

1、计算机视觉 无人驾驶车有四个感知世界的核心任务&#xff1a;检测——指找出物体在环境中的位置&#xff1b;分类——指明确对象是什么&#xff1b;跟踪——指随时间的推移观察移动物体&#xff1b;语义分割——将图像中的每个像素与语义类别进行匹配如道路、汽车、天空。 …

Adb找不到设备解决方法

有时候使用adb devices 却找不到设备&#xff0c;而用一些第三方的软件却可以找的到&#xff0c; 除了没有打开USB调试模式&#xff0c;或者没装驱动&#xff0c;数据线有问题&#xff0c;操作系统是精简版系统外&#xff0c; 一般是因为设备的安卓版本太高&#xff0c;导致a…

操作系统内存管理-01分段

前言 本文讲述本文博主在学习80386下 window下段式内存管理。 内存管理往往需要软硬件结合进行管理&#xff0c;CPU定制一套官方规范&#xff0c;要求操作系统按要求实现某些操作即可。本文所述的CPU 分段规范 在intel 第三卷 第三章。 分段概述 我们知道每一个程序会被赋予…

可自定义评教系统(教学质量评估系统)设计与实现(SSM)毕业论文+设计源码+mysql文件

目 录 摘要 I Abstract II 第1章 绪论 1 1.1 课题背景 1 1.2 研究意义 1 1.3 国内外研究现状分析 2 1.3.1 评教指标的过于简单化 2 1.3.2 评教指标的权重过于平均 2 1.3.3 评教结果的可信度和缺乏科学的数据分析 2 1.4 系统开发的主要目标和内容 3 1.4.1 开发目标 3 1.4.2 开发…

C++ 内存模型

1、内存分区模型 C程序在执行时&#xff0c;将内存大方向划分为4个区域 代码区&#xff1a;存放函数体的二进制代码&#xff0c;由操作系统进行管理的&#xff08;所有代码包含中文注释&#xff09;全局区&#xff1a;存放全局变量和静态变量以及常量栈区&#xff1a;由编译器自…