[Linux]:进程(下)

img

✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:Linux学习
贝蒂的主页:Betty’s blog

1. 进程终止

1.1 进程退出的场景

进程退出只有以下三种情况:

  1. 代码运行完毕,结果正确。
  2. 代码运行完毕,结果不正确。
  3. 代码异常终止(进程崩溃)。

1.2 进程退出码

在编程中,我们通常认为main函数是代码的入口,但实际上它只是用户级别代码的入口。main函数是被其他函数间接调用的,例如在VS2013中,main函数由__tmainCRTStartup函数调用,而__tmainCRTStartup函数又是通过加载器被操作系统调用。所以,main函数是间接性被操作系统所调用。

由于main函数是这样被调用的,当main函数调用结束后,应该给操作系统返回相应的退出信息。这个退出信息以退出码的形式作为main函数的返回值返回。一般情况下,我们以0表示代码成功执行完毕,以非0表示代码执行过程中出现错误。这就是为什么我们常在main函数的最后返回0

比如我们执行以下代码:

#include<stdio.h>
int main()
{
  printf("hello betty!\n");
  return 0;
}

我们可以通过指令echo $?查看最近一次进程的退出码。

进程正常退出返回0,如果进程不是正常退出就会返回其对应的退出码,在C语言中我们可以通过strerror函数打印出对应的错误信息。

#include<stdio.h>
#include<string.h>
int main()
{
  for(int i=0;i<150;i++)
  {
    printf("[%d]->%s\n",i,strerror(i));
  }
  return 0;
}

需要注意的是: 退出码都有对应的字符串含义,帮助用户确认执行失败的原因,而这些退出码具体代表什么含义是人为规定的,不同环境下相同的退出码的字符串含义可能不同。

1.3 exit与_exit函数

首先我们来介绍一下exit函数,其可以在代码中的任何地方退出进程,并且exit函数在退出进程前会做一系列工作:

  1. 执行用户通过atexiton_exit定义的清理函数。
  2. 关闭所有打开的流,所有的缓存数据均被写入。
  3. 调用_exit函数终止进程。

比如说以下这段代码:

#include<stdio.h>
#include<stdlib.h>
int main()
{
  printf("hello betty!");
  exit(1);
  return 0;
}

如果我们使用的是_exit函数,那么进程就会直接退出,并不会做任何处理。

#include<stdio.h>
#include<unistd.h>
int main()
{
  printf("hello betty!");
  _exit(1);
  return 0;
}

最后我们来谈谈returnexit_exit之间的区别与联系。

首先只有在main函数当中的return才能起到退出进程的作用,子函数当中return不能退出进程,而exit函数和_exit函数在代码中的任何地方使用都可以起到退出进程的作用。

使用exit函数退出进程前,exit函数会执行用户定义的清理函数、冲刷缓冲,关闭流等操作,然后再终止进程,而_exit函数会直接终止进程,不会做任何收尾工作。

画板

其中执行return num等同于执行exit(num),因为调用main函数运行结束后,会将main函数的返回值当做exit的参数来调用exit函数。

2. 进程等待

为了解决僵尸进程,获取子进程的退出信息我们需要使用进程等待。其中进程等待有两个关键的函数waitwaitpid

2.1 status参数

其中这两个关于进程等待的函数都有一个共同的参数status,如果对status参数传入NULL,表示不关心子进程的退出状态信息。否则,操作系统会通过该参数,将子进程的退出信息反馈给父进程。

*status虽然是一个整型变量,但*status不能简单的当作整型来看待,因为status的不同比特位所代表的信息不同,一般我们只考虑低的16个比特位。

*status的低16比特位当中,高8位表示进程的退出状态,即退出码。进程若是被信号所杀,则低7位表示终止信号,而第8位比特位是core dump标志。

画板

一般我们可以通过相关的位运算得到进程的退出码与退出信号。

exitCode = (status >> 8) & 0xFF; //退出码 11111111
exitSignal = status & 0x7F;      //退出信号 01111111

为了降低用户的使用成本,操作系统也为我们提供了两个宏表示对应的退出码与退出信号。

  • WIFEXITED(status):用于查看进程是否是正常退出,本质是检查是否收到信号。
  • WEXITSTATUS(status):用于获取进程的退出码。

