Linux进程间通信 - 信号

0. 前言

   进程是一个独立的资源管理单元,不同进程间的资源是独立的,不能在一个进程中访问另一个进程的用户空间和内存空间。但是,进程不是孤立的,不同进程之间需要信息的交互和状态的传递,因此需要进程间数据的传递、同步和异步的机制。

    当然,这些机制不能由哪一个进程进行直接管理,只能由操作系统来完成其管理和维护,Linux提供了大量的进程间通信机制,包括同一个主机下的不同进程和网络主机间的进程通信,如下图所示:
mark

  • 同主机间的信息交互
  • 无名管道
    特点:多用于亲缘关系进程间通信,方向为单向;为阻塞读写;通信进程双方退出后自动消失
    问题:多进程用同一管道通信容易造成交叉读写的问题
  • 有名管道
    FIFO(First In First Out),方向为单向(双向需两个FIFO),以磁盘文件的方式存在;通信双方一方不存在则阻塞
  • 消息队列
    可用于同主机任意多进程的通信,但其可存放的数据有限,应用于少量的数据传递
  • 共享内存
    可实现同主机任意进程间大量数据的通信,但多进程对共享内存的访问存在着竞争
  • 同主机进程间同步机制:信号量(Semaphore)
  • 同主机进程间异步机制:信号(Signal)
  • 网络主机间数据交互:Socket(套接字)

1. 概念

名词解释

  • ①. 发送信号:
    包括一个进程向自己发送信号、向其它进程发送信号、内核向用户进程发送信号
  • ②. 安装中断:
    当某个信号到来时,执行用户定制的中断服务程序,而非默认操作
  • ③. 递送信号:
    一个信号被操作系统发送到目标进程;所有信号的传递必须经过操作系统这一层
  • ④. 捕获信号:
    信号在目标进程中引起某段处理程序的执行
  • ⑤. 屏蔽信号:
    进程告诉操作系统,暂时不接受某些信号:屏蔽期间被屏蔽信号不会被进程捕获;当解除屏蔽后,在屏蔽期的发送的屏蔽信号将会被捕获到
  • ⑥. 忽略信号:
    目标进程不处理忽略信号,直接丢弃
  • ⑦. 未决信号:
    屏蔽期的被发送到目标进程的未被捕获的屏蔽信号
  • ⑧. 可靠与不可靠信号:

可靠信号:编号大于32的信号,屏蔽期收到多次同一信号,会记录次数,屏蔽结束后,捕获相同次数的信号;
不可靠信号:编号小于32的信号;同上,但是只捕获一次“未决信号”

信号定义:

/usr/include/bits/signum.h中,对信号宏进行了定义:

/* Signals.  */
#define SIGHUP      1   /* Hangup (POSIX).  */
#define SIGINT      2   /* Interrupt (ANSI).  */
#define SIGQUIT     3   /* Quit (POSIX).  */
#define SIGILL      4   /* Illegal instruction (ANSI).  */
#define SIGTRAP     5   /* Trace trap (POSIX).  */
#define SIGABRT     6   /* Abort (ANSI).  */
#define SIGIOT      6   /* IOT trap (4.2 BSD).  */
#define SIGBUS      7   /* BUS error (4.2 BSD).  */
#define SIGFPE      8   /* Floating-point exception (ANSI).  */
#define SIGKILL     9   /* Kill, unblockable (POSIX).  */
#define SIGUSR1     10  /* User-defined signal 1 (POSIX).  */
#define SIGSEGV     11  /* Segmentation violation (ANSI).  */
#define SIGUSR2     12  /* User-defined signal 2 (POSIX).  */
#define SIGPIPE     13  /* Broken pipe (POSIX).  */
#define SIGALRM     14  /* Alarm clock (POSIX).  */
#define SIGTERM     15  /* Termination (ANSI).  */
#define SIGSTKFLT   16  /* Stack fault.  */
#define SIGCLD      SIGCHLD /* Same as SIGCHLD (System V).  */
#define SIGCHLD     17  /* Child status has changed (POSIX).  */
#define SIGCONT     18  /* Continue (POSIX).  */
#define SIGSTOP     19  /* Stop, unblockable (POSIX).  */
#define SIGTSTP     20  /* Keyboard stop (POSIX).  */
#define SIGTTIN     21  /* Background read from tty (POSIX).  */
#define SIGTTOU     22  /* Background write to tty (POSIX).  */
#define SIGURG      23  /* Urgent condition on socket (4.2 BSD).  */
#define SIGXCPU     24  /* CPU limit exceeded (4.2 BSD).  */
#define SIGXFSZ     25  /* File size limit exceeded (4.2 BSD).  */
#define SIGVTALRM   26  /* Virtual alarm clock (4.2 BSD).  */
#define SIGPROF     27  /* Profiling alarm clock (4.2 BSD).  */
#define SIGWINCH    28  /* Window size change (4.3 BSD, Sun).  */
#define SIGPOLL     SIGIO   /* Pollable event occurred (System V).  */
#define SIGIO       29  /* I/O now possible (4.2 BSD).  */
#define SIGPWR      30  /* Power failure restart (System V).  */
#define SIGSYS      31  /* Bad system call.  */
#define SIGUNUSED   31

