Linux —— 进程控制

news2024/12/22 22:47:03

1.进程控制的四个概念

进程控制分为四类,分别是:

  • 进程创建
  • 进程终止
  • 进程等待
  • 进程替换

2.进程创建

2.1初识fork

fork的作用是通过拷贝当前进程创建一个子进程,这两个进程的区别在于PID不同(还有一些资源、统计量也不同,但PID是我们能直观感受到的)。子进程创建后,操作系统不会立马更改子进程页表的映射关系,而是让子进程与父进程共享同一个拷贝。当需要进行写入的时候,页表才进行更改,从而使各个进程拥有自己的拷贝,这就是写时拷贝技术。

fork函数具有返回值,其父进程接收子进程的PID,子进程接收0。看起来有两个返回值的原因在于:执行fork函数的代码时,当要执行 return 语句结束fork函数时,子进程已经创建好了,此时父子进程都只剩下一条代码尚未执行,即 return 语句(返回值由fork函数内部确定,即规定了父进程的fork函数返回子进程的PID,子进程的fork函数返回0)。

fork也有可能创建子进程失败,其原因一般都是系统的进程太多或者是实际用户的进程数超过了限制。 

 2.2fork之后的调度问题

很多参考书给的答案是:内核有意的先让子进程被调度,因为在大多数情况下,子进程会调用exec()函数。实际上我认为fork之后的父子进程谁先调度是随机的,因为不论先调度谁,只要发生了写入操作(调用exec也是写入),都会发生写时拷贝,即使是子进程的要完成的任务很重要,但不要忘了,每个进程都有自己的时间片,节省不了多少时间。

3.进程终止

3.1进程退出的方式

执行进程就是在执行对应的代码,而用C/C++编写程序入口是 main 函数,那么进程退出时也是通过结束 main 函数来实现。一般我们习惯写 return 0 。

0 代表了进程的退出码,一般 0 代表了程序被正确执行并退出,这个退出码会被放在pcb中供父进程来读取它。还记得僵尸进程吗?程序退出后资源空间、pcb并不会马上销毁,而是停留下来让父进程读取僵尸进程的“死亡原因”,退出码 0 也是一种死亡原因。

我们甚至可以写 return 1、return 2、……return 100 等等,每一种退出码都对应了不同的描述。也就是说,进程退出的时候会有三种情况:

  1. 代码执行完毕并且结果正确 —— return 0;
  2. 代码执行完毕但结果不正确 —— return 非0;
  3. 代码执行时程序异常,退出码无意义。

那么进程的退出不止有在 main 函数内使用 return 语句,还可以在其他任意位置调用 exit 函数。下面给出进程退出的一段代码实例:

#include <stdio.h>

int Accumulation(int from,int to)
{
    int sum = 0;
    for(int i=from;i<to;i++)        //故意少加一个数
    {
        sum += i;
    }
    return sum;
}
int main()
{
    int sum = Accumulation(1,100);
    if(sum != 5050) return 1;
    return 0;
}

执行完此程序时,我们在Linux上可以通过下面这条指令查看最近一次程序执行后的退出码:

echo $?                <--最近一次程序执行完后的退出码

当然我们还可以用 exit 函数的方式:

#include <stdio.h>
#include <stdlib.h>
int Accumulation(int from,int to)
{
    int sum = 0;
    for(int i=from;i<to;i++)        //故意少加一个数
    {
        sum += i;
    }
    if(sum != 5050) exit(1);
    return sum;
}
int main()
{
    int sum = Accumulation(1,100);
    return 0;
}

3.2exit和_exit

exit是C语言提供的库函数,_eixt是Linux提供的系统调用。这两个函数的区别在于:exit终止进程时,会主动刷新缓冲区;_exit终止进程时,不会刷新缓冲区。下面给出代码实例:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    printf("hello world!");
    exit(0);
}

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
    printf("hello world!");
    //exit(0);
    _exit(0);
}

 其原因在于:exit是C语言提供的函数,是属于用户层的;_exit是内核提供的系统调用,是属于系统层的;而缓冲区也在用户层,所有的指令都会向内核发送;所以exit能够刷新缓冲区,而_exit做不到。

 4.进程等待

4.1进程等待的意义

进程退出时会产生僵尸进程问题,如果父进程没有提供处理僵尸进程的方法,那么遗留的问题可能会造成严重的后果。所以可以通过进程等待的方式来解决僵尸进程的问题。

进程等待并不只是字面上的意思,它的意义在于读取子进程的退出信息以及回收pcb资源(进程所占的资源空间通过显示或隐士的调用exit函数完成)。

4.2进程等待的方法

可以使用 wait 方法或 waitpid 方法。wait 方法提供的参数很少,通常用于阻塞等待;waitpid 可以通过控制参数来达到非阻塞等待的目的。

