当前位置: 首页 > news >正文

Linux学习:进程的控制

        前面我们已经学习了进程的相关概念。本篇我们就来学习Linux系统中进程的控制

        相关代码已经上传至作者的个人gitee:楼田莉子/Linux学习 - Gitee.com喜欢请点个赞谢谢

目录

进程的创建

        fork函数的介绍

        fork函数返回值

        写时拷贝

        fork常规用法

        fork调用失败的原因

进程的终止       

        进程退出

        退出码

        Linux 进程退出码表格

        信号相关退出码 (128 + 信号编号)

        特殊用途退出码

        _exit函数

        exit函数

        _exit函数 vs exit函数 的主要区别

        return函数

进程等待

        进程等待的必要性

        进程等待的方法

        wait方法

        waitpid方法

        获取子进程status

        阻塞与非阻塞等待

        进程的阻塞等待

        进程的非等待阻塞

进程程序替换

        替换原理

        替换函数

        命名理解

        函数解释

        1. execl - 列表形式 + 完整路径

        2. execlp - 列表形式 + PATH搜索

        3. execle - 列表形式 + 环境变量

        4. execv - 数组形式 + 完整路径

        5. execvp - 数组形式 + PATH搜索

        6. execve - 数组形式 + 环境变量

        替换函数可以实现执行其他语言的程序

        替换函数与环境变量

Linux信号含义表

        实时信号说明

        关键特性总结

        补充说明


进程的创建

        fork函数的介绍

        在linux中fork函数是⾮常重要的函数,它从已存在进程中创建⼀个新进程。新进程为⼦进程,⽽原进程为⽗进程

#include <unistd.h>
pid_t fork(void);
//返回值:⾃进程中返回0,⽗进程返回⼦进程id,出错返回-1

        进程调用fork,当控制转移到内核中的fork代码后,内核做:

        • 分配新的内存块和内核数据结构给子进程

        • 将父进程部分数据结构内容拷贝至子进程

        • 添加子进程到系统进程列表当中

        • fork返回,开始调度器调度

        就像下图:

        当⼀个进程调⽤fork之后,就有两个⼆进制代码相同的进程。⽽且它们都运⾏到相同的地⽅。但每个进程都将可以开始它们⾃⼰的旅程,看如下程序。

#include <stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
int main()
{pid_t pid;printf("Before: pid is %d\n", getpid());if ( (pid=fork()) == -1 ) perror("fork()"),exit(1);printf("After:pid is %d, fork return %d\n", getpid(), pid);sleep(1);return 0;
}


        打印结果为:

        为什么进程21281打印两次之后的但是21282却只打印了一次呢?原因如下图所示

        所以,fork之前⽗进程独⽴执⾏,fork之后,⽗⼦两个执⾏流分别执⾏。注意,fork之后,谁先执⾏完全由调度器决定


        fork函数返回值

• ⼦进程返回0,

• ⽗进程返回的是⼦进程的pid。

        写时拷贝

        通常,父子代码共享,父子在不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。具体见下图:

        因为有写时拷贝技术的存在,所以父子进程得以彻底分离!完成了进程独立性的技术保证!

写时拷贝,是一种延时申请技术,可以提高整机内存的使用率

        fork常规用法

        一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。

        一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

        fork调用失败的原因

        1、系统中有太多的进程

        2、实际用户的进程数超过了限制

进程的终止       

        当我们的进程终止的时候,内核在做什么呢?

        如果我们的进程中止一般有几种情况?

        我们写C/C++代码中通常是这样的

int main()
{//codereturn 0;
}

        其中return 0代表终止。这就有以下几种情况

  1. 代码跑完,结果正确

  2. 代码跑完,结果不正确

  3. 代码都没跑完,进程异常了!

        进程退出

        之前有的代码我们可以通过打印来获取结果,但是很多时候代码的结果是不那么容易显示出来的(比如网络发送等),那么我们是如何确认代码正确与否呢?所以main函数才会返回0,代表着进程的退出。0表示成功,非0表示错误。而main函数返回的0会交给父进程(这里就是bash)。通过以下命令来打印上一个子进程的退出码数字

echo $?

        正常终止

  1. 从main返回

  2. 调用exit

  3. _exit

        异常退出

• ctrl + c,信号终止

        退出码

        退出码(退出状态)可以告诉我们最后一次执行的命令的状态。在命令结束以后,我们可以知道命令是成功完成的还是以错误结束的。其基本思想是,程序返回退出代码 0 时表示执行成功,没有问题。
        代码 1 或 0 以外的任何代码都被视为不成功。

        退出码的捕获可以用strerror函数来获取,我们可以写一段程序来检验

#include<stdio.h>
#include<string.h>
int main()
{for(size_t i=0;i<150;++i){printf("%zu号错误码为:%s\n",i,strerror(i));}return 0;
}

        结果为:(括号内容为翻译)

