linux IPC之信号

信号可以说是ipc最复杂的的通信方式。在网上也有很多很多讲解得比较好的资料可以参考,我在这里只是初略的对自己的学习的内容做个总结!

信号本质

信号实际上是软中断,既然是中断那么信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。
信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息。

信号来源

信号事件的发生有两个来源:硬件来源即硬件中断(比如我们按下了键盘或者其它硬件故障);软件来源,最常用发送信号的系统函数是kill, raise, alarm和setitimer以及sigqueue函数,软件来源还包括一些非法运算等操作。

信号分类

  1. 不可靠信号:Linux信号机制基本上是从Unix系统中继承过来的。早期Unix系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,因此,把那些建立在早期机制上的信号叫做”不可靠信号”,信号值小于SIGRTMIN的信号都是不可靠信号。这就是”不可靠信号”的来源。Linux下的不可靠信号问题主要指的是信号可能丢失。
  2. 可靠信号:原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。Linux在支持新版本的信号安装函数sigation()以及信号发送函数sigqueue()的同时,仍然支持早期的signal()信号安装函数,支持信号发送函数kill()。

注:可靠信号是指后来添加的新信号(信号值位于SIGRTMIN及SIGRTMAX之间);不可靠信号是信号值小于SIGRTMIN的信号。信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。目前linux中的signal()是通过sigation()函数实现的,因此,即使通过signal()安装的信号,在信号处理函数的结尾也不必再调用一次信号安装函数。同时,由signal()安装的实时信号支持排队,同样不会丢失。
对于目前linux的两个信号安装函数:signal()及sigaction()来说,它们都不能把SIGRTMIN以前的信号变成可靠信号(都不支持排队,仍有可能丢失,仍然是不可靠信号),而且对SIGRTMIN以后的信号都支持排队。这两个函数的最大区别在于,经过sigaction安装的信号都能传递信息给信号处理函数(对所有信号这一点都成立),而经过signal安装的信号却不能向信号处理函数传递信息。对于信号发送函数来说也是一样的。

signal安装响应函数

#include <signal.h>  
#include <stdio.h>  
#include <unistd.h>  

void ouch(int sig)  
{  
    printf("oh, got a signal %d\n", sig);  

    int i = 0;  
    for (i = 0; i < 5; i++)  
    {  
        printf("signal func %d\n", i);  
        sleep(1);  
    }

    // (void) signal(SIGINT, SIG_DFL);  
     //(void) signal(SIGINT, ouch);  

 }  

int main()  { 

    (void) signal(SIGINT, ouch);  

    while(1)  {  

        printf("hello world...\n");  
        sleep(1);  
    }  
}  

连续按Ctrl+C时会发现 signal函数会堵塞当前正在处理的signal,但是没有办法阻塞其它signal,比如正在处理SIG_INT,再来一个SIG_INT则会堵塞,但是来SIG_QUIT则会被其中断。

sigaction安装响应函数

#include <signal.h>  
#include <stdio.h>  
#include <unistd.h> 

void ouch(int sig)  {  
    printf("oh, got a signal %d\n", sig);  

    int i = 0;  
    for (i = 0; i < 5; i++)  
    {  
        printf("signal func %d\n", i);  
        sleep(1);  
    }  
}  

int main()  
{  
    struct sigaction act;  
    act.sa_handler = ouch;  
    sigemptyset(&act.sa_mask);  
    sigaddset(&act.sa_mask, SIGQUIT);  
    // act.sa_flags = SA_RESETHAND;  
    // act.sa_flags = SA_NODEFER;  
    act.sa_flags = 0;  

    sigaction(SIGINT, &act, 0);  

    struct sigaction act_2;  
    act_2.sa_handler = ouch;  
    sigemptyset(&act_2.sa_mask);  
    act.sa_flags = 0;  
    sigaction(SIGQUIT, &act_2, 0);  

        while(1)  {  
             sleep(1);  
        }  

    return;  

    }  
  1. 阻塞,sigaction函数有阻塞的功能,比如SIGINT信号来了,进入信号处理函数,默认情况下,在信号处理函数未完成之前,如果又来了一个SIGINT信号,其将被阻塞,只有信号处理函数处理完毕,才会对后来的SIGINT再进行处理,同时后续无论来多少个SIGINT,仅处理一个SIGINT,sigaction会对后续SIGINT进行排队合并处理。

  2. sa_mask,信号屏蔽集,可以通过函数sigemptyset/sigaddset等来清空和增加需要屏蔽的信号,上面代码中,对信号SIGINT处理时,如果来信号SIGQUIT,其将被屏蔽,但是如果在处理SIGQUIT,来了SIGINT,则首先处理SIGINT,然后接着处理SIGQUIT。

  3. sa_flags如果取值为0,则表示默认行为。还可以取如下俩值,但是我没觉得这俩值有啥用。
    SA_NODEFER,如果设置来该标志,则不进行当前处理信号到阻塞
    SA_RESETHAND,如果设置来该标志,则处理完当前信号后,将信号处理函数设置为SIG_DFL行为。

信号的实际使用

MTK8685平台有mtk的 thermal管理,实时监控平台的温度,当主芯片的温度过高时,会提示用户cooldown 即重启盒子降温,以免损坏! 温度监控系统也是采用信号的方式实现的。thermald.c 首先设置响应的信号,并设置signal_handler,最终会将此进程的pid通过proc文件系统传递给内核! 内核的驱动检测到温度过高时,会将信号发送给指定的用户态程序,由用户决定下一步的操作!

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <sys/file.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <cutils/xlog.h>
#include <sys/types.h>

