Linux系统进程控制

目录

一、进程创建

1.进程创建过程

2.写时拷贝

3.fork函数的两种常规用法

二、进程终止

1.进程终止的三种情况

2.进程退出信息

(1)退出码

(2)退出信号

3.进程终止的方式

三、进程等待

1.为什么要有进程等待?

2.进程等待方法

(1)wait函数

(2)waitpid函数

四、进程替换

1.进程替换原理

2.进程替换函数

(1)execl函数

(2)execlp函数

(3)execle函数

(4) execv函数

(5)execvp函数

(6)execve函数

 (7)execvpe函数

补充:


一、进程创建

进程创建即使用fork函数创建子进程,fork函数的返回值有三种情况:

对于子进程返回0;对于父进程返回新创建子进程pid;对于进程创建失败返回-1

1.进程创建过程

调用fork函数后,操作系统为子进程开辟一份新空间,将父进程的部分内核数据结构(task_struct,mm_struct,页表)拷贝给子进程,添加子进程到系统调度队列中,fork函数返回,系统开始调度进程。

注意:进程创建一定是先创建其内核数据结构,再加载其代码和数据

2.写时拷贝

子进程会继承父进程的数据,父子进程共用一份数据,当子进程要修改数据时,为了不影响父进程,因此要发生写时拷贝:即将父进程的数据重新拷贝一份,子进程再进行修改。

3.fork函数的两种常规用法
  1. 使用fork函数创建子进程,使父子进程同时执行不同的代码段,例如父进程等待客户端生成请求,生成子进程处理请求
  2. 使用fork函数创建子进程,再调用exec函数,使得子进程执行其他进程(进程替换)

二、进程终止

进程终止就是要释放进程加载到内存中的代码和数据,以及进程的内核数据结构。

进程终止通常是先释放其代码和数据,此时进程处于僵尸状态,等待其退出信息被父进程读取后再释放其内核数据结构。

1.进程终止的三种情况

一个进程终止,有三种情况:

  1. 代码运行完毕,结果正确
  2. 代码运行完毕,结果不正确
  3. 代码异常终止

每种情况进程终止时都会返回退出信息

2.进程退出信息

进程退出信息包括:退出码和退出信号

(1)退出码

代码执行完毕,进程会返回退出码,有0和非0,0表示运行结果正确,非0值不同的值有不同的错误描述。使用echo $? 命令查看最近一个子进程的退出码

我们写程序时也可以自定义退出码,不使用系统规定的退出码

#include <stdio.h>
// 自定义退出码
enum{
    Success=0,
    Div_Zero,
    Mod_Zero
};
// 退出描述
const char* Exit_Description(int Exit_Code)
{
    switch(Exit_Code)
    {
        case Success:
            return "Success!";
        case Div_Zero:
            return "div zero!";
        case Mod_Zero:
            return "mod zero!";
        default:
            return "unknow error!";
    }
}
// 默认退出码
int Exit_Code=Success;
// 除法函数
int Div(int x, int y) {
    if (y == 0) { // 除数为0
        Exit_Code = Div_Zero;
        return -1;
    } else {
        return x / y;
    }
}
int main() {
    int result = Div(10, 100);
    printf("result: %d [%s]\n", result, Exit_Description(Exit_Code));
    result = Div(10, 0);
    printf("result: %d [%s]\n", result, Exit_Description(Exit_Code));
    return 0;
}
//result: 0 [Success!]
//result: -1 [div zero!]
(2)退出信号

当进程运行异常终止时,会返回退出信号,退出码就无意义了

使用kill -l命令查看所有的退出信号

3.进程终止的方式
  1. main函数return
  2. exit函数
  3. _exit函数

void exit(int status)
头文件:#include <unistd.h>   status是自定义的退出码

void _exit(int status);
头文件:#include <unistd.h>   status是自定义的退出码 

_exit函数是系统调用指令,exit函数是库函数,不管在任何位置调用_exit或者exit,都表示整个进程终止了。exit库函数底层就是调用_exit指令实现的。

exit终止进程时会冲刷缓冲区,_exit终止进程时不会冲刷缓冲区

三、进程等待

