moc 发表于 2018-11-6 15:02:45

011-exec、wait、守护进程

本帖最后由 moc 于 2018-11-6 15:02 编辑

1、exec函数族替换进程映像
*在进程的创建上Unix采用了一个独特的方法,它将进程创建与加载一个新进程映象分离。这样的好处是有更多的余地对两种操作进行管理。
*当我们创建了一个进程之后,通常将子进程替换成新的进程映象,这可以用exec系列的函数来进行。当然,exec系列的函数也可以将当前进程替换掉。
1. exec关联的函数族
头文件: <unistd.h>
作用: 用exec函数可以把当前进程替换为一个新进程。exec名下是由多个关联函数组成的一个完整系列。
原型:         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[]);

总结:
        函数名中: 带l ==> 代表可变参数列表, 带v ==> 代表可变参数列表以指针数组形式给定;
                       带p ==> 代表在path环境变量中搜索该可执行文件,带e ==> 代表在envp[]环境变量数组中寻找。
int main(void)
{
        //演示程序被完全替换
        //替换以后,pid不会发生变化
        //注意 printf后的\n不能忘记,不然main函数打印不出来
        printf(“main getpid: %d\n”, getpid());
       
        //execlp(“ls”, “ls”, “-lt”,NULL);
        int ret = execlp(“./testpid2”, NULL, NULL);
        if (ret == -1)
        {
                perror(“ERR: “);
        }
        printf(“fork after….\n”);
        return 0;
}
2、父进程wait和waitpid
Wait:
头文件:        <sys/types.h>和<sys/wait.h>
函数功能:当我们用fork启动一个进程时,子进程就有了自己的生命,并将独立地运行。有时,我们需要知道某个子进程是否已经结束了,我们可以通过wait安排父进程在子进程结束之后。
函数原型:pid_t wait(int *status)
函数参数:status:该参数可以获得你等待子进程的信息
返回值:成功等待子进程函数返回等待子进程的ID
① wait系统调用会使父进程暂停执行,直到它的一个子进程结束为止。
② 状态信息允许父进程判定子进程的退出状态,即从子进程的main函数返回的值或子进程中exit语句的退出码。
③ 如果status不是一个空指针,状态信息将被写入它指向的位置。
Wait获取status后检测处理

宏定义|描述
WIFEXITED(status)|如果子进程正常结束,返回一个非零值
=>WEXITSTATUS(status)|如果WIFEXITED非零,返回子进程退出码
WIFSIGNALED(status)        |子进程因为捕获信号而终止,返回非零值
=>WTERMSIG(status)|如果WIFSIGNALED非零,返回信号代码
WIFSTOPPED(status)|如果子进程被暂停,返回一个非零值
=>WSTOPSIG(status)|如果WIFSTOPPED非零,返回一个信号代码
Waitpid:
头文件:        <sys/types.h>和<sys/wait.h>
函数功能:用来等待某个特定进程的结束,获取这些信息,然后彻底清除掉这个进程。
函数原型:pid_t waitpid(pid_t pid, int *status, int options);
函数参数:status:如果不是空,会把状态信息写到它指向的位置
        options:允许改变waitpid的行为,最有用的一个选项是WNOHANG,它的作用是防止waitpid把调用者的执行挂起。       
        pid:① pid == -1 等待任一子进程。于是在这一功能方面waitpid与wait等效。
                ② pid > 0 等待其进程ID与PID相等的子进程。
                ③ pid == 0 等待其组ID等于调用进程的组ID的任一子进程。换句话说是与调用者进程同在一个组的进程。
                ④ pid < -1 等待指定进程组内的任意子进程。
返回值:如果成功返回等待子进程的ID,失败返回-1。
Wait和waitpid区别和联系:
        ① 在一个子进程终止前, wait 使其调用者阻塞,而waitpid 有一选择项,可使调用者不阻塞。
        ② waitpid并不等待第一个终止的子进程—它有若干个选择项,可以控制它所等待的特定进程。
        ③ 实际上wait函数是waitpid函数的一个特例。
避免僵尸进程:
        ① 调用wait或者waitpid函数查询子进程退出状态,此方法父进程会被挂起。
        ② 如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。

