多进程

前言

并发是指在操作系统中,同一时间段内有多个进程都处于已启动运行到运行完毕之间的状态,但在任意时刻点上仍只有一个进程在运行
单道程序设计是指所有进程排队执行,若A阻塞则B只能等待
多道程序设计是指在计算机内存中同时存在几道相互独立的程序,它们在管理程序控制之下,相互穿插运行,多道程序设计的实现依赖于硬件时钟信号,即时钟中断,时钟中断有硬件基础作为保障,对进程而言不可抗拒,操作系统中的中断处理函数来负责调度程序执行。
内核吧进程列表存放在叫做任务队列的双向循环链表中,链表中的每一项都是类型为task_struct的结构,该结构被称为进程描述符,其定义在<linux/sched.h>中。

struct task_struct{
    pid_t pid;
    unsigned long state;
    进程切换时需要保存和恢复的CPU寄存器;
    描述虚拟地址空间的信息;
    描述控制终端的信息;
    当前工作目录;
    umask掩码;
    文件描述符表;
    信号相关的信息;
    用户id和组id;
    会话;
    进程可使用的资源上限;
    ...
}

fork()函数

函数解析
#include<unistd.h>

pid_t fork(void);

    DESCRIPTION
       fork()  creates  a new process by duplicating the calling process.  The
       new process is referred to as the child process.  The  calling  process
       is referred to as the parent process
    RETURN VALUE
       On success, the PID of the child process is returned in the parent, and
       0 is returned in the child.  On failure, -1 is returned in the  parent,
       no child process is created, and errno is set appropriately.
    Notice:
        fork()创建子进程后,遵循读时共享写时复制的原则;
        fork()结束后:
            父子进程相同的内容包括:全局变量、.data、.text、栈、堆、环境变量、用户ID、宿主目录、进程工作目录、信号处理方式...(0-3G内存空间)
            父子进程不同之处在于:进程ID、fork()返回值、父进程ID、进程运行时间、定时器、未决信号集
        fork()系统调用从内核返回两次,一次回到父进程,另一次回到新产生的子进程,父子进程执行的先后顺序取决于内核所使用的调度算法,理论上是随机的
循环创建(2^n)-1个进程
    pid_t pid;
    for(int i = 0; i < n; i++)
    {
        pid = fork();
        if(-1 == pid)
        {
            perror("fork error");
            exit(1);
        }
        else if(0 == pid)
        {
            printf("this is child process, pid = %u, ppid = %u\n", getpid(),getppid());
        }
        else
        {
            sleep(1);
        }
    }

2^n-1.PNG

循环创建n个进程
    pid_t pid;
    for(int i = 0; i < 5; i++)
    {
            pid = fork();
            if(-1 == pid)
            {
                perror("error occurs when fork");
                exit(1);
            }
            else if(0 == pid)
            {
                printf("this is the %d child process, pid = %u, ppid = %u\n",i, getpid(), getppid());
                break;
            }
            else
            {
                sleep(1);
            }
    }
gdb调试跟踪父子进程
    set follow-fork-mode parent
    set follow-fork-mode child

exec函数族

函数解析
    #include <unistd.h>

    extern char **environ;  //环境变量表,用于execle()及execvpe()

    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[]);

    DESCRIPTION
       The const char *arg and subsequent ellipses in the execl(), execlp(), and execle() functions can be thought of as
       arg0, arg1, ..., argn.  Together they describe a list of one or more pointers  to  null-terminated  strings  that
       represent  the  argument list available to the executed program.  The first argument, by convention, should point
       to the filename associated with the file being executed.  The list of arguments must  be  terminated  by  a  null
       pointer, and, since these are variadic functions, this pointer must be cast (char *) NULL.
       The  execv(), execvp(), and execvpe() functions provide an array of pointers to null-terminated strings that rep‐
       resent the argument list available to the new program.  The first argument, by convention, should  point  to  the
       filename associated with the file being executed.  The array of pointers must be terminated by a null pointer.

    RETURN VALUE
       The  exec() functions return only if an error has occurred.  The return value is -1, and errno is set to indicate
       the error.
    Notice:
        exec()函数族在fork()完成后可以创建新的地址空间,并将新的程序载入其中,当进程调用一种exec()函数时该进程的用户空间代码
        和数据将完全被新程序替换,从新程序的启动例程开始执行,调用exec()函数并不产生新进程

示例:

    pid = fork();
    if(-1 == pid)
    {
        perror("error occurs when fork");
        exit(1);
    }
    else if(0 == pid)
    {
        execlp("ls","ls -l -a","-l","-a",NULL);
        execl("/bin/ls","ls -l -a","-l","-a",NULL);
        /*
        char* argv[] = {"ls","ls -l -a","-l","-a",NULL};
        execv("/bin/ls",argv);
        */
    }
    else
    {
        printf("this is the parent thread");
        sleep(1);
    }

wait()函数族

