【Linux】进程创建、进程终止、进程等待、进程程序替换

进程创建

在Linux 中 fork 函数是非常重要的,它从已经存在的进程中创建一个新的进程。

#include<unistd.h>
pid_t frok(void);
/*
创建成功:
    父进程:返回子进程 PID
    子进程:返回 0
创建失败:
    父进程:返回 -1,并设置 errno
*/

程序调用 fork 后,当控制转移到内核中的 fork 代码后,内核:

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

                    

写时拷贝:通常代码共享,父子不再写入时,数据也是共享的,当任意一方试图写入的时候,便以写诗拷贝的方式各自一份副本;

              

#include<sys/types.h>
#include<unistd.h>
pid_t vfork(void);

vfork():创建子进程,子进程和父进程公用同一块虚拟地址空间,为了防止调用栈混乱,因此阻塞父进程直到子进程退出调用exit() 退出 或者 进行程序替换;

fork()  和 vfork() 底层都是通过调用 clone() 实现进程的创建,但是在实现的时候,会稍有区别;

fork常规用法:父进程希望复制自己创建子进程从而同时执行不同的代码段;一个进程要执行不同的程序;
fork调用失败的原因:系统中的进程太多、实际用户的进程数超过了限制;

进程终止

进程退出的场景:运行完毕,结果正确;运行完毕,结果不正确;代码异常中止;

正常中止:从mian函数返回后;调用exit()函数;调用_exit();

exit 和 _exit 区别是 _exit 退出的时候不会刷新缓冲区,直接释放资源;但是,实际上exit 最后也会调用 _exit 函数,但是在调用之前,还做了其他的工作:

  • 执行用户通过 atecit 或 no_exit 定义的清理函数;
  • 关闭所有打开的流,所有的缓存数据均被写入;
  • 调用 _exit();

return 退出:return 是一种更常见的退出进程发的方法,执行 return n 等同于执行 exit(n),因为调用 main 函数的运行时函数会将 main 函数的返回值当作 exit 的参数;

进程等待

进程等待可以有效的避免出现程序出现僵尸进程!

wait 方法:

#include<sys/types>
#include<sys/wait.h>
pid_t wait(int* status);
返回值:成功返回被等待进程的pid,失败返回-1;
参数:输出型参数,获取子进程退出状态,不关心则可以设为NULL;

waitpid 方法:

#include<sys/types.h>
#include<sys/wait.h>
pid_t waitpid(pid_t pid,int* status,int options);
返回值:
    当正常返回的时候 waitpid 返回收集到的子进程的ID;
    如果设置了WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
    如果调用中出了错,则返回-1,这时errno会被设置成相应的值以示错误所在;
参数:
    pid:
        pid=-1,等待任意一个子进程;
        pid>0,等待其进程ID与PID相等的子进程
    status:
        WIFEXITED:若为正常中止子进程返回的转来,则为真;
        WEXITSTATUS:如果WIFEXITED非0,提取子进程推出码;
    options:
        WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0,不予等待,若正常结束,返回子进程的ID
  • 如果子进程已经退出,调用wait / waitpid 时,wait / waitpid 会立即返回,并且释放资源,获得子进程退出信息。
  • 如果在任意时刻调用 wait / waitpid ,子进程存在且正常运行,则进程可能阻塞。
  • 如果不存在该子进程,则立即返回报错;

进程替换

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

#include<unistd.h>

int execl(const char* path,const char* arg……);
int execlp(const char* flie,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[]);

这6个函数都以exec开头,统称为exec函数;

  • l(list):表示参数采用列表
  • v(vector):参数用数组
  • p(path):有 p 自动搜素环境变量 PATH
  • e(env):表示自己维护环境变量
#include<unistd.h>
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);

}

事实上,只有execve是真正的系统调用,其他五个函数最终都调用 execve。

                              

发布了79 篇原创文章 · 获赞 28 · 访问量 7751

猜你喜欢

转载自blog.csdn.net/weixin_43753894/article/details/100143594