信号及其默认操作如下(信号都可以唤醒被中断的进程)(以字母排序):

Signal Signum Description Default action Comment
SIGABRT 6 异常终止 终止
SIGALRM 14 超时 终止
SIGBUS 7 硬件故障 终止
SIGCHLD 17 子进程状态改变 忽略 子进程退出时发送该信号给父进程
SIGCLD 16 栈错误 终止
SIGCONT 18 唤醒暂停进程继续工作 忽略/继续 与SIGSTOP配合使用
SIGFPE 8 算数异常 终止
SIGHUP 1 链接断开 终止
SIGILL 4 非法硬件指令 终止
SIGINT 2 终端中断符 终止
SIGIO 29 异步I/O 终止
SIGIOT 6 硬件故障 终止
SIGKILL 9 终止 终止 不可被屏蔽、安装
SIGPIPE 13 写至无读进程的管道 终止
SIGPOLL 29 可轮询事件 终止
SIGPROF 27 时间超时 终止
SIGPWR 30 电源失效/重启 忽略
SIGQUIT 3 终端退出符 终止
SIGSEGV 11 无效存储访问 终止
SIGSTKFLT 16 栈错误 终止
SIGSTOP 19 停止作业 暂停进程 不可被屏蔽、安装
SIGSYS 31 无效系统调用 终止
SIGTERM 15 终止进程 终止
SIGTRAP 5 硬件故障 终止
SIGTSTP 20 终端挂起 停止进程
SIGTTIN 21 后台从控制tty读作业 停止进程
SIGTTOU 22 后台向控制tty写作业 停止进程
SIGURG 23 紧急情况 忽略
SIGUSR1 10 用户自定义信号 终止
SIGUSR2 12 用户自定义信号 终止
SIGVTALRM 26 虚拟时间闹钟(setitimer) 终止
SIGWINCH 28 终端窗口大小改变 忽略
SIGXCPU 24 超过CPU限制(setrlimit) 终止
SIGXFSZ 25 超过文本长度限制(setrlimit) 终止

2. 信号生命周期

mark

1). 安装信号

当进程收到一个信号时,可采取三种处理方式:

  1. 忽略此信号:SIGKILL和SIGSTOP不可被忽略,其它均可
  2. 执行默认操作:如上表所示
  3. 用户自定义操作:要求必须首先安装该信号

安装信号的方式有两种:

  • signal():
  • sigaction():

①. signal()

  • 作用
    针对某个信号,安装用户自定义的操作函数

  • 头文件

      #include <signal.h>
    
  • 函数原型

      sighandler_t signal(int signum, sighandler_t handler)
    
  • 参数

signum: 信号的编号,如上表所示
handler: typedef void (*sighandler_t)(int), 即安装的中断处理函数
      可以设置为三种类型

宏参数 int 描述
SIG_ERR -1 返回错误
SIG_DFL 0 执行该信号的默认操作
SIG_IGN 1 忽略该信号
handler 用户自定义中断处理函数
  • 返回值
    成功:handler以前的值
    失败:SIG_ERR
  • 其它说明

②. sigaction()

  • 作用
    更强大的安装信号,检查和更改信号的处理操作

  • 头文件

        #include <signal.h>
    
  • 函数原型

        int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
    
  • 参数