0号错误码为:Success(成功)1号错误码为:Operation not permitted(操作不允许)2号错误码为:No such file or directory(没有这样的文件或目录)3号错误码为:No such process(没有这样的进程)4号错误码为:Interrupted system call(被中断的系统调用)5号错误码为:Input/output error(输入/输出错误)6号错误码为:No such device or address(没有这样的设备或地址)7号错误码为:Argument list too long(参数列表过长)8号错误码为:Exec format error(执行格式错误)9号错误码为:Bad file descriptor(错误的文件描述符)10号错误码为:No child processes(没有子进程)11号错误码为:Resource temporarily unavailable(资源暂时不可用)12号错误码为:Cannot allocate memory(无法分配内存)13号错误码为:Permission denied(权限被拒绝)14号错误码为:Bad address(错误的地址)15号错误码为:Block device required(需要块设备)16号错误码为:Device or resource busy(设备或资源忙)17号错误码为:File exists(文件已存在)18号错误码为:Invalid cross-device link(无效的跨设备链接)19号错误码为:No such device(没有这样的设备)20号错误码为:Not a directory(不是一个目录)21号错误码为:Is a directory(是一个目录)22号错误码为:Invalid argument(无效参数)23号错误码为:Too many open files in system(系统中打开文件过多)24号错误码为:Too many open files(打开文件过多)25号错误码为:Inappropriate ioctl for device(对设备不适当的ioctl操作)26号错误码为:Text file busy(文本文件忙)27号错误码为:File too large(文件太大)28号错误码为:No space left on device(设备无剩余空间)29号错误码为:Illegal seek(非法寻址)30号错误码为:Read-only file system(只读文件系统)31号错误码为:Too many links(链接过多)32号错误码为:Broken pipe(管道破裂)33号错误码为:Numerical argument out of domain(数值参数超出定义域)34号错误码为:Numerical result out of range(数值结果超出范围)35号错误码为:Resource deadlock avoided(避免资源死锁)36号错误码为:File name too long(文件名过长)37号错误码为:No locks available(无可用锁)38号错误码为:Function not implemented(功能未实现)39号错误码为:Directory not empty(目录非空)40号错误码为:Too many levels of symbolic links(符号链接层数过多)41号错误码为:Unknown error 41(未知错误41)42号错误码为:No message of desired type(无所需类型的消息)43号错误码为:Identifier removed(标识符已移除)44号错误码为:Channel number out of range(通道号超出范围)45号错误码为:Level 2 not synchronized(第二层未同步)46号错误码为:Level 3 halted(第三层停止)47号错误码为:Level 3 reset(第三层重置)48号错误码为:Link number out of range(链接号超出范围)49号错误码为:Protocol driver not attached(协议驱动程序未附加)50号错误码为:No CSI structure available(无CSI结构可用)51号错误码为:Level 2 halted(第二层停止)52号错误码为:Invalid exchange(无效交换)53号错误码为:Invalid request descriptor(无效请求描述符)54号错误码为:Exchange full(交换区满)55号错误码为:No anode(无阳极)56号错误码为:Invalid request code(无效请求代码)57号错误码为:Invalid slot(无效槽)58号错误码为:Unknown error 58(未知错误58)59号错误码为:Bad font file format(错误的字体文件格式)60号错误码为:Device not a stream(设备不是流)61号错误码为:No data available(无可用数据)62号错误码为:Timer expired(计时器超时)63号错误码为:Out of streams resources(流资源耗尽)64号错误码为:Machine is not on the network(机器不在网络上)65号错误码为:Package not installed(软件包未安装)66号错误码为:Object is remote(对象是远程的)67号错误码为:Link has been severed(链接已切断)68号错误码为:Advertise error(广告错误)69号错误码为:Srmount error(Srmount错误)70号错误码为:Communication error on send(发送时通信错误)71号错误码为:Protocol error(协议错误)72号错误码为:Multihop attempted(尝试多跳)73号错误码为:RFS specific error(RFS特定错误)74号错误码为:Bad message(错误消息)75号错误码为:Value too large for defined data type(值对于定义的数据类型过大)76号错误码为:Name not unique on network(网络上的名称不唯一)77号错误码为:File descriptor in bad state(文件描述符处于错误状态)78号错误码为:Remote address changed(远程地址已更改)79号错误码为:Can not access a needed shared library(无法访问所需的共享库)80号错误码为:Accessing a corrupted shared library(访问已损坏的共享库)81号错误码为:.lib section in a.out corrupted(a.out中的.lib节损坏)82号错误码为:Attempting to link in too many shared libraries(尝试链接过多的共享库)83号错误码为:Cannot exec a shared library directly(不能直接执行共享库)84号错误码为:Invalid or incomplete multibyte or wide character(无效或不完整的多字节或宽字符)85号错误码为:Interrupted system call should be restarted(被中断的系统调用应重新启动)86号错误码为:Streams pipe error(流管道错误)87号错误码为:Too many users(用户过多)88号错误码为:Socket operation on non-socket(在非套接字上执行套接字操作)89号错误码为:Destination address required(需要目标地址)90号错误码为:Message too long(消息过长)91号错误码为:Protocol wrong type for socket(套接字协议类型错误)92号错误码为:Protocol not available(协议不可用)93号错误码为:Protocol not supported(协议不支持)94号错误码为:Socket type not supported(套接字类型不支持)95号错误码为:Operation not supported(操作不支持)96号错误码为:Protocol family not supported(协议族不支持)97号错误码为:Address family not supported by protocol(协议不支持地址族)98号错误码为:Address already in use(地址已在使用中)99号错误码为:Cannot assign requested address(无法分配请求的地址)100号错误码为:Network is down(网络关闭)101号错误码为:Network is unreachable(网络不可达)102号错误码为:Network dropped connection on reset(网络在重置时断开连接)103号错误码为:Software caused connection abort(软件导致连接中止)104号错误码为:Connection reset by peer(对端重置连接)105号错误码为:No buffer space available(无缓冲空间可用)106号错误码为:Transport endpoint is already connected(传输端点已连接)107号错误码为:Transport endpoint is not connected(传输端点未连接)108号错误码为:Cannot send after transport endpoint shutdown(传输端点关闭后无法发送)109号错误码为:Too many references: cannot splice(引用过多:无法拼接)110号错误码为:Connection timed out(连接超时)111号错误码为:Connection refused(连接被拒绝)112号错误码为:Host is down(主机关闭)113号错误码为:No route to host(无路由到主机)114号错误码为:Operation already in progress(操作已在进展中)115号错误码为:Operation now in progress(操作现在在进展中)116号错误码为:Stale file handle(过时的文件句柄)117号错误码为:Structure needs cleaning(结构需要清理)118号错误码为:Not a XENIX named type file(不是XENIX命名类型文件)119号错误码为:No XENIX semaphores available(无XENIX信号量可用)120号错误码为:Is a named type file(是命名类型文件)121号错误码为:Remote I/O error(远程I/O错误)122号错误码为:Disk quota exceeded(磁盘配额超出)123号错误码为:No medium found(未找到介质)124号错误码为:Wrong medium type(错误的介质类型)125号错误码为:Operation canceled(操作已取消)126号错误码为:Required key not available(所需密钥不可用)127号错误码为:Key has expired(密钥已过期)128号错误码为:Key has been revoked(密钥已被撤销)129号错误码为:Key was rejected by service(密钥被服务拒绝)130号错误码为:Owner died(所有者死亡)131号错误码为:State not recoverable(状态不可恢复)132号错误码为:Operation not possible due to RF-kill(由于RF-kill,操作不可能)133号错误码为:Memory page has hardware error(内存页有硬件错误)134号错误码为:Unknown error 134(未知错误134)135号错误码为:Unknown error 135(未知错误135)136号错误码为:Unknown error 136(未知错误136)137号错误码为:Unknown error 137(未知错误137)138号错误码为:Unknown error 138(未知错误138)139号错误码为:Unknown error 139(未知错误139)140号错误码为:Unknown error 140(未知错误140)141号错误码为:Unknown error 141(未知错误141)142号错误码为:Unknown error 142(未知错误142)143号错误码为:Unknown error 143(未知错误143)144号错误码为:Unknown error 144(未知错误144)145号错误码为:Unknown error 145(未知错误145)146号错误码为:Unknown error 146(未知错误146)147号错误码为:Unknown error 147(未知错误147)148号错误码为:Unknown error 148(未知错误148)149号错误码为:Unknown error 149(未知错误149)

        接下来我们举例验证:

#include <stdio.h>// 这段代码既可以在C语言环境下编译也可以在C++环境下编译
#ifdef __cplusplus#include <cerrno>#include <cstring>using std::strerror;
#else#include <errno.h>#include <string.h>
#endifint main() 
{FILE *file = fopen("nonexistent.txt", "r");if (file == NULL) {// 函数失败后检查errnoprintf("错误信息为%d号,其内容为: %s\n", errno,strerror(errno));//errno是一个全局整型变量,用于存储系统调用和库函数执行失败时的错误代码。当函数调用失败时,它会设置errno为一个特定的值来指示错误类型。//strerror() - 将错误码转换为可读字符串//perror() - 直接打印错误信息//errno是线程安全的perror("fopen failed");  // 另一种输出错误信息的方式}//errno的使用场景//系统调用失败时 - open, read, write, fork等//标准库函数失败时 - fopen, malloc, strtol等//数学函数错误时 - 如除零错误、数值溢出等//网络编程错误 - socket, connect, bind等return 0;
}

        结果为:

        同时也可以用define宏定义来自定义错误码,案例如下

#define FAILED 1

        代码的正常结束有以下几类:

  1. 代码跑完(代码运行期间,没有收到信号) 0 && return 0 -> signumber :0 && 退出码: 0

  2. signumber :0 && 退出码:0

  3. signumber :10 && 退出码无意义

        进程执行的结果状态,可以用两个数字表示: sig(信号), exit_code(退出码);退出码

用户是不需要维护的。

        当一个进程退出的时候,OS会把进程退出的详细信息写入到进程的task_struct 结构体中!所以,进程退出,需要僵尸维持自己的退出状态!

        Linux Shell 中的主要退出码如下:

        Linux 进程退出码表格

退出码含义说明典型场景
0成功 (Success)程序正常执行完成,没有错误命令成功执行
1一般错误 (Catchall for general errors)未明确分类的错误权限不足、参数错误等
2Shell 内置命令误用 (Misuse of shell builtins)Shell 内置命令使用不当如 bash 中错误使用内置命令
126命令不可执行 (Command cannot execute)命令文件存在但无法执行权限问题、非可执行文件
127命令未找到 (Command not found)在 PATH 中找不到命令命令拼写错误、程序未安装
128无效退出参数 (Invalid exit argument)exit 参数无效exit 使用了无效参数

        信号相关退出码 (128 + 信号编号)

退出码对应信号信号含义触发场景
129SIGHUP (1)挂起控制终端关闭
130SIGINT (2)键盘中断Ctrl+C 终止
131SIGQUIT (3)退出Ctrl+\ 终止
132SIGILL (4)非法指令执行了非法CPU指令
133SIGTRAP (5)跟踪陷阱调试器断点
134SIGABRT (6)异常中止abort() 函数调用
135SIGBUS (7)总线错误内存访问错误
136SIGFPE (8)浮点异常除零错误、算术溢出
137SIGKILL (9)强制杀死kill -9 无法捕获
138SIGUSR1 (10)用户定义信号1用户自定义
139SIGSEGV (11)段错误非法内存访问
140SIGUSR2 (12)用户定义信号2用户自定义
141SIGPIPE (13)管道破裂向已关闭管道写入
142SIGALRM (14)定时器信号闹钟超时
143SIGTERM (15)终止信号kill 默认信号
255超出范围退出码超出 0-255 范围程序返回了负数或大于255的值

        特殊用途退出码

退出码含义使用场景
64命令使用错误按照 BSD sysexits.h 规范
65数据格式错误按照 BSD sysexits.h 规范
66无法打开输入文件按照 BSD sysexits.h 规范
67地址未知按照 BSD sysexits.h 规范
68主机名未知按照 BSD sysexits.h 规范
69服务不可用按照 BSD sysexits.h 规范

        _exit函数

#include <unistd.h>
void _exit(int status);
参数:status 定义了进程的终⽌状态,⽗进程通过wait来获取该值

        _exit 是一个系统调用,用于立即终止当前进程,不执行任何清理操作。它直接返回到操作系统内核。

        status:进程的退出状态,通常 0 表示成功,非零表示错误

        说明:虽然status是int,但是仅有低8位可以被⽗进程所⽤。所以_exit(-1)时,在终端执⾏$?发现返回值是255。

        exit函数

#include <stdlib.h>
void exit(int status);

        exit最后也会调用_exit, 但在调用_exit之前,还做了其他工作:

  1. 执行用户通过 atexit或on_exit定义的清理函数。

  2. 关闭所有打开的流,所有的缓存数据均被写入

  3. 调用_exit "

        _exit函数 vs exit函数 的主要区别

对比方面_exit 函数exit 函数
头文件<unistd.h><stdlib.h>
函数原型void _exit(int status);void exit(int status);
标准规范POSIX 标准C/C++ 标准
清理机制立即终止,不执行任何清理执行完整的清理流程
I/O缓冲区❌ 不刷新任何缓冲区✅ 刷新所有标准I/O缓冲区
atexit函数❌ 不调用注册的退出函数✅ 调用所有注册的退出函数
文件描述符❌ 不自动关闭打开的文件✅ 自动关闭所有打开的文件
临时文件❌ 不删除临时文件✅ 删除由tmpfile()创建的临时文件
退出状态status & 0377status & 0377
多线程安全直接终止所有线程会执行线程局部存储析构
使用场景子进程退出、严重错误恢复正常程序退出、资源清理
性能影响快速,无额外开销较慢,需要执行清理操作
资源泄漏风险较高(文件、内存等可能泄漏)较低(自动释放资源)
数据完整性可能丢失缓冲区的数据保证数据写入持久存储
调用链内核 → 进程终止用户清理 → 内核 → 进程终止
示例代码_exit(1);exit(0);
替代函数_Exit()(C99)无直接替代

        可以参考这个图

        终止进程最好使用exit函数

        return函数

        return是⼀种更常⻅的退出进程⽅法。执⾏return n等同于执⾏exit(n),因为调⽤main的运⾏时函数会将main的返回值当做 exit的参数

进程等待

        进程等待的必要性

        • 之前讲过,子进程退出,父进程如果不管不顾,就可能造成'僵尸进程'的问题,进而造成内存泄漏。
        • 另外,进程一旦变成僵尸状态,那就刀枪不入,"杀人不眨眼"的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
        • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
        • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

        进程等待的方法

        wait方法

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int* status);
返回值:成功返回被等待进程pid,失败返回-1。
参数:输出型参数,获取⼦进程退出状态,不关⼼则可以设置成为NULL

        如果父进程wait子进程,但是子进程不退出,那么父进程就会阻塞到wait中

        waitpid方法

