进程

162 阅读9分钟

进程系统调用

  • 创建子进程
    • pid_t fork(void)
  • 获取进程号
    • pid_t getpid(void)
    • pid_t getppid(void)
    • pid_t getpgid(pid_t pid)
  • 调用进程内部执行一个可执行文件
    • int execl(const char *path, const char *arg, .../* (char *) NULL */)
    • int execlp(const char *file, const char *arg, ... /* (char *) NULL */)
  • 进程退出
    • void exit(int status)
    • void _exit(int status)
  • 进程回收
    • pid_t wait(int *wstatus)
      • int *wstatus:进程退出时的状态信息,传入的是一个int类型的地址,传出参数。
    • pid_t waitpid(pid_t pid, int *wstatus, int options)

进程通信方式-IPC

image.png

管道

  • 分类:

    • 匿名管道
      • 有文件实体
      • 匿名管道只能在具有公共祖先的进程(父进程与子进程,或者两个兄弟进程,具有亲缘关系)之间使用。
    • 有名管道:
      • 有文件实体, 但不存储数据
      • 可以在有关系,或无关的进程之间使用
  • 本质:内核内存中维护的缓冲器

  • 通信方式:半双工通信

  • 数据结构:循环队列

管道系统调用

  • 创建匿名管道:
    • int pipe(int pipefd[2])
      • int pipefd[2] :这个数组是一个传出参数。

        • pipefd[0] 对应的是管道的读端

        • pipefd[1] 对应的是管道的写端

  • 查看管道缓冲大小函数:
    • long fpathconf(int fd, int name)
  • 创建有名管道:
    • int mkfifo(const char *pathname, mode_t mode)
      • pathname: 管道名称的路径
      • mode: 文件的权限 和 open 的 mode 是一样的,是一个八进制的数

信号

基本概念:

  • 信号:是事件发生时对进程的通知机制,有时也称之为软件中断。信号 可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。

  • 信号集:多个信号可使用一个称之为信号集的数据结构来表示,其系统数据类型为 sigset_t

    • 未决信号集:从信号的产生到信号被处理前的这一段时间。
    • 阻塞信号集:阻止信号被处理,但不是阻止信号产生。
      • 操作系统不允许我们直接对这两个信号集进行位操作。而需自定义另外一个集合,借助信号集操作函数来对 PCB 中的这两个信号集进行修改。
      • 若想处理未决信号集,需要先查看阻塞信号集对应信号是否被阻塞:
        • 如果没有阻塞,这个信号就被处理
        • 如果阻塞了,这个信号就继续处于未决状态,直到阻塞解除,这个信号就被处理

重要的信号:

  • SIGINT

    • 编号:2
    • 当用户按下了<Ctrl+C>组合键时,用户终端向正 在运行中的由该终端启动的程序发出此信号
    • 默认动作:终止进程
  • SIGQUIT

    • 编号:3
    • 用户按下<Ctrl+>组合键时产生该信号,用户终 端向正在运行中的由该终端启动的程序发出些信号
    • 默认动作:终止进程
  • SIGKILL

    • 编号:9
    • 无条件终止进程,可以杀死任何进程
    • 默认动作:该信号不能被忽略,处理和阻塞
  • SIGSEGV

    • 编号:11
    • 指示进程进行了无效内存访问(段错误)
    • 默认动作:终止进程并产生core文件
  • SIGPIPE

    • 编号:13
    • Broken pipe向一个没有读端的管道写数据
    • 默认动作:终止进程
  • SIGCHLD

    • 编号:17
    • 子进程结束时,父进程会收到这个信号
    • 默认动作:忽略这个信号
  • SIGCONT

    • 编号:18
    • 如果进程已停止,则使其继续运行
    • 默认动作:继续/忽略
  • SIGSTOP

    • 编号:19
    • 停止进程的执行。信号不能被忽略,处理和阻塞
    • 默认动作:终止进程