signum: 信号的编号,如上表所示
act: 指定欲设置的信号处理方式
oldact: 存储执行此信号前针对此信号的安装信息

 struct sigaction {
     union {
         __sighandler_t _sa_handler;                             //SIG_DFL、SIG_IGN
         void (*_sa_sigaction)(int, struct siginfo *, void *);   //信号捕获函数
     } _u;
     sigset_t sa_mask;           //执行信号捕获函数时,添加到进程屏蔽信号集中的信号集
     unsigned long sa_flags;     //影响信号行为的特殊标志
     void (*sa_restorer)(void);
 };

 #define sa_handler  _u._sa_handler
 #define sa_sigaction    _u._sa_sigaction
  • 返回值
    成功:0
    失败:-1

2). 产生信号

信号的发送是从一个进程产生,经由OS,发送到另一个进程(或自身).
发送信号有三种方式:

  • kill(): 发送信号到一个指定进程
  • raise(): 自产生一个信号
  • alarm():

①. kill()

  • 作用
    传送一个信号到指定进程

  • 头文件

        #include <signal.h>
    
  • 函数原型

        int kill(pid_t pid, int signum)
    
  • 参数

pid: 目标进程的ID

PID Description
PID > 0 将信号发送到该PID的进程中
PID = 0 将信号发送到与当前进程同一进程组的所有进程
PID = -1 将信号发送给系统内的所有进程
PID < 0 将进程发送给进程组好PGID为PID绝对值的所有进程
signum: 发送的信号编号
  • 返回值
    成功:0
    失败:-1

②. raise()

  • 作用
    给当前进程发送信号,即唤醒进程

  • 头文件

        #include <signal.h>
    
  • 函数原型

      int raise(int signum)
    
  • 参数

signum: 发送的信号编号

  • 返回值
    成功:0
    失败:-1

③. alarm()

  • 作用
    产生一个定时信号SIGALRM(只产生一次,并非循环产生)

  • 头文件

        #include <unistd.h>
    
  • 函数原型

      int alarm(unsigned int seconds)
    
  • 参数

seconds: 延迟时间

  • 返回值
    成功:0
    失败:-1

  • 相似函数

      useconds_t ualarm(useconds_t usecs, useconds_t interval)    //usecs后执行,后每隔interval循环执行一次 
    

3). 信号集

有时候,往往一个进程会忽略或者屏蔽掉多个信号,这是如果我们一个个的输入会特别麻烦,因此,就有了信号集的概念!
信号集的数据结构如下:

 File: /usr/include/bits/sigset.h
 
  # define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
  typedef struct
    {
      unsigned long int __val[_SIGSET_NWORDS];
    } __sigset_t;

Linux提供了大量的信号集操作函数,具体如下:

①. 设置进程屏蔽信号集

  • sigprocmask
  • 作用
    设置当前进程屏蔽的信号集合

  • 头文件

         #include <signal.h>
    
  • 函数原型

      int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
    
  • 参数

how:

How Description
SIG_BLOCK 将set信号集添加到当前进程的屏蔽信号集中
SIG_UNBLOCK 将set信号集从到当前进程的屏蔽信号集中删除
SIG_SETMASK 设置当前屏蔽的信号集为set
set: 设置的信号集;若为NULL,则相当于查询当前信号集
oldset: 保存执行该函数前的屏蔽信号集
  • 返回值
    成功:0
    失败:-1

②. 获取当前未决信号

  • sigpending:
  • 作用
    获取当前进程的未决信号(即被屏蔽的尚未执行的信号)

  • 头文件

      #include <signal.h>
    
  • 函数原型

         int sigpending(sigset_t *set)
    
  • 参数

set: 存储未决信号
返回值
成功:0
失败:-1

③. 信号集操作

包括清空、填充信号集/添加、删除信号/判断信号集的成员

  • 头文件

      #include <signal.h>
    
  • 函数原型

函数 原型 描述
sigemptyset int sigemptyset(sigset_t *set) 清空信号集
sigfillset int sigfillset(sigset_t *set) 置位信号集
sigaddset int sigaddset(sigset_t *set, int signum) 添加信号到信号集
sigdelset int sigdelset(sigset_t *set, int signum) 删除某个信号
sigismember int sigismember(const sigset_t *set, int signum) 判断信号是否为信号集中的成员
sigisemptyset int sigisemptyset(sigset_t *set) 判断信号集是否为空
  • 返回值
    成功: 0
    失败:-1

4). 等待信号

