linux 驱动向应用程序发射信号

发布时间 2023-09-24 21:32:42作者: 流水灯

系统支持信号

在linux终端输入kill -l可以查看系统所支持的信号,可以看出,每个信号的名字都是以SIG开头:

root@zhengyang:/work/sambashare/linux-5.2.8# kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

除了SIGSTOP和SIGKILL两个信号外,进程能够忽略或捕获其他的全部信号。一个信号被捕获的意思是当一个信号到达时有相应的代码处理它,如果一个信号没有被这个进程所捕获,内核将采用默认行为处理。

应用程序注册信号回调函数

linux中有两个函数实现信号的安装:

  • signal:它只有两个参数,不支持信号传递消息,主要用于SIGRTMIN之前的非实时信号的安装,这里的不可靠主要是不支持信号队列,就是当多个信号发生在进程中的时候(收到信号的速度超过进程处理的速度的时候),这些没来的及处理的信号就会被丢掉,仅仅留下一个信号;

  • sigaction:sigaction是较新的函数(由两个系统调用实现:sys_signal以及sys_rt_sigaction),有三个参数,支持信号传递信息,主要用来与 sigqueue系统调用配合使用,当然,sigqueue同样支持非实时信号的安装。sigaction优于signal主要体现在支持信号带有参数;

signal使用实例:

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

void sig_handler(int signo);
int main(void)
{
    printf("mian is waiting for a signal\n");
    if(signal(SIGINT,sig_handler) == SIG_ERR){
        perror("signal errror");
        exit(EXIT_FAILURE);
    }
    for(; ;);//有时间让我们发送信号


    return 0;
}

void sig_handler(int signo)
{
    printf("catch the signal SIGINT %d\n",signo);
}

 

sigaction:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <signal.h>

// 信号处理函数
static void signal_handler(int signum, siginfo_t *info, void *context)
{
    // 打印接收到的信号值
    printf("signal_handler: signum = %d \n", signum);
}

int main(void)
{
    int count = 0;
    // 注册信号处理函数
    struct sigaction sa;
    sigemptyset(&sa.sa_mask);
    sa.sa_sigaction = &signal_handler;
    sa.sa_flags = SA_SIGINFO;
    sigaction(SIGUSR1, &sa, NULL);
    sigaction(SIGUSR2, &sa, NULL);

    // 一直循环打印信息,等待接收发信号
    while (1)
    {
        printf("app_handle_signal is running...count = %d \n", ++count);
        sleep(5);
    }

    return 0;
}

设备驱动向进程发送信号

  • 信号的发送方:必须知道向谁(PID)发送信号,发送哪个信号;
  • 信号接收方:必须定义信号处理函数,并安装信号以及信号处理函数的映射关系;

获取进程PID

驱动程序如何才能知道用户进程的PID呢?可以让应用程序通过fcntl函数,把自己的PID主动告诉驱动程序;

fcntl函数可以对一个已经打开的文件描述符执行一系列控制操作,在ubuntu下运行man 2  fcntl可以查看帮助信息;

fcntl函数原型如下所示:

int fcntl(int fd, int cmd,... /* arg */);

函数参数:

  • fd: 文件描述符。
  • cmd: 操作命令。此参数表示我们将要对 fd 进行什么操作, cmd 参数支持很多操作命令,大家可以打开 man 手册查看到这些操作命令的详细介绍,这些命令都是以 F_XXX 开头的,譬如 F_DUPFD 、 F_GETFD 、F_SETFD 等,不同的 cmd 具有不同的作用, cmd 操作命令大致可以分为以下 5 种功能:
    •  复制文件描述符,对应的cmd:F_DUPFD、F_DUPFD_CLOEXEC。当使用这两个cmd时,需要传入第三个参数,fcntl返回复制后的文件描述符,此返回值是之前未被占用的描述符,并且必须一个大于等于第三个参数值。F_DUPFD命令要求返回的文件描述符会清除对应的FD_CLOEXEC标志;F_DUPFD_CLOEXEC要求设置新描述符的FD_CLOEXEC标志;
    • 获取/设置文件描述符标志,对应的cmd:F_GETFD、F_SETFD。用于设置FD_CLOEXEC标志,此标志的含义是:当进程执行exec系统调用后此文件描述符会被自动关闭;
    • 获取/设置文件状态标志,对应的cmd:F_GETFL、F_SETFL。获取当前打开文件的访问标志,设置对应的文件访问标志;
    • 管理IO信号,对应的cmd:F_GETOWN、F_SETOWN。获取和设置用来接收SIGIO/SIGURG信号的进程id或者进程组id,返回对应的进程id或者进程组id。
    • 获取/设置记录锁,对应的cmd:F_GETLK、F_SETLK、F_SETLKW;
  • fcntl 函数是一个可变参函数,第三个参数需要根据不同的 cmd 来传入对应的实参,配合 cmd 来使用;

返回值:执行失败情况下,返回 -1 ,并且会设置 errno ;执行成功的情况下,其返回值与cmd (操作命令)有关,譬如 :

  • cmd=F_DUPFD (复制文件描述符)将返回一个新的文件描述符;
  • cmd=F_GETFD (获取文件描述符标志)将返回文件描述符标志;
  • cmd=F_GETFL (获取文件状态标志)将返回文件状态标志等;

