Linux 进程控制

2023-12-13 22:50:13

进程创建

  1. fork函数
#include <unistd.h>

pid_t fork(void);

函数返回值

在父进程中,fork函数返回子进程的进程ID(PID)
在子进程中,fork函数返回0。
fork执行失败时返回-1. fork 执行失败的原因一般是系统资源不足

fork函数的一般使用逻辑:

int main() {
    pid_t pid = fork();

    if (pid < 0) 
    {
        // fork失败,创建子进程失败
        fprintf(stderr, "Fork failed\n");
        return 1;
    } 
    else if (pid == 0) 
    {
        // 子进程执行的代码逻辑
        printf("This is the child process\n");
        // 子进程执行其他任务...
    } 
    else 
    {
        // 父进程执行的代码逻辑
        printf("This is the parent process, child PID: %d\n", pid);
        // 父进程执行其他任务...
    }

    return 0;
}

内核视角
分配新的内存块和内核数据结构给子进程
将父进程部分数据结构内容拷贝至子进程
添加子进程到系统进程列表当中
fork返回,开始调度器调度

进程终止

进程终止的原因

  1. main函数返回
return n**等同于执行exit(n**),因为调用main的运行时函数会将main的返回值当做 exit的参数。
  1. exit函数返回
  2. _exit函数返沪
#include <unistd.h>

void _exit(int status);

status 定义了进程的终止状态,父进程通过wait来获取该值
虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值
是255。

  1. 信号终止

_exit函数和exit函数说明:

  1. _exit函数
_exit函数是一个系统调用函数,用于立即终止程序的执行,不会执行任何清理工作,包括关闭文件、释放内存等。
它的原型为:void _exit(int status)。
status参数表示程序的终止状态,通常0表示正常终止,非零值表示异常终止。
_exit函数不会刷新缓冲区,也不会调用任何终止处理程序,直接退出程序。
  1. exit函数:
exit函数是标准库函数,用于终止程序的执行,并执行一些清理工作,如关闭文件、释放内存等。
它的原型为:void exit(int status)。
status参数表示程序的终止状态,通常0表示正常终止,非零值表示异常终止。
exit函数会先刷新缓冲区,然后调用终止处理程序(通过atexit注册的函数),最后退出程序。

在这里插入图片描述

进程结果

获取进程结果的方法

  1. 命令获取
  echo $?

$?是一个特殊变量,用于获取上一个命令的退出状态码。退出状态码是一个整数值,表示上一个命令的执行结果

  1. 父进程进程调用函数等待

wait函数

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);

参数
status是一个指向整型变量的指针,用于存储子进程的退出状态。
如果不关心子进程的退出状态,可以将status设置为NULL。
返回值
wait()函数的返回值是子进程的进程ID,如果出错则返回-1。
如果当前进程没有子进程,返回-1,并设置errno为ECHILD
如果调用被信号中断,返回-1,并设置errno为EINTR

waitpid函数

#include <sys/types.h>
#include <sys/wait.h>

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

参数
pid:
pid > 0:等待进程ID为pid的子进程。
pid = -1:等待任意子进程,与wait函数相同。
pid = 0:等待与当前进程的进程组ID相同的任意子进程。

status:
输入输出型参数,一个指向整型的指针,用于获取子进程的终止状态。

options:
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。
若正常结束,则返回该子进程的ID。

返回值
如果成功等待到子进程的终止,返回子进程的进程ID;
如果指定的子进程尚未终止,并且使用了WNOHANG选项,则返回0;
如果调用被信号中断,返回-1,并设置errno为EINTR;
如果指定的子进程不存在或不属于当前进程的子进程组,返回-1,并设置errno为ECHILD

status参数

status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特
位),可以看到只有先判断进程是正常退出后,退出状态码才是有效的。
在这里插入图片描述

WIFEXITED 是一个(macro),用于检查 wait 或 waitpid 函数返回的子进程状态(status)是否是正常退出。WIFEXITED(status) 接受一个 status 参数,该参数是 wait 或 waitpid 函数返回的子进程状态信息。WIFEXITED 宏会对 status 进行解析,并返回一个非零值(true)表示子进程是正常退出的,返回零值(false)表示子进程不是正常退出的。