下面给出 wait 方法与 waitpid 方法的函数声明(通过[man]指令查询):

 下面给出这两种方法的实际使用用例:

#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        int cnt = 0;
        while(cnt < 5)
        {
            printf("我是子进程,我正在执行一些程序...PID:%d\n",getpid());
            ++cnt;
            sleep(1);
        }
        exit(1);
    }
    else if(id > 0)
    {
        pid_t ret = wait(NULL);
        if(ret > 0)
        {
            printf("等待子进程成功,PID:%d\n",ret);
        }
    }
    return 0;
}

#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        int cnt = 0;
        while(cnt < 5)
        {
            printf("我是子进程,我正在执行一些程序...PID:%d\n",getpid());
            ++cnt;
            sleep(1);
        }
        exit(1);
    }
    else if(id > 0)
    {
        int status = 0;
        pid_t ret = waitpid(id,&status,0);      //0默认为阻塞等待
        //varpid_t ret = wait(NULL);
        if(ret > 0)
        {
            printf("等待子进程成功,PID:%d,退出码:%d,信号:%d\n",ret,(status>>8)&0xff,status & 0x7f);
        }
    }
    return 0;
}

 4.3waitpid的第二个参数

可以看到上面的示例代码,整型变量 status 可以存储子进程的退出码和信号(信号为0表正常退出)。但是 status 的信心显示的并不那么直观,其原因在于:status 有自己的位图结构,次低8位表退出码;低8位表信号。也就是说,检测子进程的信息本质是通过 status 将退出信息拿到手。

 4.4阻塞等待与非阻塞等待

wait方法默认为阻塞等待,waitpid的第三个参数为0时也是阻塞等待。阻塞等待是将调用wait方法的进程挂起,直到子进程结束;非阻塞等待则是在某一时刻等待子进程,如果没有子进程的退出信息就取消挂起。

也就是说,调用阻塞等待的进程只能等待子进程退出后才能继续往下执行代码;非阻塞等待可以在任意时刻等待子进程退出,如果子进程没有退出,调用非阻塞等待的进程也会取消等待挂起,此时可以继续往下执行代码。

下面给出非阻塞等待的代码实例:

#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        int cnt = 0;
        while(cnt < 5)
        {
            printf("我是子进程,我正在执行一些程序...PID:%d\n",getpid());
            ++cnt;
            sleep(1);
        }
        exit(1);
    }
    else if(id > 0)
    {
        int status = 0;
        while(1)
        { 
            pid_t ret = waitpid(id,&status,WNOHANG);      //0默认为阻塞等待
            //varpid_t ret = wait(NULL);
            if(ret > 0)
            {
                printf("等待子进程成功,PID:%d,退出码:%d,信号:%d\n",ret,(status>>8)&0xff,status & 0x7f);
                break;
            }
            else if(ret == 0)
            {
                printf("非阻塞等待,父进程可以执行其他程序...\n");
            }
            sleep(1);
        }
    }
    return 0;
}

 5.进程替换

5.1进程程序替换

当我们fork创建出一个子进程时,想要执行磁盘上的程序时就可以使用进程替换。子进程创建时,与父进程共享同一份拷贝,执行进程程序替换时,会将磁盘上的程序的代码和数据覆盖调用程序替换的进程的代码和数据。进程程序替换也是一种写入操作。下面给出进程程序替换的代码实例:

#include <stdio.h>
#include <unistd.h>
int main()
{
    printf("程序正在执行...\n");
    execl("/usr/bin/ls","ls","-a","-l",NULL);
    printf("你猜我会不会被执行?\n");
    return 0;
}

5.2exec函数族

Linux内核提供的进程替换有多个常用接口,下面简单的介绍一下:

  • execl:使用列表的传参方式
  • execlp:不需要程序的路径,自动从PATH中获取
  • execv:可以将指令参数放入数组统一传参
  • execvp:不需要程序路径,自动从PATH中获取;可以将指令参数放入数组统一传参

下面展示通过[man]指令查询到的函数原型:

通过exec函数族将磁盘的程序的代码和数据覆盖掉子进程的代码和数据,这个过程没有新的进程产生,新的代码和数据使用的环境依然还是子进程的环境。

5.3模拟实现简易的shell

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