进程可以处于挂起状态而等待某些特定的信号,函数如下:

  • 头文件

      #include <signal.h>
    
  • 函数原型

        int pause(void)         //暂停程序执行,等待任意信号的到来恢复工作
        int sigsuspend(const sigset_t *mask)    //暂停程序,mask为屏蔽信号集,等待除此之外的任意信号的到来;当有信号到来后,进程屏蔽集将恢复为原来的信号集(即执行该函数之前的信号集)    
    
  • 返回值
    总是返回 -1

5. 示例代码

有两个进程(非亲缘),分别为clientserver:

  • client:
  1. 通过task_name(即“server”)获取其PID;
  2. 通过pid,向“server”持续发送10次信号 “SIGUSR1”
  3. 结束自身前,发送“SIGUSR2”信号给“server”,结束“server”
  4. 进程退出
  • server:
  1. 安装监听信号:SIGUSR1和SIGUSR2
  2. 当收到SIGUSR1时,打印信息
  3. 当收到SIGUSR2时,结束本进程

本程序包含四个文件:

  • server.c: 服务端
  • client.c: 客户端
  • task.c: 用于获取PID或task_name
  • task.h:

1). server.c:


#include "./task.h"

void handler(int num)
{
    static i=0;
    i++;
    printf("The %d times receive signal!\n", i);
}

int main(int argc, char* argv[])
{
    int pid=-1;
    int ret;
    
    signal(SIGUSR1, handler);
    signal(SIGUSR2, NULL);
    
    while(1);
    return 0;
}

2). client.c:

#include "./task.h"

#define TAR_TASK "server"

int main(int argc, char* argv[])
{
    int pid=-1;
    int ret;
    

    //get the server task pid
    ret = getPidByName(&pid, TAR_TASK);
    if((-1 == ret) || (-1 == pid))
    {
        printf("getPidByName was failed!\n");
        exit(EXIT_FAILURE);
    }

    printf("The task %s: pid=%d\n", TAR_TASK, pid);

    
    //Send a signal to server every 1 second
    int i=0;
    while(i < 10)
    {
        kill(pid,SIGUSR1);
        i++;
        printf("send %d times signal!\n", i);
        sleep(1);
    }
    
    printf("\nEnd the server and myself!\n");
    kill(pid, SIGUSR2);
    kill(getpid(), SIGSTOP);
   
    return 0;
}

3). task.c:

#include "./task.h"


int getPidByName(pid_t *pid, char *task_name)
{
    
    ////1. open the /proc , and every read, it will return the next dir struct dirent
    DIR * dirp;
    dirp = opendir("/proc");
    if(NULL == dirp)
    {
        perror("opendir");
        return -1;
    }

    //2.read the directory
    struct dirent *dp;
    while(NULL != (dp = readdir(dirp)))
    {
        //if direactory is . or .. or not a direct, skip
        if((dp->d_name == ".") || (dp->d_name == ".."))
            continue;
        if(DT_DIR != dp->d_type)
            continue;
        
        //open the /proc/pid/status file
        char fileName[128];
        FILE *fd ;
        sprintf(fileName, "/proc/%s/status", dp->d_name);
        fd = fopen(fileName, "r");
        
        if(NULL != fd)
        {
            //copy the /proc/pid/status content to buf
            char buf[128];
            fgets(buf, sizeof(buf), fd);
            
            //
            char cur_task[32] ;
            sscanf(buf, "%*s%s", cur_task);

            if(!strcmp(cur_task, task_name))
            {
                sscanf(dp->d_name, "%d", pid);
            }
        }
    }

    return 0;
}



int getNameByPid(pid_t pid, char *task_name)
{
    char fileName[128];
    sprintf(fileName, "/proc/%d/status", pid);

    FILE *fd;
    fd  = fopen(fileName, "r");
    if(NULL == fd)
    {
        perror("fopen");
        return -1;
    }
    
    char buf[128];
    fgets(buf, sizeof(buf), fd);
    sscanf(buf, "%*s%s", task_name);

    return 0;
    
}

4). task.h:

#ifndef TASK_H
#define TASK_H


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <dirent.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/ipc.h>


extern int getPidByName(pid_t *pid, char *task_name);

extern int getNameByPid(pid_t pid, char *task_name);


#endif

posted @ 2017-09-22 15:20  Jimmy_Nie  阅读(737)  评论(0编辑  收藏  举报