1.为什么要有进程等待?

父进程要通过等待,回收子进程资源(僵尸进程),获取子进程退出信息

2.进程等待方法

进程等待通过wait和waitpid函数实现

(1)wait函数

pid_t wait(int*status)

头文件:#include <sys/types.h>  #include <sys/wait.h>

等待最近子进程返回,等待成功返回被等待进程pid,等待失败返回-1;

status为输出型参数,用于获取子进程的退出信息,不关心则可设置为NULL。status

是一个32位整型数,使用其低16位来保存退出信息。其中,0~7存储退出信号,8~15存储退出码

(status>>8)&0xFF即为退出码,status&0x7F即为退出信号

wait函数代码演示

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
void ChildRun()
{
    int i=0;
    for(i=0;i<5;++i)
    {
        printf("Child process is running!\n");
    }
}
int main()
{
    pid_t id=fork();
    //子进程
    if(id==0)
    {                                                                             
        ChildRun();                                 
        exit(0);//子进程退出      
    }              
    //父进程                                   
    int status=0;                   
    pid_t rid=wait(&status);
    if(rid>0)                           
    {                  
        printf("Father process wait success! status:%d, child quit code:%d, child quit signal:%d\n",status,(status>>8)&0xFF,status&0x7F);
    }                                                              
    else                                                    
    {                                                     
        printf("Father process wait fialed!\n");                                                                                                                                       
    }                                                  
    return 0;                                  
}
(2)waitpid函数

pid_ t waitpid(pid_t pid, int *status, int options);
头文件和wait函数相同,pid是等待指定进程返回,status是输出型参数,options用于确定阻塞等待还是非阻塞等待

status输出型参数除了可以使用位运算获取退出码和退出信号,还可以使用WIFEXITED和WEXITSTATUS

WIFEXITED(status):查看进程是否正常终止(正常终止返回真,异常终止返回假)

WEXITSTATUS(status):若WIFEXITED非零(进程正常终止),则提取进程退出码

options参数可以使用WNOHANG,来实现非阻塞等待(在等待的过程中,父进程可以去完成其他任务)

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

如果pid传入-1,options传入0则与wait函数等效

waitpid函数实现非阻塞等待代码

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

void ChildRun()
{
    int i=0;
    for(i=0;i<5;++i)
    {
        printf("Child process is running!\n");
    }
}

void DoOtherthing()
{
    printf("Child process is not end! Father process is do other thing...\n");
}
int main()
{
    pid_t id = fork();
    if (id < 0)
    {
        printf("fork error!\n"); // 子进程创建失败
    }
    else if (id == 0) // 子进程执行
    {
        ChildRun();
        exit(123);
    }
    else // 父进程执行
    {
        int status = 0;
        pid_t rid = waitpid(id, &status, WNOHANG); // 非阻塞等待
        // 子进程未终止
        while (rid == 0)
        {
            DoOtherthing(); // 父进程做其他事情
            rid = waitpid(id, &status, WNOHANG);
        }
        
        // 子进程等待失败
        if (rid == -1)
        {
            printf("wait failed!\n");
        }
        else // 子进程等待成功
        {
            if (WIFEXITED(status)) // 子进程正常终止
            {
                printf("wait success! child exit code:%d\n", WEXITSTATUS(status));
            }
            else // 子进程异常终止
            {
                printf("wait success! child abnormal exit\n");
            }
        }
    }
    return 0;
}

四、进程替换

1.进程替换原理

用fork函数创建子进程后,父子进程执行的是相同的程序。当子进程调用exec函数,子进程要写发生代码和数据的写时拷贝,然后子进程的代码和数据会被新程序的代码和数据替换,这个过程中并没有创建新的进程。

2.进程替换函数
(1)execl函数

int execl(const char *path, const char *arg, ...);

execl中的 l 表示路径,path是参数路径,arg参数传参只需要像Linux命令一样带上选项即可,其最后一个选项必须是NULL(本质就是命令行参数)

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

