【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内去运行

相关推荐
番茄啊30 分钟前
linux 开启xftp连接
linux·运维·服务器
CodeYp201938 分钟前
第1讲:Linux 基础指令
linux·指令
李松桃1 小时前
基于端口号的服务配置
linux·nginx-核心模块配置
CodeYp20196 小时前
第14讲:mmap 实现文件 LRU 缓存
linux·lru·mmap
程序开发阿元6 小时前
一台 20G 的 VPS 被日志吃掉一半:我按这个顺序查了二十分钟
linux·ssh
.冰块.6 小时前
流量大了自动加 Pod!K8s HPA 扩缩容与 ResourceQuota/LimitRange 资源管控实战
linux·云原生·容器·kubernetes·hpa·quota·limitrange
承渊政道7 小时前
Linux网络学习【深入理解多路转接select、poll的代码改写】
linux·ubuntu·select·多路转接·poll
雪落漂泊7 小时前
Linux进程(四)概念:命令行参数和环境变量篇
linux·运维·服务器
qq_377572777 小时前
linux x86 arm loongarch
linux
Dragon~Snow8 小时前
Centos Stream10 安装 MySQL-9.7.2-linux-glibc2.28-x86_64.tar
linux·mysql·centos