Linux进程控制

进程创建

fork函数

它从已存在的进程中创建一个新进程,新进程为子进程,而原进程为父进程

fork创建子进程,OS做了什么?

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

写时拷贝

通常父子代码共享,父子在不写入时,数据也是共享的,当任意一方试图写入时,便以写时拷贝的方式产生各自的一份副本。实现了父子进程的彻底分离,完成了进程独立性,可以提高整机内存的使用效率。

进程终止

  1. 进程终止时,操作系统做什么?                                                                                                释放进程申请的相关内核数据结构和对应的数据和代码,本质就是释放系统资源
  2. 进程退出场景:代码运行完毕且结果正确;代码运行完毕,结果不正确;代码异常终止 
  3. 退出方式:从main函数返回;调用exit;_exit

可以使用echo $? 查看进程退出码

exit是库函数,会刷新缓冲区,在最后会调用_exit(系统接口)。而_exit 不会清理缓冲区

进程等待

进程等待的必要性

  1. 子进程退出,父进程不管子进程,子进程就处于僵尸状态,一直维护就会导致内存泄漏
  2. 父进程通过进程等待的方式,回收子进程资源,获取子进程退出码
  • 进程等待的方式

wait

Linux进程控制_第1张图片

 成功返回被等待进程的pid,失败返回-1。参数是输出型参数,获取子进程退出状态,不关心可以设置为NULL。wait是阻塞式等待。

waitpid

  1.  pid
    1. pid=-1,等待任意一个子进程,与wait等效
    2. pid>0,等待指定进程
  2. status
    1. WIFEXITED:查看进程是否是正常退出。正常退出则为真
    2. WEXITSTATUS:查看子进程退出码。若WIFEXITED非0,则提取退出码
  3. options
    1. WNOHANG:表示非阻塞等待。若pid指定的子进程没有结束,则函数返回0。正常结束就返回该子进程的PID。
    2. 默认是0,表示阻塞等待。

阻塞等待(Blocking Wait)和非阻塞等待(Non-blocking Wait)是两种不同的等待机制,它们在进程或线程等待条件满足时的行为和效果上存在区别。

阻塞等待:

  • 当进程或线程进行阻塞等待时,它会暂停执行,并释放CPU资源给其他可执行的进程或线程。
  • 进程或线程在等待期间无法继续执行其他任务,直到等待的条件满足。
  • 阻塞等待是一种同步操作,它会让进程或线程等待在等待队列中,直到某个事件发生或条件满足。
  • 阻塞等待可以确保在等待期间资源得到正确使用和分配,但可能导致整体执行时间较长。

非阻塞等待:

  • 当进程或线程进行非阻塞等待时,它会持续执行,并不会暂停或释放CPU资源。
  • 进程或线程在等待期间可以继续执行其他任务,并轮询检查等待的条件是否满足。
  • 非阻塞等待是一种轮询式的等待机制,它可以在等待的同时继续执行其他任务。
  • 非阻塞等待可以减少整体执行时间,但可能会导致CPU占用较高,因为进程或线程需要主动检查等待的条件。

总结:
阻塞等待会暂停执行并释放CPU资源,直到条件满足,而非阻塞等待会持续执行并轮询检查条件。阻塞等待确保资源的正确使用和分配,但可能导致较长的执行时间,而非阻塞等待可以减少执行时间,但可能导致CPU占用较高。选择使用阻塞等待还是非阻塞等待取决于具体的应用场景和需求。

status的构成:status并不是按照整数来整体使用的,而是按照比特位的方式,将32位比特位划分,我们只需要学习低16位,次低8位表示进程的退出码,最低的7位表示收到的信号。还有一位是core dump标志,表示是否发生了核心转储

Linux进程控制_第2张图片

(status>>8)&0xff // 得到的是退出码
status&0x7f // 得到的信号码,信号码非0,退出码就无意义,有可能是外部杀死

 示例:

#include 
#include 
#include 
#include 
#include 
#include 


int main()
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        exit(1); //标识进程运行完毕,结果不正确
    }
    else if(id == 0)
    {
        //子进程
        int cnt = 5;
        while(cnt)
        {
            printf("我是子进程:%d\n",cnt--);
            sleep(1);
        }
        exit(11);
    }
    else
    {
        int status = 0; 
        pid_t ret = waitpid(id, &status, 0); 
        //阻塞式的等待!只有子进程退出了,父进程才会waitpid函数进行返回,,父进程依旧还活着。wait/waitpid可以让进程退出具有一定的顺序性
        if(WIFEXITED(status))
        {
            printf("子进程正常退出:%d\n",WEXITSTATUS(status));           
        } 
        else
        {
            printf("子进程退出异常\n");
        }
    }
}