pid_ t waitpid(pid_t pid, int *status, int options);
返回值:当正常返回的时候waitpid返回收集到的⼦进程的进程ID;如果设置了选项WNOHANG,⽽调⽤中waitpid发现没有已退出的⼦进程可收集,则返回0;如果调⽤中出错,则返回-1,这时errno会被设置成相应的值以指⽰错误所在;
参数:pid:Pid=-1,等待任⼀个⼦进程。与wait等效。Pid>0.等待其进程ID与pid相等的⼦进程。status: 输出型参数WIFEXITED(status): 若为正常终⽌⼦进程返回的状态,则为真。(查看进程是否是正常退出)WEXITSTATUS(status): 若WIFEXITED⾮零,提取⼦进程退出码。(查看进程的退出码)options:默认为0,表⽰阻塞等待WNOHANG: 若pid指定的⼦进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该⼦进程的ID。

        • 之前讲过,子进程退出,父进程如果不管不顾,就可能造成'僵尸进程'的问题,进而造成内存泄漏。
        • 另外,进程一旦变成僵尸状态,那就刀枪不入,"杀人不眨眼"的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
        • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如果子进程运行完成,结果对还是不对,或者是否正常退出。
        • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

        获取子进程status

        wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。

        如果传递NULL,表示不关心子进程的退出状态信息。

        否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。

        参数status是来获取子进程退出的信息的,本质上是为了得到子进程推出的两个数字。

        status有32位,但是我们只使用其低16位。

        status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):

        我们以这段代码验证

