如何从 Linux 内核 space 向用户 space 发送信号以通知输入硬件事件
How to send signal from Linux kernel space to user space in order to notify about an input hardware event
我的内核模块代码需要向用户态程序发送信号,以将其执行转移到已注册的信号处理程序。
事实上,我已经为我的嵌入式板开发了一个 C 程序,它可以在我按下 BUTTON(输入事件)时使 LED 打开和关闭。另一方面,我刚刚开发了一个具有基本功能(打开、关闭、读取、写入)的简单 Linux 模块。
我只是不知道如何修改我的主要程序和我的内核模块以到达我的 objective。
我和你分享我的用户space程序:
#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] = '[=13=]';
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);
我希望你能帮助我。谢谢!
我将专注于发送信号,因为这是您要求的,尽管向进程发送信号非常残酷。最好实现 poll
和 read
文件操作,以便用户代码可以等待来自设备的事件并读取它们。
无论如何,要向打开设备的进程发送信号,您需要的是:
您需要struct fasync_struct *
在您设备的私人数据中:
struct fasync_struct *pasync_queue;
需要在您的设备私有数据初始化时通过某种方式初始化为NULL
。怎么做取决于你。
您需要一个由 struct file_operations
的 fasync
成员指向的 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,
};
您的设备驱动程序可以通过调用 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);
N.B。最后一个参数(在本例中为值 POLL_IN
)影响应用程序在其信号处理程序中看到的 siginfo_t
的 si_band
成员的值。
您的应用程序需要为 SIGIO
信号设置信号处理程序。我建议使用 sigaction()
来设置它。
您的应用程序需要在打开设备文件时设置O_ASYNC
标志,或者在打开设备文件后通过调用fcntl(fd, F_SETFL, O_ASYNC);
来设置。
我的内核模块代码需要向用户态程序发送信号,以将其执行转移到已注册的信号处理程序。
事实上,我已经为我的嵌入式板开发了一个 C 程序,它可以在我按下 BUTTON(输入事件)时使 LED 打开和关闭。另一方面,我刚刚开发了一个具有基本功能(打开、关闭、读取、写入)的简单 Linux 模块。
我只是不知道如何修改我的主要程序和我的内核模块以到达我的 objective。
我和你分享我的用户space程序:
#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] = '[=13=]';
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);
我希望你能帮助我。谢谢!
我将专注于发送信号,因为这是您要求的,尽管向进程发送信号非常残酷。最好实现 poll
和 read
文件操作,以便用户代码可以等待来自设备的事件并读取它们。
无论如何,要向打开设备的进程发送信号,您需要的是:
您需要
struct fasync_struct *
在您设备的私人数据中:struct fasync_struct *pasync_queue;
需要在您的设备私有数据初始化时通过某种方式初始化为
NULL
。怎么做取决于你。您需要一个由
的指针来调用struct file_operations
的fasync
成员指向的 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, };
您的设备驱动程序可以通过调用
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);
N.B。最后一个参数(在本例中为值
POLL_IN
)影响应用程序在其信号处理程序中看到的siginfo_t
的si_band
成员的值。您的应用程序需要为
SIGIO
信号设置信号处理程序。我建议使用sigaction()
来设置它。您的应用程序需要在打开设备文件时设置
O_ASYNC
标志,或者在打开设备文件后通过调用fcntl(fd, F_SETFL, O_ASYNC);
来设置。