3、C库函数:system()
功能: system()函数调用“/bin/sh -c command”执行特定的命令,阻塞当前进程直到command命令执行完毕
原型: int system(const char *command);
返回值:对于fork失败,system()函数返回-1。 如果exec执行成功,也即command顺利执行完毕,则返回command通过exit或return返回的值。 (注意,command顺利执行不代表执行成功,比如command:"rm debuglog.txt",不管文件存不存在该command都顺利执行了) 如果exec执行失败,也即command没有顺利执行,比如被信号中断,或者command命令根本不存在,system()函数返回127. 如果command为NULL,则system()函数返回非0值,一般为1.
system函数执行过程:
        1. fork一个子进程;
        2. 在子进程中调用exec函数去执行command;
        3. 在父进程中调用wait去等待子进程结束。
system()函数的实现:int system(const char * cmdstring)
{
  pid_t pid;
  int status;
  if(cmdstring == NULL)
  {
    return (1); //如果cmdstring为空,返回非零值,一般为1
  }
  if((pid = fork())<0)
  {
    status = -1; //fork失败,返回-1
  }
  else if(pid == 0)
  {
    execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
    _exit(127); // exec执行失败返回127,注意exec只在失败时才返回现在的进程,成功的话现在的进程就不存在啦~~
  }
  else //父进程
  {
    while(waitpid(pid, &status, 0) < 0)
    {
      if(errno != EINTR)
      {
        status = -1; //如果waitpid被信号中断,则返回-1
        break;
      }
    }
  }
  return status; //如果waitpid成功,则返回子进程的返回状态
}
4、守护进程
Daemon(精灵)进程(守护进程): Linux中的后台服务进程,生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。
        守护进程是在后台运行不受控端控制的进程,通常情况下守护进程在系统启动时自动运行;
        守护进程的名称通常以d结尾,比如sshd、xinetd、crond等.
守护进程编程步骤:
① 创建子进程,父进程退出
        所有工作在子进程中进行
        形式上脱离了控制终端
② 在子进程中创建新会话
        setsid()函数
        使子进程完全独立出来,脱离控制
③ 改变当前目录为根目录
        chdir()函数
        防止占用可卸载的文件系统
        也可以换成其它路径
④ 重设文件权限掩码
        umask()函数
        防止继承的文件创建屏蔽字拒绝某些权限
        增加守护进程灵活性
⑤ 关闭文件描述符
        继承的打开文件不会用到,浪费系统资源,无法卸载
        将标准输入、标准输出、标准错误重定向到/dev/null
⑥ 开始执行守护进程核心工作
⑦守护进程退出处理
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
void daemonize(void)
{
        pid_t pid;
        // 成为一个新会话的首进程,失去控制终端
        if ((pid = fork()) < 0) {
                perror("fork");
                exit(1);
        }
        else if (pid != 0) /* parent */
                exit(0);

        setsid();
        // 改变当前工作目录到/目录下.
        if (chdir("/") < 0) {
                perror("chdir");
                exit(1);
        }

        /* 设置umask为0 */
        umask(0);

        // 重定向0,1,2文件描述符到/dev/null,因为已经失去控制终端,再操作0,1,2没有意义.
        close(0);
        open("/dev/null", O_RDWR);
        dup2(0, 1);
        dup2(0, 2);
}
int main(void)
{
        daemonize();
        while(1); /* 在此循环中可以实现守护进程的核心工作*/
}运行这个程序,它变成一个守护进程,不再和当前终端关联。用ps命令看不到,必须运行带x参数的ps命令才能看到。另外还可以看到,用户关闭终端窗口或注销也不会影响守护进程的运行。
术语:
会话期:是一个或者多个进程组的集合,通常一个会话期开始与用户登录,终止于用户退出。在此期间,该用户运行的所有进程都属于这个会话期。
进程组: 是一个或多个进程的集合。通常,它们与同一作业相关联,可以接收来自同一终端的各种信号。每个进程组有一个唯一的进程组ID。进程组ID类似于进程ID——它是一个正整数,并可存放在pid_t数据类型中。函数getpgrp返回调用进程的进程组ID。
页: [1]
查看完整版本: 011-exec、wait、守护进程