static int debug_on = 0;

#define TM_LOG_TAG "thermald"
#define TM_DBG_LOG(_fmt_, args...) \
    do { \
        if (1 == debug_on) { \
            sxlog_printf(ANDROID_LOG_INFO, TM_LOG_TAG, _fmt_, ##args); \
        } \
    } while(0)

#define TM_INFO_LOG(_fmt_, args...) \
    do { sxlog_printf(ANDROID_LOG_INFO, TM_LOG_TAG, _fmt_, ##args); } while(0)

#define PROCFS_MTK_CL_SD_PID "/proc/driver/mtk_cl_sd_pid"

static void signal_handler(int signo, siginfo_t *si, void *uc)
{
    switch(si->si_signo) {
        // Add more signo or code to expand thermald
        case SIGIO:
            if(1 == si->si_code) {
            //待收到高温警告后,启动提示对话框
                system("am start com.mediatek.thermalmanager/.ShutDownAlertDialogActivity");
                TM_INFO_LOG("thermal shutdown signal received, si_signo=%d, si_code=%d\n", si->si_signo, si->si_code);
            }
        break;
        default:
            TM_INFO_LOG("what!!!\n");
        break;
    }
}

int main(int argc, char *argv[])
{
    int fd = open(PROCFS_MTK_CL_SD_PID, O_RDWR);
    int pid = getpid();
    int ret = 0;
    char pid_string[32] = {0};

    struct sigaction act;

    TM_INFO_LOG("START+++++++++ %d", getpid());

    /* Create signal handler */
    memset(&act, 0, sizeof(act));
    act.sa_flags = SA_SIGINFO;//发送额外的信息给signal_handler
    //act.sa_handler = signal_handler;
    act.sa_sigaction = signal_handler;
    sigemptyset(&act.sa_mask);

    sigaction(SIGIO, &act, NULL);

    /* Write pid to procfs */
    sprintf(pid_string, "%d", pid);

    ret = write(fd, pid_string, sizeof(char) * strlen(pid_string)); //将当前的进程pid写入proc文件系统,供内核使用
    if (ret <= 0)   {
        TM_INFO_LOG("Fail to write %d to %s %x\n", pid, PROCFS_MTK_CL_SD_PID, ret);
    } else {
        TM_INFO_LOG("Success to write %d to %s\n", pid, PROCFS_MTK_CL_SD_PID);
    }
    close(fd);

    TM_INFO_LOG("Enter infinite loop");

    while(1) {
        sleep(100);
    }

    TM_INFO_LOG("END-----------");

    return 0;
}

下面看看内核是如何将信号发送到指定的用户程序的,相关代码如下:

驱动在初始化时会创建proc文件节点:
static int __init mtk_cooler_shutdown_init(void){    
    ......
    entry = proc_create("driver/mtk_cl_sd_pid", S_IRUGO | S_IWUSR | S_IWGRP, NULL, &_cl_sd_pid_fops);
    .....
    }

_cl_sd_pid_fops定义如下:
static const struct file_operations _cl_sd_pid_fops = {
    .owner = THIS_MODULE,
    .open = _mtk_cl_sd_pid_open,
    .read = seq_read,
    .llseek = seq_lseek,
    .write = _mtk_cl_sd_pid_write,
    .release = single_release,
};
用户态下write  /proc/driver/mtk_cl_sd_pid文件时,kernel会调用_mtk_cl_sd_pid_write。_mtk_cl_sd_pid_write实现如下:
static ssize_t _mtk_cl_sd_pid_write(struct file *filp, const char __user *buf, size_t len, loff_t *data)
{
    int ret = 0;
    char tmp[MAX_LEN] = {0};

    /* write data to the buffer */
    if ( copy_from_user(tmp, buf, len) ) {
        return -EFAULT;
    }

    ret = kstrtouint(tmp, 10, &tm_input_pid);//通过proc文件系统,用户程序吧pid悄悄滴传送到了内核
    if (ret)
        WARN_ON(1);

    mtk_cooler_shutdown_dprintk("[%s] %s = %d\n", __func__, tmp, tm_input_pid);

    return len;
}

static int _mtk_cl_sd_send_signal(void)
{
    int ret = 0;

    if (tm_input_pid == 0) {
        mtk_cooler_shutdown_dprintk("[%s] pid is empty\n", __func__);
        ret = -1;
    }

    mtk_cooler_shutdown_dprintk("[%s] pid is %d, %d\n", __func__, tm_pid, tm_input_pid);

    if (ret == 0 && tm_input_pid != tm_pid) {
        tm_pid = tm_input_pid;
        pg_task = get_pid_task(find_vpid(tm_pid), PIDTYPE_PID);//根据pid获取task_struct结构体
    }

    if (ret == 0 && pg_task) {
        siginfo_t info;
        info.si_signo = SIGIO;
        info.si_errno = 0;
        info.si_code = 1;
        info.si_addr = NULL;
        ret = send_sig_info(SIGIO, &info, pg_task);//将SIGIO信号发送给指定的用户进程
    }

    if (ret != 0) mtk_cooler_shutdown_dprintk("[%s] ret=%d\n", __func__, ret);

    return ret;
}

源码路径mediatek/kernel/drivers/thermal/mtk_cooler_shutdown.c

较详细的linux 信号参考资料:
Linux环境进程间通信(二): 信号(上)
Linux环境进程间通信(二): 信号(下)
Linux signal那些事儿
Linux signal 那些事儿(2)
Linux signal 那些事儿 (3)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值