这里以设置进程id为例:

fcntl(fd, F_SETOWN, getpid());    // 对文件描述符设置SIGIO、SIGURG信号所通知的进程

 

使能信号驱动IO

通过F_SETFL 控制命令设置设备文件以支持O_ASYNC, 即信号驱动IO,示例:

int fl;
fl = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, fl | O_FASYNC );

cmd设置为F_GETFL、F_SETFL时。 args参数传入文件状态标志位,使用open函数时可以指定:O_APPEND、O_ASYNC、O_CLOEXEC、O_CREAT、O_DIRECT、O_DSYNC、O_NONBLOCK、O_SYNC等。

而fcntl只可以使用open函数中的部分标志:O_APPEND, O_DIRECT、O_NONBLOCK,O_SYNC和O_ASYNC

  • O_ASYNC:使能信号驱动IO,当输入缓存中的输入数据就绪时(输入数据可读),内核向用F_SETOWN来绑定的那个进程发送SIGIO信号;

注意:在修改文件描述符标志或文件状态标志时必须谨慎,先要取得现在的标志值,然后按照希望修改它,最后设置新标志值。不能只是执行F_SETFD或F_SETFL命令,这样会关闭以前设置的标志位。

按键测试应用程序

#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

int fd,ret;

/* 信号处理函数 */
void signal_fun(int signum)
{
    unsigned int key_val = 0;
    static int count = 0;
    printf("signal = %d, %d count\n",signum,++count);

    /* 读取按键值 */
    ret = read(fd, &key_val, 1);    
    if(ret < 0){
        printf("read error\n");
    }
    printf("key_val = 0x%x\n", key_val);    
}

int main(int argc,char **argv)
{
    int flags;
    
    fd = open("/dev/buttons", O_RDWR);
    if (fd < 0)
    {
        printf("can't open!\n");
        return -1;
    }

    /* 对文件描述符设置SIGIO、SIGURG信号所通知的进程 */
    fcntl(fd, F_SETOWN, getpid());    
    /* 获取文件状态描述符 */
    flags = fcntl(fd,F_GETFL);
    /* 设置信号驱动IO */
    fcntl(fd,F_SETFL, flags | O_ASYNC);

    /* 调用signal函数,让指定的信号SIGIO与处理函数signal_fun对应 */
    signal(SIGIO,signal_fun);  

    while (1)
    {
        sleep(10);    // 休眠10s
    }
    
    return 0;
}

按键驱动程序

按键测试应用程序端负责捕获信号,则设备驱动负责释放信号,需要在设备驱动程序中增加信号释放的相关代码。 

为了使设备支持信号驱动IO,设备驱动程序中涉及3项工作:

  • 支持F_SETOWN命令,能在这个控制命令处理中设置filp->f_owner为对应进程ID。不过此项工作已由内核完成,设备驱动程序中无须处理;
  • 支持F_SETFL命令的处理,添加O_ASYNC标志,会调用驱动程序中的.fasync函数。因此,我们在按键驱动中实现了button_fasync函数;
  • 当有按键发生变化时,向按键测试应用程序发送SIGIO信号,调用kill_fasync函数激发相应的信号;

上述的3项工作和按键测试应用程序中的3项是对应的,他们之间的关系,如下图:

 

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/irq.h>        // 包含了mach/irqs.h
#include <linux/interrupt.h>
#include <linux/gpio/machine.h>
#include <mach/gpio-samsung.h>
/*
 全局静态变量:希望全局变量仅限于在本源文件中使用,在其他源文件中不能引用,也就是说限制其作用域只在
 定义该变量的源文件内有效,而在同一源程序的其他源文件中不能使用
 */
#define OK   (0)
#define ERROR  (-1)
#define __IRQT_FALEDGE IRQ_TYPE_EDGE_FALLING
#define __IRQT_RISEDGE IRQ_TYPE_EDGE_RISING
#define __IRQT_LOWLVL IRQ_TYPE_LEVEL_LOW
#define __IRQT_HIGHLVL IRQ_TYPE_LEVEL_HIGH
#define IRQT_NOEDGE (0)
#define IRQT_RISING (__IRQT_RISEDGE)
#define IRQT_FALLING (__IRQT_FALEDGE)
#define IRQT_BOTHEDGE (__IRQT_RISEDGE|__IRQT_FALEDGE)
#define IRQT_LOW (__IRQT_LOWLVL)
#define IRQT_HIGH (__IRQT_HIGHLVL)
#define IRQT_PROBE IRQ_TYPE_PROBE

/* 键值: 按下时, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 */
/* 键值: 松开时, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86 */
static unsigned char key_val;

struct pin_desc{
    unsigned int pin;
    unsigned int key_val;
};

/*
 * S3C2410_GPG 定义在arch/arm/mach-s3c24xx/include/mach/gpio-samsung.h
 */
