【问题标题】:How to send signal from Linux kernel space to user space in order to notify about an input hardware event如何从 Linux 内核空间向用户空间发送信号以通知输入硬件事件
【发布时间】:2020-02-26 13:46:17
【问题描述】:

我的内核模块代码需要向用户态程序发送信号,以将其执行转移到注册的信号处理程序。

事实上,我已经为我的嵌入式板开发了一个 C 程序,当我按下 BUTTON(输入事件)时,它可以使 LED 开启和关闭。另一方面,我刚刚开发了一个简单的 Linux 模块及其基本功能(OPEN、CLOSE、READ、WRITE)。

我只是不知道如何修改我的主程序和我的内核模块以达到我的目标。

我与你分享我的用户空间程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

#include <sys/select.h>
#include <sys/time.h>
#include <errno.h>

#include <linux/input.h>

#define BTN_FILE_PATH "/dev/input/event0"
#define LED_PATH "/sys/class/leds"

#define green "green"

void change_led_state(char *led_path, int led_value)
{
    char    lpath[64];
    FILE    *led_fd;

    strncpy(lpath, led_path, sizeof(lpath) - 1);
    lpath[sizeof(lpath) - 1] = '\0';

    led_fd = fopen(lpath, "w");

    if (led_fd == NULL) {
        fprintf(stderr, "simplekey: unable to access led\n");
        return;
    }

    fprintf(led_fd, "%d\n", led_value);

    fclose(led_fd);
}

void reset_leds(void)
{

    change_led_state(LED_PATH "/" green "/brightness", 0);
}

int configure_leds(void)
{
    FILE    *r_fd;
    char    *none_str = "none";

    /* Configure leds for hand control */

    r_fd = fopen(LED_PATH "/" green "/trigger", "w");




    fprintf(r_fd, "%s\n", none_str);


    fclose(r_fd);


    /* Switch off leds */
    reset_leds();

    return 0;
}

void eval_keycode(int code)
{

    static int green_state = 0;

    switch (code) {
    case 260:
        printf("BTN left pressed\n");

        /* figure out green state */

        green_state = green_state ? 0 : 1;

        change_led_state(LED_PATH "/" green "/brightness", green_state);
        break;
    }
}


