2025-03-03
如果给Linux驱动实现fasync
函数,那么驱动就能够通过信号机制异步通知客户端程序。这个方法一般比较少用,但我们也要熟悉这种方法。下面就给大家带来一个完整的例子。
// simple_cdev.c
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/device/class.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#define BUFFER_SIZE 1024
#define DEV_MINOR_COUNTS 1
#define DEV_NAME "simple_cdev"
typedef struct {
struct cdev *cdev; // 字符设备
struct class *class; // 设备类
struct device *dev; // /dev目录下的设备
struct fasync_struct *fap; // 异步队列,用于信号异步通知机制
char buffer[BUFFER_SIZE]; // 缓冲区
} simple_cdev_t;
static simple_cdev_t my_cdev = {0};
static int _open(struct inode *inode, struct file *file) {
pr_info("simple_cdev_open\n");
return 0;
}
static ssize_t _read(struct file *file, char __user *userbuf, size_t size,
loff_t *offset) {
pr_info("simple_cdev_read\n");
size = min(size, BUFFER_SIZE);
unsigned long remain_len = copy_to_user(userbuf, my_cdev.buffer, size);
if (remain_len > 0) {
pr_info("copy_to_user failed\n");
return -EIO;
}
return size;
}
static ssize_t _write(struct file *file, const char __user *userbuf,
size_t size, loff_t *offset) {
pr_info("simple_cdev_write\n");
size = min(size, BUFFER_SIZE);
unsigned long remain_len = copy_from_user(my_cdev.buffer, userbuf, size);
if (remain_len > 0) {
pr_err("copy_from_user failed\n");
return -EIO;
}
// 遍历异步队列中的fd, 并发送SIGIO信号
kill_fasync(&my_cdev.fap, SIGIO, POLL_IN);
return size;
}
static int _release(struct inode *inode, struct file *file) {
pr_info("simple_cdev_release\n");
return 0;
}
// 将文件描述符添加到异步队列
static int _fasync(int fd, struct file *file, int on) {
pr_info("simple_cdev_fasync\n");
return fasync_helper(fd, file, on, &my_cdev.fap);
}
static struct file_operations fops = {
.open = _open,
.read = _read,
.write = _write,
.release = _release,
.fasync = _fasync,
};
static int __init simple_cdev_init(void) {
pr_info("simple_cdev_init\n");
my_cdev.cdev = cdev_alloc();
if (!my_cdev.cdev) {
pr_err("simple_cdev cdev_alloc failed!\n");
return -ENOMEM;
}
// 初始化字符设备
cdev_init(my_cdev.cdev, &fops);
// 申请设备号, 由系统分配主设备号和一个从设备号
int ret =
alloc_chrdev_region(&my_cdev.cdev->dev, 0, DEV_MINOR_COUNTS, DEV_NAME);
if (ret) {
pr_err("simple_cdev alloc_chrdev_region failed!\n");
return ret;
}
pr_info("simple_cdev major = %d\n", MAJOR(my_cdev.cdev->dev));
// 添加到内核
ret = cdev_add(my_cdev.cdev, my_cdev.cdev->dev, DEV_MINOR_COUNTS);
if (ret) {
pr_err("simple_cdev cdev_add failed!\n");
return ret;
}
// 申请设备类, 会在/sys/class目录下常见一个simple_cdev的目录
my_cdev.class = class_create("simple_cdev");
if (IS_ERR(my_cdev.class)) {
pr_err("simple_cdev class_create failed\n");
return PTR_ERR(my_cdev.class);
}
// 申请设备对象,会在/dev目录下创建设备文件simple_cdev,
// 并且在/sys/class/simple_cdev目录下常见一个simple_cdev的设备节点
my_cdev.dev =
device_create(my_cdev.class, NULL, my_cdev.cdev->dev, NULL, DEV_NAME);
if (IS_ERR(my_cdev.dev)) {
pr_err("simple_cdev device_create failed\n");
return PTR_ERR(my_cdev.dev);
}
return 0;
}
static void __exit simple_cdev_exit(void) {
pr_info("simple_cdev_exit\n");
device_destroy(my_cdev.class, my_cdev.cdev->dev);
class_destroy(my_cdev.class);
cdev_del(my_cdev.cdev);
unregister_chrdev_region(my_cdev.cdev->dev, DEV_MINOR_COUNTS);
kfree(my_cdev.cdev);
}
module_init(simple_cdev_init);
module_exit(simple_cdev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("heng30");
MODULE_VERSION("v0.0.1");
MODULE_DESCRIPTION("A simple_cdev kernel module");
#!/bin/sh
top-dir = $(shell pwd)
kernel-version = $(shell uname -r)
kernel-dir ?= /lib/modules/$(kernel-version)/build
obj-m = simple_cdev.o
all:
make -C $(kernel-dir) modules M=$(top-dir)
clean:
rm -f *.o *.ko *.mod *.mod.c *.order *.symvers
make -C $(kernel-dir) clean m=$(top-dir)
// signal.c
// 编译命令:gcc -o signal signal.c
#include <assert.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/signal.h>
#include <sys/types.h>
#include <unistd.h>
#define BUFFER_SIZE 1024
int g_fd = -1;
char buffer[BUFFER_SIZE];
static void _sigio_cb(int sig) {
printf("sig = %d\n", sig);
if (sig == SIGIO) {
ssize_t rlen = read(g_fd, buffer, BUFFER_SIZE - 1);
if (rlen <= 0) {
buffer[0] = '\0';
perror("read error");
} else {
buffer[rlen] = '\0';
printf("len = %ld, %s\n", rlen, buffer);
}
}
}
int main(int argc, char *argv[]) {
if (argc != 2) {
perror("please input a device to read and write");
return -1;
}
g_fd = open(argv[1], O_RDONLY);
assert(g_fd >= 0);
// 设置信号回掉函数
signal(SIGIO, _sigio_cb);
// 指定处理fd IO的进程
fcntl(g_fd, F_SETOWN, getpid());
// 指定处理IO的方式:异步处理
int flags = fcntl(g_fd, F_GETFL);
flags |= FASYNC;
fcntl(g_fd, F_SETFL, flags);
while (true) {
sleep(1);
}
return 0;
}
安装驱动:insmod simple_cdev.ko
移除驱动:rmmod simple_cdev.ko
打开两个终端窗口:
./signal /dev/simple_cdev
。signal
命令会一直等待。echo "hello" > /dev/simple_cdev
。第1个窗口会马上输出hello
字符串。在第2个窗口不断地输入内容,第1个窗口会输出相同的内容。