#include <sys/wait.h>
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>  // 添加这行int main()
{pid_t pid;if ((pid = fork()) == -1)perror("fork"), exit(1);if (pid == 0){sleep(2);exit(10);}else{int st;int ret = wait(&st);if (ret > 0 && (st & 0X7F) == 0){ // 正常退出printf("child exit code:%d\n", (st >> 8) & 0XFF);}else if (ret > 0){ // 异常退出printf("sig code : %d\n", st & 0X7F);}}
}

        结果在等待2秒后输出

        阻塞与非阻塞等待

        进程的阻塞等待

        我们写一段代码来验证:

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <stdlib.h>int main()
{printf("我是一个父进程:pid: %d, ppid: %d\n", getpid(), getppid());pid_t id = fork();if(id < 0){exit(1);}else if(id == 0){int cnt = 5;while(cnt--){printf("我是一个子进程:pid: %d, ppid: %d\n", getpid(), getppid());sleep(1);int* ptr=NULL;*ptr=100;//NULL的0号地址没办法写入,会导致野指针问题}exit(0);}else{int status = 0;pid_t rid = waitpid(-1, &status, 0);if(rid > 0){printf("等待成功,推出的子进程是:%d,退出码为:%d,退出信号为:%d\n", rid, (status >> 8) & 0xFF, status & 0x7F);  // 修正:0xFF 和 0x7F//(status>>8)&0xFF的作用:提取进程的正常退出码//原理:status >> 8:将状态值右移8位,把退出码移到低位,& 0xFF:用位与操作取低8位(一个字节),//当进程正常退出时(通过 exit()),退出码存储在高8位中//status&0x7F的作用:提取导致进程异常退出的信号编号//原理:0x7F 的二进制是 0111 1111   //& 0x7F:用位与操作取低7位//当进程因信号而异常终止时,信号编号存储在低7位中}}return 0;
}​​​

        结果为:

        我们的代码中是有野指针的,不过仍然正常退出。我们想排查错误可以通过

kill -l

        来查找推出信号。

        这些信号的内容和相关含义可以去文章末尾去查找,这里不再赘述。

#include <stdio.h>      // 用于 printf
#include <stdlib.h>     // 用于 exit
#include <unistd.h>     // 用于 fork, sleep, getpid
#include <sys/types.h>  // 用于 pid_t
#include <sys/wait.h>   // 用于 waitpid, WIFEXITED, WEXITSTATUSint main()
{pid_t pid;pid = fork();if(pid < 0){printf("%s fork error\n",__FUNCTION__);return 1;} else if( pid == 0 ){ //childprintf("child is run, pid is : %d\n",getpid());sleep(5);exit(257);} else{ //parentint status = 0;pid_t ret = waitpid(-1, &status, 0);//阻塞式等待,等待5Sprintf("this is test for wait\n");if( WIFEXITED(status) && ret == pid ){printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status));}else{printf("wait child failed, return.\n");return 1;}}return 0;
}

        进程的非等待阻塞

#include<iostream>
#include<cstdio>
#include<vector>
#include<sys/wait.h>
#include<sys/types.h>
#include<cstdlib>
#include<string>
#include<unistd.h>
using std::cout;
using std::endl;
using std::vector;typedef void (*callback_t)();  // 函数指针类型enum 
{OK,        // 正确运行USAGE_ERR  // 错误码
};void Task()
{int cnt = 5;while(cnt--){printf("我是一个子进程1, pid:>%d, ppid:>%d, cnt=%d\n", getpid(), getppid(), cnt);sleep(1);}
}void Hello()
{int cnt = 5;while(cnt--){printf("我是一个子进程2, 在完成hello任务, pid:>%d, ppid:>%d, cnt=%d\n", getpid(), getppid(), cnt);sleep(1);}
}// 新增任务函数3
void Work()
{int cnt = 5;while(cnt--){printf("我是一个子进程3, 正在工作, pid:>%d, ppid:>%d, cnt=%d\n", getpid(), getppid(), cnt);sleep(1);}
}// 新增任务函数4
void Play()
{int cnt = 5;while(cnt--){printf("我是一个子进程4, 正在玩耍, pid:>%d, ppid:>%d, cnt=%d\n", getpid(), getppid(), cnt);sleep(1);}
}// 对于参数一般而言
// 对于输入性参数:const &
// 对于输出性参数:*
// 对于输入输出性参数:&
template<class T>
void CreateChildProcess(int num, vector<T>* v, const vector<callback_t>& cbs)
{for(int i = 0; i < num; ++i){pid_t id = fork();if(id == 0)  // child{ // 根据下标i选择对应的任务函数// 使用取模运算确保不会越界int task_index = i % cbs.size();cbs[task_index]();  // 执行对应的任务函数exit(0);}v->push_back(id);  // 因为v是指针,所以用箭头printf("创建子进程 %d,将执行任务 %lu\n", id, i % cbs.size() + 1);}
}template<class T>
void WaitChild(const vector<T>& subs)
{for(auto& pdi : subs){int status = 0;pid_t rid = waitpid(pdi, &status, 0);if(rid > 0){printf("子进程为 %d, 退出码:%d\n", rid, WEXITSTATUS(status));        sleep(1);}}
}// 启动多进程方案
// 要这么用:./xxx 5 ??我们要创建5个子进程
int main(int argc, char* argv[])
{if(argc != 2){cout << "用法: " << argv[0] << " <进程数量>" << endl;exit(USAGE_ERR);}int num = atoi(argv[1]);  // 使用atoi避免兼容性问题vector<pid_t> subs;  // 进程PID容器vector<callback_t> cbs;  // 任务函数容器// 初始化任务函数容器cbs.push_back(Task);cbs.push_back(Hello);cbs.push_back(Work);cbs.push_back(Play);cout << "可用任务函数数量: " << cbs.size() << endl;cout << "将创建 " << num << " 个子进程" << endl;// 创建多进程CreateChildProcess(num, &subs, cbs);// 等待多进程WaitChild(subs);cout << "所有子进程执行完毕" << endl;return 0;
}

        运行结果为:

        父进程等待时间取决于子进程推出时间  

        阻塞等待在等待期间,什么都没干!!
        非阻塞等待,等待期间,可以干其他事!把等待时间利用了起来!

进程程序替换

        一个进程fork出父子进程后,谁先运行呢?这个是不那么确定的。

        不过对于一般的父子进程来说,谁先退出呢?子进程,然后由父进程回收子进程的资源。    

        替换原理

       用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

        程序替换本质是从磁盘中的代码和数据拷贝到内存中。程序运行前必须加载到内存。

        替换函数

        其实有六种以exec开头的函数,统称exec函数

#include <unistd.h>//库函数
int execl(const char *path, const char *arg, ...);int execlp(const char *file, const char *arg, ...);int execle(const char *path, const char *arg, ...,char *const envp[]);int execv(const char *path, char *const argv[]);int execvp(const char *file, char *const argv[]);int execvpe(const char *file, char *const argv[],char *const envp[]);
//系统调用函数
int execve(const char *path, char *const argv[], char *const envp[]);

        命名理解

  • exec:表示执行新程序

  • l:参数以列表(list)形式传递

  • v:参数以向量/数组(vector)形式传递

  • p:在PATH环境变量中搜索可执行文件

  • e:可以指定环境变量(environment)

函数名参数格式是否使用PATH搜索是否使用当前环境变量常用程度特点说明
execl列表★★☆☆☆需要完整路径,参数逐个列出
execlp列表★★★☆☆自动PATH搜索,参数逐个列出
execle列表否,须自己组装环境变量★★☆☆☆需要完整路径,可自定义环境变量
execv数组★★★☆☆需要完整路径,参数用数组传递
execvp数组★★★★★最常用,自动PATH搜索,参数用数组传递
execve数组否,须自己组装环境变量★★★★☆系统调用,可自定义环境变量

        函数解释


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

        

        1. execl - 列表形式 + 完整路径
int execl(const char *path, const char *arg, ...);
  • 参数列表形式传递参数

  • 需要提供完整路径

  • 参数以NULL结束

execl("/bin/ls", "ls", "-l", "-a", NULL);
        2. execlp - 列表形式 + PATH搜索
int execlp(const char *file, const char *arg, ...);
  • 参数列表形式传递参数

  • PATH环境变量中搜索可执行文件

  • 参数以NULL结束

execlp("ls", "ls", "-l", "-a", NULL);  // 不需要完整路径
        3. execle - 列表形式 + 环境变量
int execle(const char *path, const char *arg, ..., char *const envp[]);
  • 参数列表形式传递参数

  • 需要提供完整路径

  • 可以指定自定义环境变量数组

char *env[] = {"HOME=/home/user", "PATH=/bin", NULL};
execle("/bin/ls", "ls", "-l", NULL, env);
        4. execv - 数组形式 + 完整路径
int execv(const char *path, char *const argv[]);
  • 参数数组形式传递参数

  • 需要提供完整路径

char *args[] = {"ls", "-l", "-a", NULL};
execv("/bin/ls", args);
        5. execvp - 数组形式 + PATH搜索
int execvp(const char *file, char *const argv[]);
  • 参数数组形式传递参数

  • PATH环境变量中搜索可执行文件

char *args[] = {"ls", "-l", "-a", NULL};
execvp("ls", args);  // 最常用的版本
        6. execve - 数组形式 + 环境变量

int execve(const char *path, char *const argv[], char *const envp[]);
 
  • 参数数组形式传递参数

  • 需要提供完整路径

  • 可以指定自定义环境变量数组

  • 这是唯一的系统调用,其他都是库函数

char *args[] = {"ls", "-l", NULL};
char *env[] = {"HOME=/home/user", NULL};
execve("/bin/ls", args, env);

        事实上,只有execve是真正的系统调用,其它五个函数最终都调用execve,所以execve在man手册第2节,其它函数在man手册第3节。这些函数之间的关系如下图所示。

        下面我们以以下代码来做案例:

#include<stdio.h>
#include<sys/wait.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
int main()
{printf("我变成了一个进程:%d\n", getpid());pid_t id = fork();if(id == 0){// 执行另一个程序的代码execl("/usr/bin/ls", "ls", "-a", "-l", NULL); //程序替换函数exit(0);}wait(NULL);printf("我的代码运行中......\n");printf("我的代码运行中......\n");printf("我的代码运行中......\n");printf("我的代码运行中......\n");printf("我的代码运行中......\n");printf("我的代码运行中......\n");return 0;
}

        对于程序替换,部分参数可以省略。

        替换函数成功时没有返回值,失败时返回-1。

        也可以自己替换自己,不过会陷入死循环。(不推荐)

        替换函数可以实现执行其他语言的程序

        我们举例说明。

        C++测试代码

#include<iostream>
using std::cout;
using std::endl;int main()
{cout<<"我是一个C++程序"<<endl;    cout<<"我是一个C++程序"<<endl;    cout<<"我是一个C++程序"<<endl;    cout<<"我是一个C++程序"<<endl;    cout<<"我是一个C++程序"<<endl;    cout<<"我是一个C++程序"<<endl;    return 0;
}

        C语言代码

//C语言程序
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include <sys/wait.h>
//C语言下调用执行C++程序
int main()
{printf("我变成了一个进程:%d\n", getpid());pid_t id = fork();if(id == 0){sleep(2);printf("下面的代码都是子进程在执行:\n");execl("./test/Test","Test",NULL);return 0;}// 父进程代码可以放在这里wait(NULL);  // 等待子进程结束printf("父进程结束\n");int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid > 0){printf("wait success, exit code: %d\n", WEXITSTATUS(status));}  return 0;
}

        结果为:

        不仅是C++,Java,python甚至脚本语言都可以调用执行

        替换函数与环境变量

        主要函数为execvpe。其语法如下

        以以下代码为例子

#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>int main()
{printf("我变成了一个进程:%d\n", getpid());pid_t id = fork();if(id == 0){sleep(2);printf("下面的代码都是子进程在执行:\n");char *myargv[] = {(char*)"./test/Test",(char*)"-a",(char*)"-b",NULL};char *myenv[] = {(char*)"PATH=/home/loukou-ruizi/linux-learning/course_control/Exec/test/Test",NULL};extern char**environ;execvpe("./test/Test", myargv, environ); //覆盖式的使用全新的环境变量表                                        execvpe("./test/Test", myargv, myenv); //使用父进程的环境变量表}return 0;
}​

        

        命令行参数表和环境变量表是父进程通过exec*传递给你的

Linux信号含义表

信号编号信号名称含义说明默认行为
1SIGHUP挂起信号,终端断开连接终止进程
2SIGINT中断信号,Ctrl+C产生终止进程
3SIGQUIT退出信号,Ctrl+\产生终止并核心转储
4SIGILL非法CPU指令终止并核心转储
5SIGTRAP跟踪陷阱,调试器使用终止并核心转储
6SIGABRT异常终止,abort()产生终止并核心转储
7SIGBUS总线错误,内存访问错误终止并核心转储
8SIGFPE浮点异常,如除零错误终止并核心转储
9SIGKILL强制终止,不可捕获/忽略终止进程
10SIGUSR1用户自定义信号1终止进程
11SIGSEGV段错误,非法内存访问终止并核心转储
12SIGUSR2用户自定义信号2终止进程
13SIGPIPE管道破裂,向无读端管道写数据终止进程
14SIGALRM定时器信号,alarm()超时终止进程
15SIGTERM终止信号,友好终止请求终止进程
16SIGSTKFLT栈错误终止进程
17SIGCHLD子进程状态改变忽略信号
18SIGCONT继续执行,让停止进程继续继续进程
19SIGSTOP强制暂停,不可捕获/忽略停止进程
20SIGTSTP终端停止信号,Ctrl+Z产生停止进程
21SIGTTIN后台进程尝试读控制终端停止进程
22SIGTTOU后台进程尝试写控制终端停止进程
23SIGURG紧急数据到达忽略信号
24SIGXCPU超过CPU时间限制终止并核心转储
25SIGXFSZ超过文件大小限制终止并核心转储
26SIGVTALRM虚拟定时器超时终止进程
27SIGPROF性能分析定时器超时终止进程
28SIGWINCH窗口大小改变忽略信号
29SIGIO异步I/O事件终止进程
30SIGPWR电源故障终止进程
31SIGSYS非法系统调用终止并核心转储
32(未使用)保留信号,通常未使用-
33(未使用)保留信号,通常未使用-

        实时信号说明

信号范围类型说明
34-64SIGRTMIN到SIGRTMAX实时信号,用于应用程序自定义用途,可以排队不会丢失

        关键特性总结

类别信号特点
不可捕获SIGKILL(9), SIGSTOP(19)不能被进程捕获、忽略或阻塞
常见错误SIGSEGV(11), SIGFPE(8), SIGABRT(6)程序错误导致的信号
用户交互SIGINT(2), SIGTSTP(20), SIGQUIT(3)用户通过终端产生的信号
进程控制SIGTERM(15), SIGKILL(9), SIGCONT(18)用于进程管理和控制的信号
保留信号32, 33系统保留,通常不使用

        补充说明

  • 信号32和33:在大多数Linux系统中,这两个信号是保留的,不用于常规用途

  • 实时信号:34-64号信号为实时信号,具有排队功能,不会像标准信号那样丢失

  • 默认行为

    • 终止:进程立即结束

    • 终止并核心转储:进程结束并生成核心转储文件用于调试

    • 停止:进程暂停执行

    • 继续:让停止的进程恢复执行

    • 忽略:信号被忽略,不做任何处理

        本期的内容就到这里啦,喜欢的话请点个赞谢谢

封面图自取:

http://www.dtcms.com/a/598256.html

相关文章:

  • 【计算思维】蓝桥杯STEMA 科技素养考试真题及解析 6
  • 界面美观和可用性冲突时怎么办
  • 做网站能自己找服务器吗基础型网站价格
  • 网站建设哪家最专业2021网页游戏
  • 营销网站建设多少钱网站建设分享
  • 模板网站建设清单湘潭做网站广告的公司
  • 【SOMEIP】【R24-11】【需求翻译】[RS_SOMEIP_00005]-[RS_SOMEIP_00007]
  • 在安卓源码编译中,如何修改配置文件来增加L2TP功能
  • Netty详解-02
  • 快手网站题怎么做做直播哪个网站好
  • 脚本:使用AWR快照原始数据评估存储性能
  • 嵌入式C语言中指针详解
  • 网站建设要学多久wordpress能否解析万网的域名
  • stm32 printf重定向到USART
  • npu环境docker部署vllm
  • 建站广告爱山东app下载安装健康码
  • 网站网站做员工犯法吗企业建设网站的目的( )
  • SpringBoot面试题11-Bean的生命周期
  • 个人网站建设策划书怎么写经济技术开发区人才网
  • AI搜索优化技术特点与服务模式客观剖析,比较代表性公司优势
  • 网站制作服务公司网站建设具备什么条件
  • c2c网站开发策划别墅外观设计网站推荐
  • 免费建站搜索引擎 网站推广 举例
  • 梧州论坛看点重庆seo网站设计
  • 【SPIE出版丨往届已EI检索】第二届遥感技术与图像处理国际学术会议(RSTIP 2025)
  • 1-Linux驱动开发-内核模块介绍
  • 汽车品牌推广方案知名seo网站优化公司
  • 滕州英文网站建设网络推广营销工具
  • 网站建设太金手指六六二八宿州做网站公司
  • 国外做网站公司能赚钱电商培训方案