2.2 wait函数

我们首先来介绍一下wait函数的使用的方法:

  • 原型:pid_t wait(int* status);
  • 参数:输出型参数,获取子进程的退出状态,不关心可设置为NULL
  • 返回值:等待成功返回被等待进程的pid,等待失败返回-1。

比如下面这段代码,我们用父进程一直等待子进程,然后获取其退出信息。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/types.h>
int main()
{
    pid_t id = fork();//创建子进程
    if(id==0)
    {
        //chlld
        int count=10;
        while(count--)
        {
            printf("I am child:PID:%d,PPID:%d\n",getpid(),getppid());
            sleep(1);
        }
        exit(0);
    }
    //father
    int status=0;
    pid_t ret=wait(&status);
    //如果等待成功
    if(ret>0)
    {
        printf("wait child success\n");
        if(WIFEXITED(status))
        {
            //退出正常
            printf("exit code:%d\n",WEXITSTATUS(status));
        }
        else
        {
            printf("exit signal:%d\n",status&0x7f);
        }
    }
    sleep(10);
    return 0;
}

子进程正常退出,父进程成功获取退出信息,子进程就不会形成僵尸进程。

如果我们通过指令杀死进程,父进程同样能等待成功并返回对应的信号。

2.3 waitpid函数

同样我们再来介绍waitpid函数。

  • 原型:pid_t waitpid(pid_t pid, int* status, int options);
  • 返回值:等待成功返回被等待进程的pid。如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程,则返回0。如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在。
  • 参数:pid:待等待子进程的pid,若设置为-1,则等待任意子进程。status:输出型参数,获取子进程的退出状态,不关心可设置为NULLoptions:当设置为WNOHANG时,若等待的子进程没有结束,则waitpid函数直接返回0,不予以等待。若正常结束,则返回该子进程的pid

例如,创建子进程后,父进程可使用waitpid函数一直等待子进程(此时将waitpid的第三个参数设置为0),直到子进程退出后读取子进程的退出信息。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
	pid_t id = fork();
	if (id == 0){
		//child          
		int count = 10;
		while (count--){
			printf("I am child...PID:%d, PPID:%d\n", getpid(), getppid());
			sleep(1);
		}
		exit(0);
	}
	//father           
	int status = 0;
	pid_t ret = waitpid(id, &status, 0);
	if (ret >= 0){
		//wait success                    
		printf("wait child success...\n");
		if (WIFEXITED(status)){
			//exit normal                                 
			printf("exit code:%d\n", WEXITSTATUS(status));
		}
		else{
			//signal killed                              
			printf("eixt siganl %d\n", status & 0x7F);
		}
	}
	sleep(10);
	return 0;
}

并且我们还可以使用创建等待多进程的方式。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
	pid_t ids[10]={0};
	for (int i = 0; i < 10; i++){
		pid_t id = fork();
		if (id == 0){
			//child
			printf("child process created successfully...PID:%d\n", getpid());
			sleep(3);
			exit(i); //将子进程的退出码设置为该子进程PID在数组ids中的下标
		}
		//father
		ids[i] = id;
	}
	for (int i = 0; i < 10; i++){
		int status = 0;
		pid_t ret = waitpid(ids[i], &status, 0);
		if (ret >= 0){
			//wait child success
			printf("wait child success..PID:%d\n", ids[i]);
			if (WIFEXITED(status)){
				//exit normal
				printf("exit code:%d\n", WEXITSTATUS(status));
			}
			else{
				//signal killed
				printf("exit signal %d\n", status & 0x7F);
			}
		}
	}
	return 0;
}

2.4 非阻塞轮询

在传统的父子进程关系中,当子进程未退出时,父进程通常处于阻塞等待状态,在此期间父进程不能进行其他操作。