如果 WIFEXITED(status) 返回为真,可以使用 WEXITSTATUS(status) 宏来获取子进程的退出状态码。WEXITSTATUS(status) 会返回子进程的退出状态码,该状态码通常是在子进程调用 exit 或返回 main 函数时传递的值。

如果 WIFEXITED(status) 返回为假,可使用WTERMSIG(status),同样是通过wait或waitpid函数获取的子进程的终止状态。如果进程是由于收到信号而异常终止,则WTERMSIG宏返回信号的编号;否则,返回0。

具体使用:

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

int main() 
{
    pid_t pid = fork();

    if (pid == 0) 
    {
        // 子进程
        printf("This is child process\n");
        // 子进程正常退出,退出状态码为 42
        exit(42);
    } 
    else if (pid > 0) 
    {
        // 父进程
        int status;
        wait(&status);
        
        if (WIFEXITED(status)) 
        {
            // 子进程正常退出
            int exit_status = WEXITSTATUS(status);
            printf("Child process exited with status: %d\n", exit_status);
        }
         else 
        {
            // 子进程非正常退出
            WTERMSIG(int status); // WTERMSIG获取异常退出时信号的编号
            printf("Child process did not exit normally\n");
        }
    }
     else 
    {
        // fork 出错
        perror("fork");
        return 1;
    }

    return 0;
}

进程替换

进程替换原理

当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动
例程开始执行,调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。结合下图,进程替换的本质是替换虚拟内存和物理内存/外存的映射关系,故这个过程保留了原进程的task_struct, 和原进程存储堆和栈区中的数据。

需要注意:

  1. 进程替换后,task_struct 结构体中执行流相关字段会被重置,新的程序加载进来的代码从main函数重新开始执行
  2. 环境变量存储在环境变量表中,环境变量表也是task_struct的一个字段,所以进行进程切换的用户可以通过调用不同类型的进程替换函数来决定是否保留环境变量
  3. 进程替换本质没有创建新的task_struct, 也就是说没有创建新的进程,故进程ID不变。
  4. 堆、栈区数据被保留
  5. 文件描述符存储在文件描述表中,文件描述符表存储在task_struct中,所以如果在进程替换前如果对文件描述符进行了重定向,重定向的效果会保留到进程替换过后的进程里 。

在这里插入图片描述

进程替换函数

#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 execve(const char *path, char *const argv[], char *const envp[]);

返回值::
成功无返回值
失败返回 -1
参数
path :可执行文件绝对路径
file :可执行文件名
arg : 可变参数列表
envp : 环境变量数组
argv : 参数数组
在这里插入图片描述
记忆方法:
l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量

补充/拓展

  1. 终止信号: 下图前31种信号为进程异常退出时可能接收的信号,信号的具体含义可通过man手册查明。
man 7 signal

在这里插入图片描述

  1. 核心转储
    有时在本地运行程序时会报错:Segmentation fault (core dumped) ,这表明发生了核心转储。

Linux核心转储(coredump)是指在程序发生崩溃或异常终止时,将程序在崩溃时的内存状态保存到磁盘上的一个文件中。这个文件可以用于调试程序,找出导致程序崩溃的原因。

在Linux系统中,当一个程序崩溃时,默认情况下会生成一个核心转储文件。这个文件包含了程序崩溃时的内存映像、寄存器状态、堆栈信息等。通过分析这个文件,开发人员可以确定程序崩溃的原因,并进行调试。

要启用核心转储功能,可以通过设置ulimit命令来控制。ulimit命令用于设置shell进程的资源限制,其中包括核心转储文件的大小限制。可以使用ulimit -c命令来查看当前的核心转储文件大小限制,使用ulimit -c unlimited命令来设置核心转储文件的大小为无限制。

当程序崩溃时,核心转储文件会被保存在当前工作目录下。可以使用gdb调试器来分析核心转储文件。例如,可以使用gdb程序名核心转储文件来加载核心转储文件并进行调试。

需要注意的是,核心转储文件可能会非常大,因此在生产环境中可能需要限制核心转储文件的大小,以避免磁盘空间被耗尽。另外,核心转储文件可能包含敏感信息,因此在共享或提交给第三方时需要谨慎处理。

  1. 进程替换的实际使用(位于代码调用cgi模式的部分)
    个人代码练习

文章来源:https://blog.csdn.net/WSK1454360679/article/details/134852344
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。