int main(void)
{
    int file;
    /* how many bytes were read */
    size_t  rb;
    int ret;
    int yalv;
    /* the events (up to 64 at once) */
    struct input_event  ev[64];
    char    *str = BTN_FILE_PATH;

    printf("Starting simplekey app\n");

    ret = configure_leds();
    if (ret < 0)
        exit(1);

    printf("File Path: %s\n", str);

    if((file = open(str, O_RDONLY)) < 0) {
        perror("simplekey: File can not open");
        exit(1);
    }

    for (;;) {
        /* Blocking read */
        rb= read(file, &ev, sizeof(ev));

        if (rb < (int) sizeof(struct input_event)) {
            perror("simplekey: short read");
            exit(1);
        }

        for (yalv = 0;
            yalv < (int) (rb / sizeof(struct input_event));
            yalv++) {
            if (ev[yalv].type == EV_KEY) {
                printf("%ld.%06ld ",
                    ev[yalv].time.tv_sec,
                    ev[yalv].time.tv_usec);
                printf("type %d code %d value %d\n",
                        ev[yalv].type,
                        ev[yalv].code, ev[yalv].value);

                /* Change state on button pressed */
                if (ev[yalv].value == 0)
                    eval_keycode(ev[yalv].code);
            }
        }
    }

    close(file);

这是基本的内核模块:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>  
#include <linux/kernel.h>
#include <linux/uaccess.h>
#include <linux/input.h>

MODULE_LICENSE("GPL");      
MODULE_AUTHOR("Gaston");  
MODULE_DESCRIPTION("A simple Linux char driver"); 
MODULE_VERSION("0.1"); 


ssize_t exer_open(struct inode *pinode, struct file *pfile) {

    printk(KERN_INFO "Device has been opened\n");

    return 0;
}



ssize_t exer_read(struct file *pfile, char __user *buffer, size_t length, loff_t *offset) {

    return 0;
}



ssize_t exer_write(struct file *pfile, const char __user *buffer, size_t length, loff_t *offset) {

    return 0;

}   




ssize_t exer_close(struct inode *pinode, struct file *pfile) {

    printk(KERN_INFO "Device successfully closed\n");
    return 0;
}


struct file_operations exer_file_operations = { 
    .owner = THIS_MODULE,
    .open = exer_open,
    .read = exer_read,
    .write = exer_write,
    .release = exer_close,
};


int exer_simple_module_init(void) {

    printk(KERN_INFO "Initializing the LKM\n");
    register_chrdev(240, "Simple Char Drv", &exer_file_operations);
    return 0;
}


void exer_simple_module_exit(void) {

    unregister_chrdev(240, "Simple Char Drv");
}


module_init(exer_simple_module_init);
module_exit(exer_simple_module_exit);

我希望你能帮助我。谢谢!

【问题讨论】:

  • 您是否搜索过有关此主题的其他问题?谷歌搜索“linux 内核通知用户空间”会发现几个使用 poll/epoll 建议的问题(例如 that one)。 Another one 建议发送信号。
  • 首先,您需要实现一些代码,识别按钮按下。这可以是轮询某个端口的(内核或用户空间)线程,也可以是中断处理程序。这取决于您使用的硬件。之后,您可以跟踪打开 char 设备的进程,如果您真的不想使用轮询,请向这些进程发送信号(使用 send_sig_info() 或类似的)。
  • 没有理由“从 Linux 内核发送信号……通知输入硬件事件”。当您阅读 struct input_event 时,用户空间已经收到通知。您是否注意到按钮按下作为“输入事件”从内核传递到用户空间?所以你想发送一个冗余的 "signal" 来表示有一个需要读取的“输入事件”?您的程序已经使用的阻塞 read() 是您以最及时和有效的方式接收通知所需的全部内容。

标签: c linux-kernel signals linux-device-driver kernel-module


【解决方案1】:

我将专注于发送信号,因为这是您所要求的,尽管向进程发送信号非常残酷。最好实现pollread 文件操作,以便用户代码可以等待来自设备的事件并读取它们。

无论如何,要向打开设备的进程发送信号,您需要的是:

  1. 您需要在设备的私人数据中添加struct fasync_struct *

    struct fasync_struct *pasync_queue;
    

    在初始化设备私有数据的过程中,需要通过某种方式将其初始化为NULL。如何做到这一点取决于您。

  2. 您需要struct file_operationsfasync 成员指向的fasync 文件操作处理程序。 fasync 处理程序的实现非常简单,因为它只需要使用提供的参数和指向设备私有struct fasync_struct * 的指针调用fasync_helper()

    static int exer_fasync(int fd, struct file *pfile, int mode)
    {
         // N.B. Change this code to use the pasync_queue member from your device private data.
         struct fasync_struct **fapp = &pasync_queue;
         return fasync_helper(fd, pfile, mode, fapp);
    }
    
    struct file_operations exer_file_operations = { 
         .owner = THIS_MODULE,
         .open = exer_open,
         .read = exer_read,
         .write = exer_write,
         .release = exer_close,
         .fasync = exer_fasync,
    };
    
  3. 您的设备驱动程序可以通过调用kill_fasync() 发送SIGIO 信号,如下所示:

         // N.B. Change this code to use the pasync_queue member from your device private data.
         struct fasync_struct **fapp = &pasync_queue;
         kill_fasync(fapp, SIGIO, POLL_IN);
    

    注意最后一个参数(在本例中为值 POLL_IN)会影响应用程序在其信号处理程序中看到的 siginfo_t 的成员 si_band 的值。

  4. 您的应用程序需要为SIGIO 信号设置一个信号处理程序。我建议使用sigaction() 进行设置。

  5. 您的应用需要在打开设备文件时设置O_ASYNC标志,或者在打开设备文件后调用fcntl(fd, F_SETFL, O_ASYNC);进行设置。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-08-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多