进程控制

前一篇文章我们大致掌握了进程的相关概念,下面我们就需要来具体的控制进程了。
fork()函数创建子进程

  1. fork()函数的作用是在父进程中创建子进程。
  2. fork()函数体:pid_t fork(void)
  3. fork()函数的头文件为#include与#include
  4. 返回值:成功对父进程返回子进程id,对子进程返回0;失败: 失败:返回-1

getpid()函数与getppid()函数

  1. getpid()与getppid()的头文件与fork()函数相同
  2. getpid()的作用是获取当前进程的id
  3. getppid()的作用是子进程获取父进程的id

下面我们就来展示一段代码

#include
#include
#include
#include
int main()
{
    pid_t pid = fork();
    if(pid < 0){
        perror("fork error");
        return -1;
    }
    if(pid > 0){
        printf("This is father ID: %d\n", getpid());
    }
    if(pid == 0){
        printf("this is child ID: %d\n", getpid());
    }
    return 0;
}

深入理解fork()函数

  1. fork()函数的内层机制是什么?我们都知道进程的地址空间从上到下大致分别为栈区,共享区,堆区,静态数据区,代码段。那么子进程怎样从父进程中分离出来,而不破坏父进程呢,现在的答案是明确的引用计数。子进程一开始被复制出来就继承了父进程的内核空间,但是子进程并不会开辟自己的空间。如果我们改变了子进程的栈区,或者其它区域,那么子进程就会独立出自己的栈区,或者其它区域。
  2. 在此之上我们还有必要了解一下vfork()函数,它也是创建子进程的一个函数,但是这个函数已经快被淘汰了,因为他的机制是阻塞等待,子进程与父进程同用一块进程地址空间,为了不破坏父进程的程序逻辑,父进程必须等待子进程完毕之后才能继续运行。

进程等待

  1. 什么是进程等待?进程等待是为了让父进程获取子进程的推出状态
  2. 为什么需要进程等待?进程等待是为了避免产生僵尸进程

那么如何进行进程等待

  1. wait()函数
  2. waitpid()函数

wait()函数

  1. wait()函数作用:等待任意一个子进程退出,若没有子进程退出,则一直阻塞等待
  2. wait()函数的头文件为#include与#include
  3. 函数体:pid_t wait(int* status)
  4. 参数:输出型参数,获取子进程退出状态,不关心可以设置为NULL,我们后面详细说明这个参数
  5. 返回值:成功返回等待进程pid,失败返回-1

演示一段wait函数的等待

#include
#include
#include
#include
int main()
{
  pid_t pid = fork();
  if(pid < 0){
    perror("fork error");
  }
  if(pid == 0){
    printf("this is child id:%d\n", getpid());
  }
  if(pid > 0){
    printf("this is father id:%d\n", getpid());
    if(wait(NULL) < 0){
      if(errno == ECHILD){
        perror("have not child process!\n");
      }
      else{
        perror("wait error");
      }
    }
  }
  while(1){
    printf("%d\n", getpid());
    sleep(1);
  }
  return 0;
}

这里父进程阻塞在wait()函数处,直到子进程退出父进程才会运行
进程控制_第1张图片
waitpid()函数

  1. waitpid()函数作用:(可以设置为非阻塞式)可以等待指定子进程退出,也可以等待任意一个子进程退出,默认阻塞。
  2. 头文件:#include与#include
  3. 参数与返回值:在代码段详解
  4. 阻塞:为了完成一个功能发起一个函数调用,如果没有完成这个功能则一直挂起等待功能完成才返回
  5. 非阻塞:为了完成一个功能发起一个函数调用,如果现在不具备完成的条件, 则立即返回而不等待

子进程进程状态获取

  1. 退出状态用4个字节来存储
  2. 在wait的参数中存储了子进程的退出原因以及退出码而参数中只用了低16位两个字节用于存储这些信息。在这低16位中:高8位存储的是退出码,进程运行完毕时才会有;低7位存储进程引起异常退出信号值,只有进程异常退出才会有(一般判断进程是否正常退出,正常退出则低7位为0)。第8位存储core dump(核心转储)标志。
  3. statu >> 8 获取退出码
  4. statu & 0x7f判断进程是否正常退出
  5. 调用WTERMSIG(wstatus)返回一个布尔量来判断子进程是否正常退出
  6. 调用WIFEXITED(wstatus)返回子进程退出异常信号,若正常退出则此值为0

下面演示一段waitpid()函数与获取子进程状态代码