信号系统调用

  • 给任何的进程或者进程组pid, 发送任何的信号 sig

    • int kill(pid_t pid, int sig);
  • 给当前进程发送信号

    • int raise(int sig);
  • 发送SIGABRT信号给当前的进程,杀死当前进程

    • void abort(void);
  • 设置定时器

    • unsigned int alarm(unsigned int seconds);
  • 设置定时器

    • int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value)
      • int which : 定时器以什么时间计时

        • ITIMER_REAL: 真实时间,时间到达,发送 SIGALRM 常用

        • ITIMER_VIRTUAL: 用户时间,时间到达,发送 SIGVTALRM

        • ITIMER_PROF: 以该进程在用户态和内核态下所消耗的时间来计算,时间到达,发送 SIGPROF

        • 真实时间 = 内核时间 + 用户时间 + 消耗的时间

      • const struct itimerval *new_value: 设置定时器的属性

        struct itimerval { // 定时器的结构体
        
            struct timeval it_interval; // 间隔时间:每隔多久定时
        
            struct timeval it_value; // 程序开始后,延迟多长时间执行定时器
        
        };
        
        
        struct timeval { // 时间的结构体
        
            time_t tv_sec; // 秒数
        
            suseconds_t tv_usec; // 微秒
        
        };
        

        举例:过10秒后,每隔2秒定时一次:10s:it_value, 2s:it_interval

      • struct itimerval *old_value :记录上一次的定时的时间参数,一般不使用,指定NULL

  • 信号捕捉

    • sighandler_t signal(int signum, sighandler_t handler)
      • sighandler_t handler: 捕捉到信号要如何处理

        • SIG_IGN : 忽略信号

        • SIG_DFL : 使用信号默认的行为,相当于没有捕捉。

        • 回调函数 : 这个函数是内核调用,程序员只负责写,捕捉到信号后如何去处理信号。

          • void (*sighandler_t)(int)函数指针,int类型的参数表示捕捉到的信号的值。

          • 需要程序员实现,提前准备好的,函数的类型根据实际需求,看函数指针的定义

          • 不是程序员调用,而是当信号产生,由内核调用

          • 函数指针是实现回调的手段,函数实现之后,将函数名放到函数指针的位置就可以了。

  • 将信号集设置不阻塞

    • int sigemptyset(sigset_t *set)
  • 将信号集设置阻塞

    • int sigfillset(sigset_t *set)
  • 将信号集某个信号设置阻塞

    • int sigaddset(sigset_t *set, int signum)
  • 将信号集某个信号设置不阻塞

    • int sigdelset(sigset_t *set, int signum)
  • 判断信号集某个信号是否阻塞

    • int sigismember(const sigset_t *set, int signum)
  • 将自定义信号集中的数据设置到内核中

    • int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
  • 获取内核中的未决信号集

    • int sigpending(sigset_t *set)
  • 信号捕捉

    • int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
      • int signum : 需要捕捉的信号的编号或者宏值(推荐)

      • const struct sigaction *act :捕捉到信号之后的处理动作

        struct sigaction {
        
            // 函数指针,指向的函数就是信号捕捉到之后的处理函数
        
            void (*sa_handler)(int);
        
            // 不常用
        
            void (*sa_sigaction)(int, siginfo_t *, void *);
        
            // 临时阻塞信号集,在信号捕捉函数执行过程中,临时阻塞某些信号。
        
            sigset_t sa_mask;
        
            // 使用哪一个信号处理对捕捉到的信号进行处理
        
            // 这个值可以是0,表示使用sa_handler,也可以是SA_SIGINFO表示使用sa_sigaction
        
            int sa_flags;
        
            // 被废弃掉了
        
            void (*sa_restorer)(void);
        
        };
        
      • struct sigaction *oldact : 上一次对信号捕捉相关的设置,一般不使用,传递NULL