使用wait/waitpid函数等待子进程退出并获取其退出状态,相较于使用全局变量的方式,有以下几个优势:

  1. 阻塞等待:wait/waitpid函数可以实现阻塞等待子进程退出。这意味着父进程会被暂停,直到子进程退出为止。这样可以避免父进程在子进程还没有完成时继续执行,从而确保父进程能够获取到子进程的退出状态。

  2. 释放资源:通过使用wait/waitpid,操作系统可以及时回收子进程的资源,防止产生孤儿进程。子进程退出后,操作系统会清理子进程的资源,包括退出状态、内存、文件描述符等。这样可以有效避免资源泄漏和浪费。

  3. 处理多个子进程:如果父进程创建了多个子进程,使用wait/waitpid可以分开处理每个子进程退出的情况。wait/waitpid函数提供了灵活的参数设置,例如可以使用waitpid指定要等待的具体子进程PID,也可以使用WNOHANG选项进行非阻塞轮询等待。这样可以更好地组织和管理多个子进程的退出状态。

  4. 错误处理:wait/waitpid函数可以处理多种错误和退出状态。通过检查wait/waitpid返回值,可以得知子进程是正常退出还是异常终止,以及导致异常终止的原因。这对于父进程来说是很有价值的,可以根据返回值采取相应的后续操作,如重启子进程或记录异常日志等。

既然进程具有独立性,进程退出码也是子进程的数据,父进程怎么拿到的?

        至少要保留该进程的PCB信息,里面保留了任何进程退出时的退出结果信息。wait的本质就是读取了子进程的PCB

wait和waitpid就是系统调用

进程替换

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

是通过特定的接口,加载磁盘上的一个全新的程序,加载到调用进程的地址空间中,让子进程执行其他的程序,并和当前进程的页表重新建立映射。

如果新程序替换成功,则原先的进程已经不存在,变成了全新的程序进程。如果原进程没有退出,那么新进程也不会执行。如果需要在运行新程序时保留原进程,可以使用fork创建子进程,然后在子进程中调用替换函数新程序,而父进程继续执行原程序。

详细说明:

  1. execl
    • 函数原型:int execl(const char *path, const char *arg, …);
    • 功能:根据指定的程序路径执行一个新程序,并传递命令行参数作为参数。
    • 特点:需要给出新程序的完整路径,并以NULL作为参数列表的结束标志。
    • 示例:execl(“/bin/ls”, “ls”, “-l”, NULL);
  2. execlp():

    • 函数原型:int execlp(const char *file, const char *arg, …);
    • 功能:根据文件名称在路径中执行指定的可执行程序,并传递命令行参数作为参数。
    • 特点:使用名称搜索可执行文件,搜索路径由系统环境变量PATH控制。
    • 示例:execlp(“ls”, “ls”, “-l”, NULL);
  3. execle():

    • 函数原型:int execle(const char *path, const char *arg, …, char *const envp[]);
    • 功能:根据文件路径执行指定的可执行程序,并传递命令行参数和环境变量作为参数
    • 特点:需要指定可执行程序的完整路径,并且需要传递环境变量参数。
    • 示例:execle(“/bin/ls”, “ls”, “-l”, NULL, envp);
  4. execv
    • 函数原型:int execv(const char *path, char *const argv[]);
    • 特点:需要指定可执行程序的完整路径
    • 功能:根据文件路径执行指定的可执行程序,并传递命令行参数作为参数。
    • 示例:execv(“/bin/ls”, argv);
  5. execvp():

    • 函数原型:int execvp(const char *file, char *const argv[]);
    • 功能:根据文件名称在路径中执行指定的可执行程序,并传递命令行参数作为参数。
    • 特点:使用路径搜索可执行文件,搜索路径由系统环境变量PATH控制。
    • 示例:execvp(“ls”, argv);
  6. execve():

    • 函数原型:int execve(const char *filename, char *const argv[], char *const envp[]);
    • 功能:根据文件路径执行指定的可执行程序,并传递命令行参数和环境变量作为参数。
    • 特点:需要指定可执行程序的完整路径,并且需要手动传递环境变量参数。
    • 示例:execve(“/bin/ls”, argv, envp);
#include 
int main()
{
    char *const argv[] = {"ps", "-ef", NULL};
    char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
    execl("/bin/ps", "ps", "-ef", NULL);
    // 带p的,可以使用环境变量PATH,无需写全路径
    execlp("ps", "ps", "-ef", NULL);
    // 带e的,需要自己组装环境变量
    execle("ps", "ps", "-ef", NULL, envp);
    execv("/bin/ps", argv);
    // 带p的,可以使用环境变量PATH,无需写全路径
    execvp("ps", argv);
    // 带e的,需要自己组装环境变量
    execve("/bin/ps", argv, envp);
    exit(0);
}

你可能感兴趣的:(Linux,linux,服务器,运维)