char command[1024]={0};
char* myargv[64]={0};
int main()
{
    while(1)
    {
        printf("[用户名@主机名 当前路径] #");
        fflush(stdout);         //刷新缓冲区
        fgets(command,sizeof(command)-1,stdin);     //fgets会自动添加'\0',所以-1
        command[strlen(command)-1]=0;       //覆盖最后一个'\0'
        
        myargv[0]=strtok(command," ");      //字符串切割
        int i=1;
        while(myargv[i++]=strtok(NULL," "));
        
        if(myargv[0] != NULL && strcmp(myargv[0],"cd") == 0)        //内建命令
        {
            if(myargv[1] != NULL) chdir(myargv[1]);
            continue;
        }
        
        pid_t id = fork();
        if(id == 0)
        {
            execvp(myargv[0],myargv);       //执行哪个指令,如何执行
            exit(1);        //如果替换失败
        }
        else if(id > 0)
        {
            int status = 0;
            pid_t ret = waitpid(id,&status,0);
            if(ret > 0)
            {
                if(((status >> 8)& 0xff) != 0)
                {
                    printf("进程替换失败!\n");
                }
            }
        }
        
    }
}

 5.4内建命令

如上面的代码所示,我们想要[cd]命令移动路径,本质是更改父进程的工作路径,这个工作不需要子进程来完成,由shell自己完成。这种行为叫内建命令。

使用[cd]更改父进程的工作目录后(通过chdir接口实现),再创建子进程,会继承父进程的工作目录,所以使用[pwd]指令是可以看到当前路径已经发生改变的([pwd]指令的实现是通过调用getcwd接口实现的,与环境变量无关)。

进程的工作目录可以使用[ls]指令与进程的PID配合查找:

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

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

相关文章

E. Split Into Two Sets(染色法判断二分图)

Problem - 1702E - Codeforces 波利卡普最近得到了一组n&#xff08;数字n-偶数&#xff09;的骨牌。每块多米诺骨牌包含1到n的两个整数。 他能把所有的骨牌分成两组&#xff0c;使每组骨牌上的数字都不一样吗&#xff1f;每张多米诺骨牌必须正好进入两组中的一组。 例如&…

7种主流数据分析软件比较及经典教材推荐

前言 STATA 软件优点&#xff1a;Stata以其简单易懂和功能强大受到初学者和高级用户的普遍欢迎。使用时可以每次只输入一个命令&#xff0c;也可以通过一个Stata程序一次输入多个命令。这样的话即使发生错误&#xff0c;也较容易找出并加以修改。尽管Stata的数据管理能力没有…

智慧点餐系统源码 扫码点餐小程序源码

&#x1f353;&#x1f353;文末获取联系&#x1f353;&#x1f353; JAVAUniappMySQLWinForm 系统功能介绍 1、单/多门店自由切换&#xff1b; 2、扫码&#xff08;桌号&#xff09;点餐&#xff1b; 3、多规格商品&#xff1b; 4、手动/自动接单&#xff1b; 5、自助&am…

MyBatis-Plus标准数据层开发

1. 标准CRUD使用 对于标准的CRUD功能都有哪些以及MP都提供了哪些方法可以使用呢&#xff1f; 我们先来看张表&#xff1a; 功能自定义接口MP接口新增boolean save(T t)int insert(T t)删除boolean delete(int id)int deleteById(Serializeble id)修改boolean update(T t)int…

系统分析与设计 复习

文章目录系统分析与设计 复习第 1 章 系统分析与设计概述系统特性DevOps第 2 章 系统规划**系统规划步骤**规划模型诺兰模型**CMM 模型**系统规划方法战略集合转换法 SST关键成功因素法 CSF企业资源规划法 BSPCSB 三者联系和区别第 3 章系统分析系统分析概述业务流程图系统流程…

【微电网优化】萤火虫算法求解微电网优化问题【含Matlab源码 2146期】

⛄一、萤火虫算法求解微电网经济优化问题简介 利用迭代搜索法、剔除劣势策略法、逆推归纳法和最大最小优化方法[7,8,9]等均可实现博弈均衡点的求解。但当维数较大时, 这些方法可能存在搜索速度、路径和精度上的问题。萤火虫优化算法[10]由于其原理简单、参数少、易于实现、具有…

Maven中依赖无法导入的终极解决方案

maven依赖无法引入的问题解决 修改maven配置 添加阿里云的设置 阿里云云效maven官方配置指南 创建自己的maven库 jdk的导入设置 阿里云仓库官网 仓库服务 (aliyun.com) jar包下载所在位置 在命令终端进行jar包的引入 mvn install:install-file -Dfilejar包所在路径…

【SQLite】二、SQLite 和 HeidiSQL 的安装

作者主页&#xff1a;Designer 小郑 作者简介&#xff1a;浙江某公司软件工程师&#xff0c;负责开发管理公司OA、CRM业务系统&#xff0c;全栈领域优质创作者&#xff0c;CSDN学院、蓝桥云课认证讲师&#xff0c;开发过20余个前后端分离实战项目&#xff0c;主要发展方向为Vue…

生物素标记试剂:(1458576-00-5,1802908-00-4)Biotin-PEG4-alkyne,Dde-生物素-四聚乙二醇-炔

