【Linux】进程控制

目录

进程创建

进程终止

进程等待

进程程序替换

自定义shell的编写


进程创建

1、fork()函数创建进程,其返回值

(1)子进程返回0

(2)父进程返回的是子进程的pid

(3)子进程复制父进程的数据和代码,当二者没有写的时候,共享同一份数据和代码,因此PC的值也是一样,故在子进程中不会再执行fork()及之前的代码,因为父进程的PC保存的是下一条代码的地址。

2、fork()函数的常规用法

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

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

进程终止

1、进程退出

(1)一个进程的退出码,用echo $?去打印

(2)如果将来main函数退出的时候,就会把退出码写到task_struct中去,让bash获得

(3)如果我在main函数里面写了退出比如return 89,但要是程序异常,这个89就不是当前进程的退出码了,就无意义了。

2、退出三种场景

代码运行完毕,结果正确

代码运行完毕,结果不正确

代码异常终止

3、退出三种方法:

(1)从main返回:return

(2)调用exit :下面代码只会打印begin,然后echo $?会打印40

cpp 复制代码
void fun()
{
    printf("begin\n");
    exit(40);
    printf("end\n");
}

void main()
{
    fun();
}

(3)_exit

补充:printf打印内容到屏幕时,会先把内容加载到缓冲区,这时如果sleep(2)的话,就不会马上打印内容,但是要是遇到\n或者进程结束才会刷新缓冲区将所有缓冲区内容打印到屏幕,但是_exit()退出的时候,不会刷新缓冲区

cpp 复制代码
void main()
{
    printf("main");
    sleep(2);
    exit(23);//会打印main,因为exit()会刷新缓冲区
}
cpp 复制代码
void main()
{
    printf("main");
    sleep(2);
    _exit(23);//直接退出main,根本不会打印main,因为用_exit退出的
}
    

库(下级)调用系统调用(上级),exit()是库,_exit()是系统调用,所以说明缓冲区一定是库的缓冲区。因为如果是系统的缓冲区,那么两个退出都应该刷新,但是只有exit()会去刷新

进程等待

1、等待的原因

2、等待的方法:wait()/waitpid()

(1)wait()

能够解决僵尸,当子进程运行的时候,父进程一直在等待子进程运行,也就是父进程会被阻塞,类似于c语言中的scanf()一样;

wait执行成功返回子进程的pid

(2)waitpid(pid_t pid, int *status, int options)

参数和wait不同,第一个pid,可以指定获取哪个子进程的pid,也就是要父进程指定等哪一个子进程;

第二个参数status是输出型参数,下面是示例:

运行完成后发现status并不是子进程的退出码1,而是256,这是为什么?

status是32位的,高16位不用,只用低16位,低16位情况如下:

退出码是1,后面8位全是0,则100000000就是256

如果代码运行完毕,结果正确返回的退出码是0;

如果代码运行完毕,结果不正确,返回错误的退出码;

如果异常,代码没运行完,就不会看退出码,没用

僵尸进程是数据和代码资源会被释放,但是PCB还会被系统保存,等待父进程去处理;所以父进程是怎么通过waitpid获取子进程的status的呢?就是通过系统调用,查看子进程的PCB,所以PCB里面一定有进程退出码等信息;用宏也可以获取,如下图所示

status: 输出型参数

WIFEXITED(status): 若为正常终⽌⼦进程返回的状态,则为真。(查看进程 是否是正常退出) WEXITSTATUS(status): 若 WIFEXITED ⾮零,提取⼦进程退出码。(查看进程 的退出码)

(3)阻塞与非阻塞等待

非阻塞等待:主进程在等待子进程运行的时候,可以干自己的事情;

阻塞等待:主进程在等待子进程运行的时候,不能干自己的事情,要一直等

cpp 复制代码
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>

int main() {
    pid_t child = fork();
    if (child == 0) {
        // 子进程:立即退出
        _exit(0);
    }

    // 父进程场景1:正常回收子进程
    pid_t ret1 = waitpid(child, NULL, 0);
    printf("正常回收返回:%d(子进程PID)\n", ret1);  // 输出子进程PID

    // 父进程场景2:使用WNOHANG且无子进程可回收
    pid_t ret2 = waitpid(-1, NULL, WNOHANG);
    printf("WNOHANG无进程返回:%d\n", ret2);  // 输出0

    // 父进程场景3:调用出错(指定不存在的PID)
    pid_t ret3 = waitpid(99999, NULL, 0);
    if (ret3 == -1) {
        printf("调用出错返回:%d,错误原因:%s\n", ret3, strerror(errno));
        // 输出:-1,错误原因:No child processes
    }

    return 0;
}

阻塞等待的例子:sleep也是一个子进程,父进程是bash,当子进程运行的时候,我们在命令行输入ls 等没有反应,也就是bash阻塞等待了

进程程序替换

1、为什么要有程序替换?

fork() 之后,⽗⼦各⾃执⾏⽗进程代码的⼀部分如果⼦进程就想执⾏⼀个全新的程序呢?进程的程序 替换来完成这个功能!

2、程序替换的例子:main的后续直接不运行了

3、进程程序替换的原理

在execl里面调用python/C++程序都是可以的

如下是替换的代码

程序替换并不会创建新的进程

execv用法示例,其实就是用数组提前把参数写好了而已:

所以通过argv给子进程的main函数就可以传参数了,即-l -a,那同理,谁给我们的main函数传递的argv呢,就是bash通过execv给main传递的

execlp:

execvp:

execvpe: 多了一个环境变量

父进程可以传递环境变量给子进程,但是会覆盖掉原本子进程就有的,所以可以用putenv方法在子进程中追加环境变量,而不是覆盖原来的所有

只有execve是系统调用,其他都是语言的封装

自定义shell的编写

1、bash运行命令的原理:外部命令创建子进程,内建命令在bash内去运行

相关推荐
Xの哲學2 小时前
Linux电源管理深度剖析
linux·服务器·算法·架构·边缘计算
破刺不会编程2 小时前
socket编程TCP
linux·运维·服务器·开发语言·网络·网络协议·tcp/ip
CILMY232 小时前
【Linux】进度条实践教程:使用Makefile构建项目
linux·进度条·make和makefile
沉在嵌入式的鱼2 小时前
linux串口对0X0D、0X0A等特殊字符的处理
linux·stm32·单片机·特殊字符·串口配置
Better Bench3 小时前
Ubuntu aarch64\arm64系统安装vscode
linux·vscode·ubuntu
暴风游侠4 小时前
linux知识点-服务相关
linux·服务器·笔记
阿海5744 小时前
卸载nginx的shell脚本
linux·nginx
JANG10244 小时前
【Linux】常用指令
linux·服务器·javascript
DeeplyMind4 小时前
使用parted工具扩展QCOW2磁盘大小完整方案
linux·qemu·virtialization