然而,我们可以采用非阻塞等待的方式。具体做法是在调用waitpid函数时,向第三个参数options传入WNOHANG。这样,如果等待的子进程没有结束,waitpid函数将直接返回 0,父进程不进行等待,可以去做自己的事情。而当等待的子进程正常结束时,waitpid函数会返回该子进程的pid,此时父进程可以读取子进程的退出信息。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
    pid_t id=fork();//创建子进程
    if(id==0)
    {
        //child
        int count=3;
        while(count--)
        {
            printf("child do something\n");
            sleep(3);
        }
        exit(0);
    }
    //father
    while(1)
    {
        int status=0;
        pid_t ret=waitpid(id,&status,WNOHANG);
        if(ret>0)
        {
            printf("wait success\n");
            printf("exit code:%d\n",WEXITSTATUS(status));
            break;
        }
        else if(ret==0)
        {
            printf("father do other things\n");
            sleep(1);
        }
        else
        {
            //wait error
            break;
        }
    }
    return 0;
}

3. 进程替换

3.1 进程替换的概念

我们前面知道,父子进程是共享代码与数据的,如果修改子进程的数据就会发生写实拷贝。而今天我们需要修改子进程的代码,则需要进行进程替换

当进程替换时,该进程的用户空间代码和数据完全被新程序替换,并从新程序的启动例程开始执行。

画板

如果父子进程共享数据与代码,当对子进程进行进程替换时就会发生写实拷贝,所以对子进程就行进程替换并不会影响父进程。

3.2 进程替换函数

进程替换可以使用一下六个函数:

这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回,如果调用出错则返回-1。

首先我们要介绍的是execl函数:

  • 原型:int execl(const char *path, const char *arg, …)
  • 参数:path是要执行程序的路径,arg是可变参数列表,表示你要如何执行这个程序, 注意以NULL为参数传递的结尾。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>

int main() 
{
    pid_t id = fork();
    if (id < 0) 
    {
        perror("fork failed");
        return 1;
    } 
    else if (id == 0) 
    {
        // child
        if (execl("/usr/bin/ls","ls","-l","-a", NULL) == -1) 
        {
            perror("execl failed");
            exit(-1);
        }
    } 
    else 
    {
        // father
        int status;
        pid_t ret = waitpid(id, &status, 0);
        if (ret < 0) {
            perror("waitpid failed");
            return 1;
        }
        if (WIFEXITED(status)) {
            printf("wait success\n");
            printf("exit code:%d\n", WEXITSTATUS(status));
        }
    }
    return 0;
}

并且我们也可能通过该接口,调用其他语言的脚本,比如我们调用一个python脚本。

execl("/usr/bin/python3","python","testp.py", NULL)

接下来我们将学习execlp接口:

  • 原型:int execlp(const char *file, const char *arg, …);
  • 参数:file是要执行程序的名称,arg是可变参数列表,表示你要如何执行这个程序, 注意以NULL为参数传递的结尾。
execlp("ls", "ls", "-a", "-l", NULL);//执行ls -a -l

继续学习execle接口:

  • 原型:int execle(const char *path, const char *arg, …, char *const envp[]);
  • 参数:path是要执行程序的路径,arg是可变参数列表,表示你要如何执行这个程序, 注意以NULL为参数传递的结尾,envp是你自己设置的环境变量。

例如,你设置了MYVAL环境变量,在mycmd程序内部就可以使用该环境变量。

char* myenvp[] = { "MYVAL=2021", NULL };
execle("./mycmd", "mycmd", NULL, myenvp);//执行./mycmd

继续学习execv接口:

  • 原型:int execv(const char *path, char *const argv[]);
  • 参数:path是要执行程序的路径,argv是一个指针数组,数组当中的内容表示你要如何执行这个程序,数组以NULL结尾。
char* myargv[] = { "ls", "-a", "-i", "-l", NULL };
execv("/usr/bin/ls", myargv);//执行ls -a -i -l

剩余接口我们就不在介绍了,因为每个接口的参数都类似,如果学习完上面接口,剩余接口的使用也是零成本。

最后为了方便记忆,我们将这些接口归类形成如下表格:

替换函数接口后后缀含义
l(list)参数采用列表方式
v(vector)参数采用数组方式
p(path)自动搜索环境变PATH,进行程序查找
e(env)自己维护环境变量,或者说自定义环境变量,可以传入自己设置的环境变量
函数名参数格式是否带路径是否使用当前环境变量
execl列表
execlp列表
execle列表否,需自己组装环境变量
execv数组
execvp数组
execve数组否,需自己组装环境变量