#include
#include
#include
#include
#include
int main()
{
        int pid = -1;
        pid = fork();
        if(pid < 0)
        {
               exit(-1);
        }
        else if(pid == 0)
        {
               sleep(20);
               exit(12);
        }
        //pid_t waitpid(pid_t pid, int *wstatus, int options);
        //  功能:默认等待子进程的退出
        //  pid:
        //    -1  等待任意子进程
        //    >0  等待进程id==pid的子进程
        //  wstatus:
        //    用于获取子进程退出状态码
        //  options:选项参数
        //    WNOHANG:如果没有子进程退出,则立即返回报错,如果有则回收资源
        //
        int wstatus;
        //waitpid函数第一个参数给-1,第三个参数默认0:默认阻塞等待任意一个子进程退出,获取状态吗,释放资源,效果等同于wait函数
        while(waitpid(pid, &wstatus, WNOHANG) == 0)
        {
               printf("have no child exit!!!\n");
               sleep(2);
        }
        if(WIFEXITED(wstatus))
        {
               //代表进程运行完毕退出
               printf("child process exit normal\n");
        }
        else if( WTERMSIG(wstatus))
        {
               //代表异常信号导致进程退出
               printf("cause child process exit signal:%d\n",WTERMSIG(wstatus));
        }
        if((wstatus & 0x7f) == 0)
        {
               printf("child process exit code:%d\n",wstatus >> 8);
        }
        else if((wstatus >> 8) == 0)
        {
               printf("cause child process exit signal:%d\n",wstatus & 0x7f);
        }
        while(1)
        {
               printf("hello world\n");
               sleep(1);
        }
        return 0;
}

进程控制_第2张图片

程序替换

  1. 创建一个子进程大多时候,并不希望子进程跟父进程做同样的事情,而是希望能够运行其他的程序代码,这个时候就用到了程序替换,程序替换只是替换了代码段,初始化了数据区域,因此程序替换并不会重新创建虚拟地址空间和页表,只是替换了其中的内容。替换后这个程序将从入口函数开始运行。
  2. 替换方法:exec函数族(头文件#include
    int execl(const char *path, const char *arg, … (char *) NULL );
    int execlp(const char *file, const char *arg, … (char *) NULL );
    int execle(const char *path, const char *arg, …, (char *) NULL, char * const envp[] );
    int execv(const char *path, char *const argv[]);
    int execvp(const char *file, char *const argv[]);
    int execvpe(const char *file, char *const argv[], char *const envp[]);
  3. execl与execv的区别:
    1.参数如何赋予(参数平铺,指针数组)
    execl与execlp的区别:
    1.execl:需要告诉操作系统这个程序文件的全部路径
    2.execlp:不需要告诉路径,只需要告诉文件名即可,操作系统会自动到PATH中的路径去找程序文件
    execl与execle:
    1.execl 继承于父进程的环境变量
    2.execle 需要由我们用户来组织环境变量
  4. 环境变量:这里不做详解,有兴趣的同学可以自学,这个很重要的!

下面展示一下程序替换代码,不做演示

#include
#include
#include
#include
#include
int main(int argc, char* argv[])
{
        int i = 0;
        for(i = 0; argv[i] != NULL; i++)
        {
               printf("argv[%d]:%s\n",i,argv[i]);
        }
        //exec函数族作用是程序替换,如果替换成功,代表运行的代码段已经不是以前的代码段,而是新程序,因此原来代码段以后的代码都不会运行,除非出错
        //execl与execv区别:参数如何赋予(平铺,指针数组)
        //
        //execl与execlp区别:
        //      execl需要告诉系统这个程序文件的全路径  /bin/ls
        //      execlp不需要告诉路径,只需要告诉文件名称即可,会自动到PATH中的路径下寻找  ls
        //
        //execl与execle区别:
        //      execl  继承于父进程的环境变量
        //      execle  自己由我们用户来组织环境变量
        if(execl("/bin/ls","ls","-l", NULL) < 0)
        {
               perror("ls error");
        }
        execlp("ls","ls","-l", NULL);
        //execlv需要我们自己组织环境变量,以一个指针数组来存储
        char* env[] = {"MYENV=nihaoa",NULL};
        execle("./test_exec","test_exec",NULL,env);
        char* param[] = {"ls","ls","-l", NULL};
        execv("ls",param);
        return 0;
}

进程终止

  1. exit()函数:在进程任意位置都可以退出进程,在进程推出之前会刷新缓冲区,关闭文件,等一系列操作
  2. _exit()函数:强行推出进程,什么操作也没有,直接释放全部资源
  3. return:只有在main函数中执行才会退出进程,在main中return跟调用exit函数效果一样

你可能感兴趣的:(进程控制)