static struct pin_desc pins_desc[4] = {
    {S3C2410_GPG(0), 0x01},
    {S3C2410_GPG(3), 0x02},
    {S3C2410_GPG(5), 0x03},
    {S3C2410_GPG(6), 0x04},
    {S3C2410_GPG(7), 0x05},
    {S3C2410_GPG(11), 0x06},
};

/*  异步信号结构体变量  */
static struct fasync_struct * button_async;

/*
 * 中断处理服务
 */
static irqreturn_t button_irq(int irq, void *dev_id)
{
    struct pin_desc *pindesc = (struct pin_desc *)dev_id;
    unsigned int pinval;

   pinval = gpio_get_value(pindesc->pin);
 
    if (pinval){
        /* 松开 */
        key_val = 0x80 | pindesc->key_val;
    }
    else{
        /* 按下 */
        key_val = pindesc->key_val;
    }
 
    kill_fasync(&button_async, SIGIO, POLL_IN);   /* 发送SIGIO信号给应用层 */
 
    return IRQ_RETVAL(IRQ_HANDLED);
}

/* 
   GPG0、GPG3、GPG5、GPG6、GPG7、GPG11配置为中断功能 
   IRQ_EINTX 定义在 arch/arm/mach-s3c24xx/include/mach/irqs.h
*/
static int button_open(struct inode *inode, struct file *file)
{
    /* 注册中断 */
    int ret;
    ret = request_irq(IRQ_EINT8,button_irq,IRQT_BOTHEDGE,"K1",&pins_desc[0]);
    if(ret){
        printk("open failed K1");
    }

    ret = request_irq(IRQ_EINT11,button_irq,IRQT_BOTHEDGE,"K2",&pins_desc[1]);
    if(ret){
        printk("open failed K2");
    }

    ret = request_irq(IRQ_EINT13,button_irq,IRQT_BOTHEDGE,"K3",&pins_desc[2]);
    if(ret){
        printk("open failed K3");
    }

    ret = request_irq(IRQ_EINT14,button_irq,IRQT_BOTHEDGE,"K4",&pins_desc[3]);
    if(ret){
        printk("open failed K4");
    }

    ret = request_irq(IRQ_EINT15,button_irq,IRQT_BOTHEDGE,"K5",&pins_desc[4]);
    if(ret){
        printk("open failed K5");
    }

    ret = request_irq(IRQ_EINT19,button_irq,IRQT_BOTHEDGE,"K6",&pins_desc[5]);
    if(ret){
        printk("open failed K6");
    }

    return 0;
}

static ssize_t button_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
    int count;
    if (size != 1){
        printk("read error\n");
        return -EINVAL;
    }

    /* 如果有按键动作, 上传key_val给用户层 */
    count = copy_to_user(buf, &key_val, 1);
    
    return count;
}

/*
 * 释放中断资源
 */
static int button_close(struct inode *inode, struct file *file)
{
    free_irq(IRQ_EINT8, &pins_desc[0]);
    free_irq(IRQ_EINT11, &pins_desc[1]);
    free_irq(IRQ_EINT13, &pins_desc[2]);
    free_irq(IRQ_EINT14, &pins_desc[3]);
    free_irq(IRQ_EINT15, &pins_desc[4]);
    free_irq(IRQ_EINT19, &pins_desc[5]);

    return 0;
}

static int button_fasync (int fd, struct file *file, int on)
{
      printk("button_fasync ok\n");
    /* 初始化button_async结构体,就能使用kill_fasync函数 */
    return fasync_helper(fd, file, on, &button_async); 
}

static struct file_operations button_fops = {
    .owner   =   THIS_MODULE,
    .open    =   button_open,
    .read    =   button_read,
    .release =   button_close,
    .fasync  =   button_fasync, 
};

static dev_t devid;                      // 起始设备编号
static struct cdev button_cdev;          // 保存操作结构体的字符设备 
static struct class *button_cls;

static int button_init(void)
{
    
    /* 动态分配字符设备: (major,0) */
    if(OK == alloc_chrdev_region(&devid, 0, 1,"button")){   // ls /proc/devices看到的名字
        printk("register_chrdev_region ok\n");
    }else {
        printk("register_chrdev_region error\n");
        return ERROR;
    }
    
     cdev_init(&button_cdev, &button_fops);
     cdev_add(&button_cdev, devid, 1);


    /* 创建类,它会在sys目录下创建/sys/class/button这个类  */
     button_cls = class_create(THIS_MODULE, "button");
     if(IS_ERR(button_cls)){
         printk("can't create class\n");
         return ERROR;
     }
    /* 在/sys/class/button下创建buttons设备,然后mdev通过这个自动创建/dev/buttons这个设备节点 */
     device_create(button_cls, NULL, devid, NULL, "buttons"); 

     return 0;
}

static void __exit button_exit(void)
{
    printk("button driver exit\n");
    /* 注销类、以及类设备 /sys/class/button会被移除*/
    device_destroy(button_cls, devid);
    class_destroy(button_cls);

    cdev_del(&button_cdev);
    unregister_chrdev_region(devid, 1);
    return;
}

module_init(button_init);
module_exit(button_exit);
MODULE_LICENSE("GPL");