事实上,在系统调用中,只有execve才是真正的系统调用,其他五个函数(如execlexecleexeclpexecvexecvp)都是对execve函数的封装,目的是为了满足不同用户的需求。这也导致了在man手册中,execve位于第 2 节,而其他五个函数在第 3 节。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

flume 使用 exec 采集容器日志,转储磁盘

flume 使用 exec 采集容器日志&#xff0c;转储磁盘 在该场景下&#xff0c;docker 服务为superset&#xff0c;flume 的sources 选择 exec &#xff0c; sinks选择 file roll 。 任务配置 具体配置文件如下&#xff1a; #simple.conf: A single-node Flume configuration#…

Shader 渲染路径

实际的游戏开发中&#xff0c;场景中的光源肯定是更多、更复杂的&#xff0c;如果只有一个平行光的处理&#xff0c;完全不能满足需求。处理更多的光源&#xff0c;我们就需要了解Unity底层是如何处理这些光源的。 1、渲染路径是什么 渲染路径&#xff08;Rendering Path&…

Apache POl的使用(导出报表)

介绍 Apache POl是一个处理Miscrosoft Office各种文件格式的开源项目。简单来说就是&#xff0c;我们可以使用 PO! 在 Java 程序中对Miscrosoft Office各种文件进行读写操作。一般情况下&#xff0c;POI都是用于操作 Excel 文件。 Apache POl的应用场景: 银行网银系统导出交…

中秋之美——html5+css+js制作中秋网页

中秋之美——html5cssjs制作中秋网页 一、前言二、功能展示三、系统实现四、其它五、源码下载 一、前言 八月十五&#xff0c;秋已过半&#xff0c;是为中秋。 “但愿人长久&#xff0c;千里共婵娟”&#xff0c;中秋时节&#xff0c;气温已凉未寒&#xff0c;天高气爽&#x…

助贷行业的三大严峻挑战:贷款中介公司转型债务重组业务

大家是否察觉到一种趋势&#xff1f;现如今&#xff0c;众多贷款辅助服务机构与专注于债务再构的公司之间形成了紧密的“联动”。有的选择将获取的贷款需求转介给债务重组方&#xff0c;有的则直接下场&#xff0c;动用自身资本参与债务重组业务。这一现象背后&#xff0c;究竟…

紫光展锐完成Android 15同步升级,驱动技术创新与生态共赢

近日&#xff0c;紫光展锐宣布&#xff0c;展锐5G移动平台T820、T770、T765、T760、T750以及4G平台T620、T619、T616、T615、T612、T606&#xff0c;完成Android 15同步升级。相较于过往Android发布&#xff0c;今年同步升级Android 15主要有三大提升&#xff1a; ■ 紫光展锐实…

Oracle版本简介手册

Oracle版本简介手册 图1—数据库发布路线图表 Oracle数据库的各个版本反映了其技术的发展历程和功能增强&#xff0c;从最早的Oracle 1&#xff08;1979年&#xff09;到最新的版本&#xff0c;每个版本都带来了新的特性和改进&#xff0c;以满足不断变化的企业需求。以下是Or…

2024 第七届“巅峰极客”网络安全技能挑战赛初赛 Web方向 题解WirteUp

EncirclingGame 题目描述&#xff1a;A simple game, enjoy it and get the flag when you complete it. 开题&#xff0c;前端小游戏&#xff0c;红点出不去就行 直接玩通关了 看看如何不玩也能拿到flag&#xff0c;flag存储在后端php文件内&#xff0c;前端找不到。 看一下…

uniapp使用uni-popup做底部弹出选项(vue3)

效果图 页面代码 <!-- 发票筛选弹出框 --><uni-popup ref"popupRef" type"bottom" border-radius"10px 10px 0 0" background-color"#fff"><h4 style"text-align: center;margin-bottom: 20px;">发票筛…

内推|京东|后端开发|运维|算法...|北京 更多岗位扫内推码了解,直接投递,跟踪进度