在进行wait()函数族之前,先区分两个概念:僵尸进程和孤儿进程
僵尸进程:进程终止,父进程尚未及时回收,子进程残留资源(PCB)存放于内核中,成为僵尸进程。
孤儿进程:父进程先于子进程结束,子进程变为孤儿进程,之后孤儿进程的父进程编程init进程,这一过程成为init进程领养孤儿进程

函数解析
    #include <sys/types.h>
    #include <sys/wait.h>

    pid_t wait(int *status);/*1.阻塞等待子进程退出 2.回收子进程残留资源 3.获取子进程退出状态*/
    pid_t waitpid(pid_t pid, int *status, int options);
    int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);/* This function 
    is the glibc and POSIX interface; see NOTES for information on the raw system call. */

    DESCRIPTION
    The wait() system call suspends execution of the calling process until one of its children terminates.  The  call
    wait(&status) is equivalent to:
                    waitpid(-1, &status, 0);
    The  waitpid() system call suspends execution of the calling process until a child specified by pid argument has changed
    state. By default, waitpid() waits only for terminated children, but this behavior is modifiable via the options argument, 
    as described below.

    The value of pid can be:
    <-1   meaning wait for any child process whose process group ID is equal to the absolute value of pid.
    -1    meaning wait for any child process.
    0     meaning wait for any child process whose process group ID is equal to that of the calling process.
    >0    meaning wait for the child whose process ID is equal to the value of pid.

    The value of options is an OR of zero or more of the following constants:
    WNOHANG     return immediately if no child has exited.
    WUNTRACED   also  return if a child has stopped (but not traced via ptrace(2)).
                Status for traced children which have stopped is provided even if this option is not specified.
    WCONTINUED (since Linux 2.6.10)
                also return if a stopped child has been resumed by delivery of SIGCONT.

    If status is not NULL, wait() and waitpid() store status information in the int to which it points.  This integer
    can be inspected with the following macros (which take the integer itself as an argument, not a pointer to it, as
    is done in wait() and waitpid()!):
    WIFEXITED(status)
            returns true if the child terminated normally, that is, by calling exit(3) or _exit(2),  or  by  returning
            from main().
    WEXITSTATUS(status)
            returns  the  exit status of the child.  This consists of the least significant 8 bits of the status argu‐
            ment that the child specified in a call to exit(3) or _exit(2) or as the argument for a  return  statement
            in main().  This macro should be employed only if WIFEXITED returned true.
    WIFSIGNALED(status)
            returns true if the child process was terminated by a signal.
    WTERMSIG(status)
            returns  the  number  of  the  signal  that  caused  the child process to terminate.  This macro should be
            employed only if WIFSIGNALED returned true.
    WIFSTOPPED(status)
            returns true if the child process was stopped by delivery of a signal; this is possible only if  the  call
            was done using WUNTRACED or when the child is being traced (see ptrace(2)).
    WSTOPSIG(status)
            returns  the  number  of the signal which caused the child to stop.  This macro should be employed only if
            WIFSTOPPED returned true.

    RETURN VALUE
        wait(): on success, returns the process ID of the terminated child; on error, -1 is returned.
        waitpid():  on success, returns the process ID of the child whose state has changed; if WNOHANG was specified and
        one or more child(ren) specified by pid exist, but have not yet changed state, then 0 is returned.  On error,  -1
        is returned.

进程组

进程组也称为作业,BSD于1980年前后向UNIX中增加的一个特性,代表一个或多个进程的集合,每个进程都会属于一个进程组,操作系统设计使用进程组的特性,是为了简化对多个进程的管理。
当父进程创建一个子进程的时候,默认子进程与父进程属于同一个进程组,进程组ID等于第一个进程ID(组长进程),所以,组长进程标识:其进程组ID等于其进程ID。
组长进程可以创建一个进程组,创建该进程组中的进程,然后终止,只要进程组中有一个进程存在,进程组就存在,与其组长进程是否终止无关。
组长进程的生命周期:从进程组创建到最后一个进程终止。
一个进程可以为自己或子进程设置进程组ID。

进程组操作函数
getpgrp()
#include <unistd.h>
pid_t getpgrp(void);                 /* POSIX.1 version */
pid_t getpgrp(pid_t pid);            /* BSD version *

RETURN VALUE
getpgid(), and the BSD-specific getpgrp() return a process group on success. On error, -1 
is returned, and errno is set appropriately.

获取当前进程的进程组ID

setpgrp()
#include <unistd.h>
int setpgrp(void);                   /* System V version */
int setpgrp(pid_t pid, pid_t pgid);  /* BSD version */

RETURN VALUE
On success, setpgid() and setpgrp() return zero. On error, -1 is returned, and errno is set 
appropriately.

设置当前进程的进程组ID

getpgid()
#include <unistd.h>
pid_t getpgid(pid_t pid);

RETURN VALUE
getpgid(), and the BSD-specific getpgrp() return a process group on success. On error, -1 
is returned, and errno is set appropriately.

获取指定进程的进程组ID,如果pid=0,那么该函数作用和getpgrp()一样。