内存映射

  • 内存映射(Memory-mapped I/O):将磁盘文件的数据映射到内存,用户通过修改内存就能修改磁盘文件。
  • 内存映射是进程通信(IPC)的一种方式,可以用于:
    • 有关系的父子进程
    • 无关的两个进程
  • 内存映射通信,是非阻塞的。

内存映射系统调用:

  • 将一个文件或者设备的数据映射到内存中:
    • void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
      • void *addr : NULL, 由内核指定

      • size_t length : 要映射的数据的长度,这个值不能为0。建议使用文件的长度。
        获取文件的长度:stat() lseek()

      • int prot : 对申请的内存映射区的操作权限

        • PROT_EXEC :可执行的权限

        • PROT_READ :读权限

        • PROT_WRITE :写权限

        • PROT_NONE :没有权限

          要操作映射内存,必须要有读的权限.PROT_READ、PROT_READ|PROT_WRITE

      • int flags :

        • MAP_SHARED : 映射区的数据会自动和磁盘文件进行同步,进程间通信,必须要设置这个选项

        • MAP_PRIVATE :不同步,内存映射区的数据改变了,对原来的文件不会修改,会重新创建一个新的文件。(copy on write)

      • int fd: 需要映射文件的文件描述符

        • 通过open得到,open的是一个磁盘文件

        • 注意:文件的大小不能为0,open指定的权限不能和prot参数有冲突。

          • prot: PROT_READ open:只读/读写

          • prot: PROT_READ | PROT_WRITE open:读写

      • off_t offset:偏移量,一般不用。必须指定的是4k的整数倍,0表示不偏移。

  • 释放内存映射:
    • int munmap(void *addr, size_t length);

守护进程

  • 进程组:是一组相关进程的集合,共享同一进程组标识符(PGID)
    • 进程组首进程:该进程是创建该组的进程,其进程 ID 为该进程组的 ID
    • 新进程会继承其父进程所属的进程组 ID
    • 生命周期:其开始时间为首进程创建组的时刻,结束时间为最后一个 成员进程退出组的时刻
  • 会话:是一组相关进程组的集合。
    • 会话首进程是创建该新会话的进程,其进程 ID 会成为会话 ID
    • 新进程会继承其父进程的会话 ID
  • 任一时刻,会话中的其中一个进程组会成为终端的前台进程组,其他进程组会成为 后台进程组。只有前台进程组中的进程才能从控制终端中读取输入。

系统调用

◼ 进程组:pid_t getpgrp(void);
◼ 进程组:pid_t getpgid(pid_t pid);
◼ 进程组:int setpgid(pid_t pid, pid_t pgid); 
◼ 会话:pid_t getsid(pid_t pid);
◼ 会话:pid_t setsid(void);

守护进程(Daemon 进程、精灵进程)

  • Linux 后台服务进程
  • 生命周期很长:守护进程会在系统启动的时候被创建并一直运行直至系统被关闭
  • 它在后台运行并且不拥有控制终端
  • Linux 的大多数服务器就是用守护进程实现的。
    • Internet 服务器 inetd
    • Web 服务器 httpd
    • ssh 协议 sshd

守护进程的创建步骤

  • 执行一个 fork(),之后父进程退出,子进程继续执行。
    • 确保子进程不会成为进程组的首进程。
  • 子进程调用 setsid() 开启一个新会话。
    • 目的:脱离控制终端。创建新会话没有控制终端。
  • 清除进程的 umask 以确保当守护进程创建文件和目录时拥有所需的权限。
    • 非必需。
  • 修改进程的当前工作目录,通常会改为根目录(/)。
    • 非必需。
  • 关闭守护进程从其父进程继承而来的所有打开着的文件描述符。
    • 非必需。
  • 在关闭了文件描述符0、1、2之后,守护进程通常会打开/dev/null 并使用dup2() 使所有这些描述符指向这个设备。
    • 非必需。
  • 核心业务逻辑