一、Biotin-PEG4-alkyne 【中文名称】生物素-四聚乙二醇-炔&#xff0c;生物素-四聚乙二醇-丙炔基 【英文名称】 Biotin-PEG4-alkyne 【CAS】1458576-00-5 【分子式】C21H35N3O6S 【分子量】457.58 【纯度】95% 【外观】 淡黄色或白色固体 &#xff08;具体由其分子量大小决定…

web前端网页制作课作业:用DIV+CSS技术设计的静态网站【四大名著】中国传统文化主题题材设计

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

深入ftrace uprobe原理和功能介绍

上一章我们学习了&#xff0c;kprobe 可以实现动态内核的注入&#xff0c;基于中断的方法在任意指令中插入追踪代码&#xff0c;并且通过 pre_handler/post_handler去接收回调。另一个 kprobe 的同族是 kretprobe&#xff0c;只不过是针对函数级别的内核监控&#xff0c;根据用…

Arduino开发实例-RCWL0516微波雷达传感器驱动

RCWL0516微波雷达传感器驱动 接近感应是入侵警报、电灯开关以及其他家庭和工业自动化应用的常见应用。 电子工业中使用了多种接近检测方法。 最常见的方法是使用 PIR 传感器,它可以感应由温暖的身体引起的环境红外辐射的变化。 其他常见的方法包括使用反射的超声波或光束,其…

关于 SAP HANA 数据库的死锁问题(deadlock)

一个朋友在我的知识星球里提问&#xff1a; hana数据库发生死锁后&#xff0c;会自动解开吗&#xff1f;还是会等着自动超时后报错。 笔者在 15 年的 SAP 开发生涯中对 HANA 数据库接触得比较少&#xff0c;这里只能根据网络上搜索出的一些材料来回答。 首先&#xff0c;如果是…

YoC的使用

1 YoC的使用 参考地址 https://mp.csdn.net/mp_blog/analysis/article/all CB2201是基于CH2201的物联网应用开发板&#xff0c;开发板提供丰富的接口&#xff0c;满足应用的需求。基于该开发板&#xff0c;YoC 提供多种应用场景的开发示例&#xff0c;通过示例可以快速应用于…

卷出头了,终于学完阿里架构师推荐 413 页微服务分布式架构基础与实战笔记

时间飞逝&#xff0c;转眼间毕业七年多&#xff0c;从事 Java 开发也六年了。我在想&#xff0c;也是时候将自己的 Java 整理成一套体系。 这一次的知识体系面试题涉及到 Java 知识部分、性能优化、微服务、并发编程、开源框架、分布式等多个方面的知识点。 写这一套 Java 面试…

可视化大屏--响应式适配解决方案flexible.js

响应式适配解决方案flexible.js 最近公司开了第二个项目&#xff0c;是一个可视化大屏。 那么&#xff0c;在可视化大屏的基础上&#xff0c;我们肯定是要适配所有的屏幕设备&#xff0c;不能出现一换电脑&#xff0c;样式就紊乱的情况。 so,我们也不需要自己写媒体查询了&…

第40讲:MySQL索引的语法以及基本使用

文章目录1.索引的使用语法2.索引的基本使用2.1.准备一张数据表2.2.按照如下需求为表中的字段创建索引2.3.查看创建的索引2.4.删除索引3.验证使用索引前后的执行效率1.索引的使用语法 1&#xff09;创建索引 创建索引时&#xff0c;如果不指定索引的类型&#xff0c;默认就是常…

极简示例揭示 SwiftUI 中 @ObservedObject 与 @StateObject 状态的关键区别

问题现象 话说在 SwiftUI 中视图是状态的函数,这话一点都不假。正是秉性各异的各种状态构成了 SwiftUI 视图千变万化的功能。 这里,我们将为大家揭开其中两个常用状态,即 @ObservedObject 与 @StateObject 状态之间的最关键不同,并带领大家绕过实际使用中可能出现的坑: …

秋染田野稻菽飘香 国稻种芯·中国水稻节:河北各地农业丰收

秋染田野稻菽飘香 国稻种芯中国水稻节&#xff1a;河北各地农业丰收 河北日报 &#xff08;记者郝东伟&#xff09; 新闻中国采编网 中国新闻采编网 谋定研究中国智库网 中国农民丰收节国际贸易促进会 国稻种芯中国水稻节 中国三农智库网-功能性农业农业大健康大会报道&#x…

JavaIO流:模型

IO 的字面意思是读/写数据&#xff0c;IO 模型是读/写数据的方式。常用到的读/写数据方式有&#xff1a;同步阻塞 IO、同步非阻塞 IO、IO 多路复用、信号驱动、异步 IO &#xff5e; 本篇内容包括&#xff1a;Java IO 与 IO 模型、五种 IO 模型、三种 Java IO 模型。 文章目录一…