setpgid()
#include <unistd.h>
int setpgid(pid_t pid, pid_t pgid);

RETURN VALUE
On success, setpgid() and setpgrp() return zero. On error, -1 is returned, and errno is set 
appropriately.

NOTICE
权级问题:非root进程只能改变自己创建的子进程,或有权限操作的进程

改变进程默认所属的进程组。通常可用来加入一个进程组或创建一个新进程组。

进程组操作函数示例
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>

int main(void)
{
    pid_t pid;
    if((pid = fork()) < 0)
    {
        perror("fork fail");
        exit(1);
    }
    else if(pid == 0)
    {
        printf("This is child process, pid == %d, child group ID == %d",getpid(),getpgid(0));
        sleep(3);
        printf("This is child process, pid == %d, child group ID == %d",getpid(),getpgid(0));
        exit(0);
    }
    else
    {
        sleep(1);
        setpgid(pid,pid);   //为子进程设立单独的进程组
        sleep(5);
        printf("This parent process, process ID = %d, group ID = %d", getpid(), getpgid(0));
        sleep(7);
        setpgid(getpid(),getppid());
        printf("This parent process, process ID = %d, group ID = %d", getpid(), getpgid(0));
    }
}

会话

会话代表一个或多个进程组的集合
创建一个会话需注意以下6点:

  1. 调用进程不能是进程组组长,该进程会变成新会话首进程(session header);
  2. 该进程成为一个新进程组的组长进程;
  3. 需要有root权限(Ubuntu不需要);
  4. 新会话丢弃原有会话控制终端(shell, keyboard…),该会话没有控制终端;
  5. 该调用进程是组长进程,则出错返回;
  6. 建立新会话时应先调用fork,终止父进程,子进程调用setsid。
会话操作函数
#include <unistd.h>
pid_t setsid(void);

DESCRIPTION
setsid() creates a new session if the calling process is not a process group leader. The 
calling process is the leader of the new session(i.e., its session ID is made the same as 
its process ID). The calling process also becomes the process group leader of a new process 
group in the session (i.e., its process group ID is made the same as its process ID).

The calling process will be the only process in the new process group and in the new session. 
The new session has no controlling terminal.

RETURN VALUE
On success, the (new) session ID of the calling process is returned. On error, (pid_t) -1 
is returned, and errnois set to indicate the error.
#include <unistd.h>
pid_t getsid(pid_t pid);

DESCRIPTION
getsid(0) returns the session ID of the calling process. getsid(p) returns the session ID 
of the process with process ID p. (The session ID of a process is the process group ID of 
the session leader.)

RETURN VALUE
On success, a session ID is returned. On error, (pid_t) -1 will be returned, and errno is 
set appropriately.

示例:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>

int main(void)
{
    pid_t pid;
    if((pid = fork()) < 0)
    {
        perror("fork fail");
        exit(1);
    }
    else if(pid == 0)
    {
        printf("This is child process, pid == %d, child process group ID == %d, child process session ID == %d\n",getpid(),getpgid(0), getsid(0));
        sleep(3);
        setsid();   //子进程非组长进程,故可使其成为新会话首进程,且成为组长进程
        printf("This is child process, pid == %d, child process group ID == %d, child process session ID == %d\n",getpid(),getpgid(0), getsid(0));
        exit(0);
    }
    return 0;
}

守护进程

守护进程又称精灵(daemon)进程,是Linux中的后台服务进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件,一般采用d结尾。
Linux后台的一些系统服务进程,没有控制终端,不能和用户直接交互,不受用户登录及注销的影响,一直在运行着,他们都是守护进程,创建守护进程的第一步是调用setsid()创建一个新的session,并成为session header。

创建守护进程模型
  1. 创建子进程,父进程退出,所有工作在子进程中进行,形式上脱离了控制终端;
  2. 在子进程中创建新会话,使子进程完全独立出来,脱离控制;
  3. 使用chdir(),改变当前目录为根目录,防止占用可卸载的文件系统,也可切换到其他目录 ;
  4. 使用umask(),重设文件权限掩码,防止继承的文件创建屏蔽字拒绝某些权限;
  5. 使用dup2(),将STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO重定向到/dev/null;
  6. 守护进程主逻辑。
  7. 特殊的退出逻辑
    示例
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/stat.h>

int main(void)
{
    pid_t pid,sid;
    int fd;

    pid = fork();
    if(pid<0)
    {
        perror("fork error");
        exit(1);
    }
    if(pid>0)
    {
        return 0;
    }
    else
    {
        sid = setsid();

        if(chdir("/home/daemon")<0)
        {
            perror("change directory fail");
            exit(1);
        }
        umask(0002);

        close(0);
        fd = open("/dev/null"); //此时文件描述符中最小的为0,即fd=0
        dup2(fd,STDOUT_FILENO);
        dup2(fd,STDERR_FILENO);

        {
            //主逻辑
        }

        {
            //退出逻辑
            signal();
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值