下面是一个使用 poll
机制从内核层通知应用层的简单示例。在这个示例中,我们将创建一个字符设备驱动程序,并在该驱动程序中实现对 poll
的支持,以便能够通过 poll()
系统调用从用户空间等待事件。
内核模块(字符设备)
首先,我们需要编写一个简单的字符设备驱动,它支持 poll
:
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/poll.h>
#include <linux/wait.h>
#define DEVICE_NAME "mychar"
#define BUFFER_SIZE 128
static int major_number;
static char message[BUFFER_SIZE] = {0};
static int message_length = 0;
static DECLARE_WAIT_QUEUE_HEAD(wq);
static int data_available = 0;
static ssize_t mychar_read(struct file *file, char __user *buffer, size_t len, loff_t *offset) {
if (data_available == 0) {
// 如果没有数据可读,阻塞用户进程
if (wait_event_interruptible(wq, data_available != 0)) {
return -ERESTARTSYS; // 被信号中断
}
}
// 拷贝数据到用户空间
if (copy_to_user(buffer, message, message_length)) {
return -EFAULT;
}
data_available = 0; // 重置标志
return message_length; // 返回读取的字节数
}
static unsigned int mychar_poll(struct file *file, poll_table *wait) {
poll_wait(file, &wq, wait); // 将当前文件描述符添加到等待队列
if (data_available > 0) {
return POLLIN | POLLRDNORM; // 表示有数据可读
}
return 0; // 没有数据可读
}
static ssize_t mychar_write(struct file *file, const char __user *buffer, size_t len, loff_t *offset) {
if (len > BUFFER_SIZE) len = BUFFER_SIZE; // 限制写入长度
if (copy_from_user(message, buffer, len)) {
return -EFAULT;
}
message_length = len;
data_available = 1; // 设置标志为有数据可读
wake_up(&wq); // 唤醒所有在等待队列上的进程
return len; // 返回写入的字节数
}
static struct file_operations fops = {
.read = mychar_read,
.write = mychar_write,
.poll = mychar_poll,
};
static int __init mychar_init(void) {
major_number = register_chrdev(0, DEVICE_NAME, &fops);
if (major_number < 0) {
printk(KERN_ALERT "Failed to register a major number\n");
return major_number;
}
printk(KERN_INFO "MyChar device registered with major number %d\n", major_number);
return 0;
}
static void __exit mychar_exit(void) {
unregister_chrdev(major_number, DEVICE_NAME);
printk(KERN_INFO "MyChar device unregistered\n");
}
module_init(mychar_init);
module_exit(mychar_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple character device with poll support");
用户空间代码
下面是一个简单的用户空间程序,使用 poll()
等待字符设备的数据可用:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/poll.h>
#define DEVICE_FILE "/dev/mychar"
int main() {
int fd = open(DEVICE_FILE, O_RDWR);
if (fd == -1) {
perror("Failed to open the device...");
return errno;
}
struct pollfd fds[1];
while (1) {
fds[0].fd = fd;
fds[0].events = POLLIN;
printf("Waiting for input...\n");
int ret = poll(fds, 1, -1); // 阻塞直到有事件发生
if (ret == -1) {
perror("poll error");
close(fd);
exit(EXIT_FAILURE);
} else if (ret > 0 && fds[0].revents & POLLIN) {
char buffer[128] = {0};
ssize_t bytes_read = read(fd, buffer, sizeof(buffer));
if (bytes_read >= 0) {
printf("Read from device: %s\n", buffer);
} else {
perror("Read error");
break;
}
}
sleep(1);
}
close(fd);
return EXIT_SUCCESS;
}
编译与加载模块
确保安装了 Linux 开发工具,然后可以使用以下命令编译并加载内核模块:
# 保存上面的内核模块代码到 my_char_device.c 中,然后运行:
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
# 加载模块
sudo insmod my_char_device.ko
# 检查设备是否创建成功,应该在 /dev 下看到对应的设备文件:
ls /dev/mychar
测试用户程序
运行用户程序之前,请确保您已经打开了终端以执行编译和加载操作。然后编译并运行上述用户空间代码:
gcc user_program.c -o user_program
./user_program &
现在您可以向 /dev/mychar
写入数据,例如通过另一个终端使用 echo
命令,如下所示:
echo "Hello World" > /dev/mychar
这样会触发应用层中的 poll()
通知,输出读取到的数据。
注意:这是一个基本示例。在实际开发中,请确保处理好错误情况和资源释放,以免造成内存泄漏或其他问题。
内容由零声教学AI助手提供,问题来源于学员提问