int main()
{
    pid_t id = fork();
    if (id == 0) // 子进程
    {
        execl("/usr/bin/ls", "ls", "-l", "-a", "--color", NULL);
        exit(1);
    }
    else if (id > 0) // 父进程
    {
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if (rid > 0)
        {
            printf("father process wait success! child exit code:%d\n", WEXITSTATUS(status));
        }
        else
        {
            printf("father process wait failed!\n");
        }
    }
    else
    {
        printf("fork failed!\n");
    }
}
(2)execlp函数

int execlp(const char *file, const char *arg, ...);
execlp函数和execl函数只有第一个参数不同,file参数表示只需要传文件名,不需要传路径,系统会自动在环境变量PATH保存的路径中查找文件

execl("ls", "ls", "-l", "-a", "--color", NULL);
(3)execle函数

int execle(const char *path, const char *arg, ...,char *const envp[]);

execle函数比execl函数多了第三个参数,参数envp用于接收环境变量(可以是自定义的环境变量,也可以是bash父进程的环境变量)

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

int main()
{
    pid_t id = fork();
    if (id == 0) // 子进程
    {
        //传入自定义环境变量
        char* const envp[] = {(char*)"HAHA=111", (char*)"HEHE=222", NULL};
        execle("/home/zz/240927/test1cpp.exe", "test1cpp.exe", NULL, envp);
        //传入父进程的环境变量
        // extern char** environ;
        // execle("/home/zz/240927/test1cpp.exe", "test1cpp.exe", NULL, environ); 
    }
    else if (id > 0) // 父进程
    {
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if (rid == -1)
            printf("wait failed\n");
        else
        {
            if (WIFEXITED(status))
                printf("wait success! child exit code:%d\n", WEXITSTATUS(status));
            else
                printf("wait success! child abnormal exit\n");
        }
    }
    else
    {
        printf("fork failed\n");
        exit(-1);
    }
    return 0;
}
(4) execv函数

int execv(const char *path, char *const argv[]);

execv中的 v 表示容器,path是参数路径,argv是将命令选项都放入其中

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

int main()
{
    pid_t id = fork();
    if (id == 0) // 子进程
    {
        char* const argv[] = {
            (char*) "ls",
            (char*) "-l",
            (char*) "-a",
            (char*) "--color",
            NULL
        };
        execv("/usr/bin/ls", argv);
        exit(1);
    }
    else if (id > 0) // 父进程
    {
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if (rid > 0)
        {
            printf("father process wait success! child exit code:%d\n", WEXITSTATUS(status));
        }
        else
        {
            printf("father process wait failed!\n");
        }
    }
    else
    {
        printf("fork failed!\n");
    }
}
(5)execvp函数

int execvp(const char *file, char *const argv[]);
同execlp函数,不需要传路径,只要传文件名即可

char* const argv[] = {
    (char*) "ls",
    (char*) "-l",
    (char*) "-a",
    (char*) "--color",
    NULL
};
execv("ls", argv);
(6)execve函数

int execve(const char *path, char *const argv[], char *const envp[]);

execve函数比execv函数多了第三个参数envp,用于接收环境变量

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

int main()
{
    pid_t id = fork();
    if (id == 0) // 子进程
    {
        char* const argv[] = {(char*)"test1cpp.exe", (char*)"-a", (char*)"-b", (char*)"-c", NULL};
        char* const envp[] = {(char*)"HAHA=111", (char*)"HEHE=222", NULL};
        execve("/home/zz/240927/test1cpp.exe", argv, envp);
        // extern char** environ;
        // execle("/home/zz/240927/test1cpp.exe", "test1cpp.exe", NULL, environ); 
    }
    else if (id > 0) // 父进程
    {
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if (rid == -1)
            printf("wait failed\n");
        else
        {
            if (WIFEXITED(status))
                printf("wait success! child exit code:%d\n", WEXITSTATUS(status));
            else
                printf("wait success! child abnormal exit\n");
        }
    }
    else
    {
        printf("fork failed\n");
        exit(-1);
    }
    return 0;
}
 (7)execvpe函数

int execve(const char *file, char *const argv[], char *const envp[]);

根据上述所有函数,execvpe函数很容易能理解,此处不做代码演示

补充:

execve既是一个系统调用,也是一个C语言库函数,其他的所有函数都是基于execve封装实现的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南林yan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值