热招岗位 更多岗位欢迎扫描末尾二维码&#xff0c;小程序直接提交简历等面试。实时帮你查询面试进程。 安全运营中心研发工程师 岗位要求 1、本科及以上学历&#xff0c;3年以上的安全相关工作经验&#xff1b; 2、熟悉c/c、go编程语言之一、熟悉linux网络编程和系统编程 3、…

AMD Zen5芯片技术架构分析

AMD Zen 5架构深入研究 在Zen 5技术日上&#xff0c;AMD隆重推出了其最新锐龙9000系列—“Granite Ridge”以及Ryzen AI 300系列—“Strix Point”。此次展示深入剖析了公司下一代芯片的卓越性能与创新力。 AMD 一直在缓慢地公布 Zen 5 处理器的细节&#xff0c;但今天我们可以…

【QT】基础入门学习

文章目录 浅析Qt应用程序的主函数使用qDebug()函数常用快捷键Qt 编码风格信号槽连接模型实现方案 信号和槽的工作机制Qt对象树机制 浅析Qt应用程序的主函数 #include "mywindow.h"#include <QApplication>// 程序的入口 int main(int argc, char *argv[]) {//…

log4j靶场,反弹shell

1.用vulhub靶场搭建&#xff0c;首先进入目录CVE-2021-44228中&#xff0c;docker启动命令 2.发现端口是8983&#xff0c;浏览器访问http://172.16.1.18:8983/ 3.用dnslog平台检测dns回显&#xff0c;看看有没有漏洞存在 4.反弹shell到kali&#xff08;ip为172.16.1.18&#xf…

负载均衡--资源申请说明(三)

1.负载方式&#xff1a;分为四层负载和七层负载 2.负载协议&#xff1a;四层负载为TCP和UDP&#xff0c;七层负载为HTTP和HTTPS 4.负载端口&#xff1a;填写虚地址的端口&#xff08;一般与后端服务端口保持一致&#xff09; 5.真实服务IP&#xff1a;指被负载的后台真实服务…

抖音素材网站有哪些?这几个高质量的抖音无水印素材网站分享

在今天的互联网时代&#xff0c;独特而高质量的视频素材已成为抖音内容创作者的必备武器。如果你正在寻找能够让你的作品一鸣惊人的抖音素材&#xff0c;那么你来对地方了。接下来&#xff0c;我将为你详细介绍几个国内外优质的抖音无水印素材网站&#xff0c;帮助你的视频更加…

【数据分享】《中国城市统计年鉴》(1985-2023)全PDF版本 第一次补档

数据介绍 中国城市&#xff0c;如同一本生动的历史书&#xff0c;承载着经济、社会的快速变迁。《中国城市统计年鉴》记录了城市的发展轨迹&#xff0c;是我们理解城市化进程、洞察城市挑战的重要指南。 这份年鉴的数据庞大而详实&#xff0c;囊括了中国城市发展的多个方面。…

如何在极狐GitLab中添加 SSH Key?

本文分享如何生成 SSH Key 并添加到极狐GitLab 中&#xff0c;然后用 SSH Key 进行代码拉取。 极狐GitLab 是 GitLab 在中国的发行版&#xff0c;可以私有化部署&#xff0c;对中文的支持非常友好&#xff0c;是专为中国程序员和企业推出的企业级一体化 DevOps 平台&#xff0…

【原创】java+swing+mysql密码管理器系统设计与实现

个人主页&#xff1a;程序员杨工 个人简介&#xff1a;从事软件开发多年&#xff0c;前后端均有涉猎&#xff0c;具有丰富的开发经验 博客内容&#xff1a;全栈开发&#xff0c;分享Java、Python、Php、小程序、前后端、数据库经验和实战 文末有本人名片&#xff0c;希望和大家…

营养方案调整执行流程 第十篇

追踪调整方案 疾病诊断≠营养诊断 出健康管理建议应考虑地域习惯等特点&#xff0c;因人而异

Vulnhub:hacksudo2

靶机下载地址 信息收集 主机发现 nmap 192.168.31.0/24 -Pn -T4 靶机ip&#xff1a;192.168.31.188 端口扫描 nmap 192.168.31.188 -A -p- -T4 开放端口有80,111,1337(ssh),2049(nfs)。 目录扫描 访问http服务。 点击图片进入游戏。玩了一下没看到什么信息。 目录扫描。…