Linux中驱动程序通过fasync异步通知应用程序的实现
一、fasync_helper
异步通知注册和取消函数
struct fasync_struct {int magic;int fa_fd;struct fasync_struct *fa_next; /* singly linked list */struct file *fa_file;
};
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
{struct fasync_struct *fa, **fp;struct fasync_struct *new = NULL;int result = 0;if (on) {new = kmem_cache_alloc(fasync_cache, SLAB_KERNEL);if (!new)return -ENOMEM;}write_lock_irq(&fasync_lock);for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) {if (fa->fa_file == filp) {if(on) {fa->fa_fd = fd;kmem_cache_free(fasync_cache, new);} else {*fp = fa->fa_next;kmem_cache_free(fasync_cache, fa);result = 1;}goto out;}}if (on) {new->magic = FASYNC_MAGIC;new->fa_file = filp;new->fa_fd = fd;new->fa_next = *fapp;*fapp = new;result = 1;}
out:write_unlock_irq(&fasync_lock);return result;
}
1. 函数原型和参数
int fasync_helper(int fd, struct file *filp, int on, struct fasync_struct **fapp)
参数:
fd
:文件描述符filp
:文件结构指针on
:启用或禁用异步通知(1=启用,0=禁用)fapp
:指向异步通知结构链表头指针的指针
2. 第1部分:变量声明和内存分配
struct fasync_struct *fa, **fp;
struct fasync_struct *new = NULL;
int result = 0;if (on) {new = kmem_cache_alloc(fasync_cache, SLAB_KERNEL);if (!new)return -ENOMEM;
}
分析:
- 如果启用异步通知(
on = 1
),预先分配一个新的fasync_struct
- 使用
kmem_cache_alloc
从专用的 slab 缓存分配内存,提高性能 - 如果内存分配失败,返回
-ENOMEM
3. 第2部分:加锁和链表遍历
write_lock_irq(&fasync_lock);
for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) {
分析:
write_lock_irq(&fasync_lock)
:获取写锁并禁用中断,保护全局的异步通知链表- 遍历异步通知链表,查找是否已经为该文件注册过异步通知
4. 第3部分:找到现有条目的处理
if (fa->fa_file == filp) {if(on) {fa->fa_fd = fd;kmem_cache_free(fasync_cache, new);} else {*fp = fa->fa_next;kmem_cache_free(fasync_cache, fa);result = 1;}goto out;
}
4.1. 情况1:启用通知,但条目已存在
if(on) {fa->fa_fd = fd; // 更新文件描述符kmem_cache_free(fasync_cache, new); // 释放预分配的内存
}
- 只需更新现有的文件描述符
- 释放之前预分配但未使用的内存
4.2. 情况2:禁用通知,且条目存在
} else {*fp = fa->fa_next; // 从链表中移除kmem_cache_free(fasync_cache, fa); // 释放条目内存result = 1; // 返回成功
}
- 将当前条目从链表中移除
- 释放对应的内存
- 返回 1 表示成功移除
5. 第4部分:添加新条目
if (on) {new->magic = FASYNC_MAGIC;new->fa_file = filp;new->fa_fd = fd;new->fa_next = *fapp;*fapp = new;result = 1;
}
分析:
- 只有启用通知且条目不存在时才执行
- 初始化新条目:
magic
:魔术字,用于调试和验证fa_file
:关联的文件结构fa_fd
:文件描述符fa_next
:指向链表下一个条目
- 将新条目插入链表头部
- 返回 1 表示成功添加
6. 第5部分:清理和返回
out:write_unlock_irq(&fasync_lock);return result;
分析:
- 释放保护锁并恢复中断
- 返回操作结果
7. 数据结构分析
7.1. fasync_struct
结构
struct fasync_struct {int magic; // 魔术字 FASYNC_MAGICint fa_fd; // 文件描述符struct fasync_struct *fa_next; /* 单链表 */struct file *fa_file; // 文件结构指针
};
8.完整执行流程
这个函数是 Linux 异步 I/O 通知机制的基础构建块,确保了多个进程可以安全地注册和取消注册对同一文件的异步通知
二、kill_fasync
异步通知信号发送函数
void __kill_fasync(struct fasync_struct *fa, int sig, int band)
{while (fa) {struct fown_struct * fown;if (fa->magic != FASYNC_MAGIC) {printk(KERN_ERR "kill_fasync: bad magic number in ""fasync_struct!\n");return;}fown = &fa->fa_file->f_owner;/* Don't send SIGURG to processes which have not set aqueued signum: SIGURG has its own default signallingmechanism. */if (!(sig == SIGURG && fown->signum == 0))send_sigio(fown, fa->fa_fd, band);fa = fa->fa_next;}
}void kill_fasync(struct fasync_struct **fp, int sig, int band)
{/* First a quick test without locking: usually* the list is empty.*/if (*fp) {read_lock(&fasync_lock);/* reread *fp after obtaining the lock */__kill_fasync(*fp, sig, band);read_unlock(&fasync_lock);}
}
1. 函数概述
__kill_fasync()
:实际遍历链表并发送信号的内部函数kill_fasync()
:对外接口,处理锁保护和快速检查
2. __kill_fasync()
函数详解
void __kill_fasync(struct fasync_struct *fa, int sig, int band)
{while (fa) {struct fown_struct * fown;// 1. 魔术字验证if (fa->magic != FASYNC_MAGIC) {printk(KERN_ERR "kill_fasync: bad magic number in ""fasync_struct!\n");return;}
魔术字验证:
- 检查
fasync_struct
的魔术字是否匹配FASYNC_MAGIC
- 如果不匹配,打印错误信息并立即返回
// 2. 获取文件所有者信息fown = &fa->fa_file->f_owner;// 3. SIGURG 特殊处理if (!(sig == SIGURG && fown->signum == 0))send_sigio(fown, fa->fa_fd, band);
SIGURG 特殊逻辑:
SIGURG
用于带外数据(out-of-band data)通知- 如果信号是
SIGURG
且进程没有设置自定义信号(fown->signum == 0
),则不发送信号 - 这是因为
SIGURG
有自己默认的信号处理机制
// 4. 移动到下一个节点fa = fa->fa_next;}
}
链表遍历:
- 循环遍历整个
fasync_struct
链表 - 对每个注册的进程发送信号
3. kill_fasync()
函数详解
void kill_fasync(struct fasync_struct **fp, int sig, int band)
{/* First a quick test without locking: usually* the list is empty.*/if (*fp) {read_lock(&fasync_lock);/* reread *fp after obtaining the lock */__kill_fasync(*fp, sig, band);read_unlock(&fasync_lock);}
}
3.1. 快速路径检查
if (*fp) {
- 在加锁前先检查链表是否为空
- 这是重要的性能优化,因为大多数情况下异步通知链表是空的
- 避免不必要的加锁操作
3.2. 锁保护
read_lock(&fasync_lock);
__kill_fasync(*fp, sig, band);
read_unlock(&fasync_lock);
- 使用读锁(
read_lock
)保护链表遍历 - 读锁允许多个
kill_fasync
调用并发执行,只要没有修改操作 - 在锁内重新读取
*fp
,确保获取的是最新值
4. 参数说明
4.1. 信号参数
int sig, int band
sig
:要发送的信号,通常是:SIGIO
:通用异步 I/O 通知SIGURG
:紧急数据通知
band
:事件类型,通常是:POLL_IN
:数据可读POLL_OUT
:数据可写POLL_PRI
:紧急数据可读
三、案例文件之scull.h
#ifndef _SCULL_H_
#define _SCULL_H_#ifndef SCULL_P_NR_DEVS
#define SCULL_P_NR_DEVS 4 /* scullpipe0 through scullpipe3 */
#endif#ifndef SCULL_P_BUFFER
#define SCULL_P_BUFFER 4000
#endifstruct scull_dev {struct scull_qset *data;int quantum;int qset;unsigned long size;unsigned int access_key;struct semaphore sem;struct cdev cdev;
};#endif /* _SCULL_H_ */
定义字符设备常量和scull_dev
结构体
四、案例文件之scullp.c
#include <linux/config.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>#include <linux/kernel.h> /* printk() */
#include <linux/slab.h> /* kmalloc() */
#include <linux/fs.h> /* everything... */
#include <linux/errno.h> /* error codes */
#include <linux/types.h> /* size_t */
#include <linux/fcntl.h> /* O_ACCMODE */
#include <linux/cdev.h>#include <asm/system.h> /* cli(), *_flags */
#include <asm/uaccess.h> /* copy_*_user */#include "scull.h" /* local definitions */struct scull_pipe {wait_queue_head_t inq, outq; /* read and write queues */char *buffer, *end; /* begin of buf, end of buf */int buffersize; /* used in pointer arithmetic */char *rp, *wp; /* where to read, where to write */int nreaders, nwriters; /* number of openings for r/w */struct fasync_struct *async_queue; /* asynchronous readers */struct semaphore sem; /* mutual exclusion semaphore */struct cdev cdev; /* Char device structure */
};static int scull_p_nr_devs = SCULL_P_NR_DEVS; /* number of pipe devices */
int scull_p_buffer = SCULL_P_BUFFER; /* buffer size */
dev_t scull_p_devno; /* Our first device number */module_param(scull_p_nr_devs, int, 0);
module_param(scull_p_buffer, int, 0);static struct scull_pipe *scull_p_devices;static int scull_p_fasync(int fd, struct file *filp, int mode);
static int spacefree(struct scull_pipe *dev);MODULE_LICENSE("Dual BSD/GPL");struct scull_dev *scull_devices; /* allocated in scull_init_module *//** Open and close*/static int scull_p_open(struct inode *inode, struct file *filp)
{struct scull_pipe *dev;dev = container_of(inode->i_cdev, struct scull_pipe, cdev);filp->private_data = dev;if (down_interruptible(&dev->sem))return -ERESTARTSYS;if (!dev->buffer) {/* allocate the buffer */dev->buffer = kmalloc(scull_p_buffer, GFP_KERNEL);if (!dev->buffer) {up(&dev->sem);return -ENOMEM;}}dev->buffersize = scull_p_buffer;dev->end = dev->buffer + dev->buffersize;dev->rp = dev->wp = dev->buffer; /* rd and wr from the beginning */if (filp->f_mode & FMODE_READ)dev->nreaders++;if (filp->f_mode & FMODE_WRITE)dev->nwriters++;up(&dev->sem);/** This is used by subsystems that don't want seekable* file descriptors*/return nonseekable_open(inode, filp);
}static int scull_p_release(struct inode *inode, struct file *filp)
{struct scull_pipe *dev = filp->private_data;/* remove this filp from the asynchronously notified filp's */scull_p_fasync(-1, filp, 0);down(&dev->sem);if (filp->f_mode & FMODE_READ)dev->nreaders--;if (filp->f_mode & FMODE_WRITE)dev->nwriters--;if (dev->nreaders + dev->nwriters == 0) {kfree(dev->buffer);dev->buffer = NULL; /* the other fields are not checked on open */}up(&dev->sem);return 0;
}static int scull_p_fasync(int fd, struct file *filp, int mode)
{struct scull_pipe *dev = filp->private_data;return fasync_helper(fd, filp, mode, &dev->async_queue);
}static int scull_getwritespace(struct scull_pipe *dev, struct file *filp)
{while (spacefree(dev) == 0) { /* full */DEFINE_WAIT(wait);up(&dev->sem);if (filp->f_flags & O_NONBLOCK)return -EAGAIN;printk("\"%s\" writing: going to sleep\n",current->comm);prepare_to_wait(&dev->outq, &wait, TASK_INTERRUPTIBLE);if (spacefree(dev) == 0)schedule();finish_wait(&dev->outq, &wait);if (signal_pending(current))return -ERESTARTSYS; /* signal: tell the fs layer to handle it */if (down_interruptible(&dev->sem))return -ERESTARTSYS;}return 0;
} /* How much space is free? */
static int spacefree(struct scull_pipe *dev)
{if (dev->rp == dev->wp)return dev->buffersize - 1;return ((dev->rp + dev->buffersize - dev->wp) % dev->buffersize) - 1;
}static ssize_t scull_p_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos)
{struct scull_pipe *dev = filp->private_data;int result;if (down_interruptible(&dev->sem))return -ERESTARTSYS;/* Make sure there's space to write */result = scull_getwritespace(dev, filp);if (result)return result; /* scull_getwritespace called up(&dev->sem) *//* ok, space is there, accept something */count = min(count, (size_t)spacefree(dev));if (dev->wp >= dev->rp)count = min(count, (size_t)(dev->end - dev->wp)); /* to end-of-buf */else /* the write pointer has wrapped, fill up to rp-1 */count = min(count, (size_t)(dev->rp - dev->wp - 1));printk("Going to accept %li bytes to %p from %p\n", (long)count, dev->wp, buf);if (copy_from_user(dev->wp, buf, count)) {up (&dev->sem);return -EFAULT;}dev->wp += count;if (dev->wp == dev->end)dev->wp = dev->buffer; /* wrapped */up(&dev->sem);/* finally, awake any reader */wake_up_interruptible(&dev->inq);if (dev->async_queue)kill_fasync(&dev->async_queue, SIGIO, POLL_IN);printk("\"%s\" did write %li bytes\n",current->comm, (long)count);return count;
}static ssize_t scull_p_read (struct file *filp, char __user *buf, size_t count,loff_t *f_pos)
{struct scull_pipe *dev = filp->private_data;if (down_interruptible(&dev->sem))return -ERESTARTSYS;while (dev->rp == dev->wp) { /* nothing to read */up(&dev->sem); /* release the lock */if (filp->f_flags & O_NONBLOCK)return -EAGAIN;printk("\"%s\" reading: going to sleep\n", current->comm);if (wait_event_interruptible(dev->inq, (dev->rp != dev->wp)))return -ERESTARTSYS; /* signal: tell the fs layer to handle it *//* otherwise loop, but first reacquire the lock */if (down_interruptible(&dev->sem))return -ERESTARTSYS;}/* ok, data is there, return something */if (dev->wp > dev->rp)count = min(count, (size_t)(dev->wp - dev->rp));else /* the write pointer has wrapped, return data up to dev->end */count = min(count, (size_t)(dev->end - dev->rp));if (copy_to_user(buf, dev->rp, count)) {up (&dev->sem);return -EFAULT;}dev->rp += count;if (dev->rp == dev->end)dev->rp = dev->buffer; /* wrapped */up (&dev->sem);/* finally, awake any writers and return */wake_up_interruptible(&dev->outq);printk("\"%s\" did read %li bytes\n",current->comm, (long)count);return count;
}struct file_operations scull_pipe_fops = {.owner = THIS_MODULE,.llseek = no_llseek,.read = scull_p_read,.write = scull_p_write,.open = scull_p_open,.release = scull_p_release,.fasync = scull_p_fasync,
};void scull_p_cleanup(void)
{int i;if (!scull_p_devices)return; /* nothing else to release */for (i = 0; i < scull_p_nr_devs; i++) {cdev_del(&scull_p_devices[i].cdev);kfree(scull_p_devices[i].buffer);}kfree(scull_p_devices);unregister_chrdev_region(scull_p_devno, scull_p_nr_devs);scull_p_devices = NULL; /* pedantic */
}
/** The cleanup function is used to handle initialization failures as well.* Thefore, it must be careful to work correctly even if some of the items* have not been initialized*/
void scull_cleanup_module(void)
{scull_p_cleanup();
}/** Set up the char_dev structure for this device.*/
static void scull_p_setup_cdev(struct scull_pipe *dev, int index)
{int err, devno = scull_p_devno + index;cdev_init(&dev->cdev, &scull_pipe_fops);dev->cdev.owner = THIS_MODULE;err = cdev_add (&dev->cdev, devno, 1);/* Fail gracefully if need be */if (err)printk(KERN_NOTICE "Error %d adding scullpipe%d", err, index);
}int scull_p_init(dev_t firstdev)
{int i, result;result = alloc_chrdev_region(&firstdev, 0, scull_p_nr_devs,"scullp");printk(KERN_NOTICE "alloc_chrdev_region result=%d\n", result);if (result < 0) {printk(KERN_NOTICE "Unable to get scullp region, error %d\n", result);return 0;}scull_p_devno = firstdev;scull_p_devices = kmalloc(scull_p_nr_devs * sizeof(struct scull_pipe), GFP_KERNEL);if (scull_p_devices == NULL) {unregister_chrdev_region(firstdev, scull_p_nr_devs);return 0;}memset(scull_p_devices, 0, scull_p_nr_devs * sizeof(struct scull_pipe));for (i = 0; i < scull_p_nr_devs; i++) {init_waitqueue_head(&(scull_p_devices[i].inq));init_waitqueue_head(&(scull_p_devices[i].outq));init_MUTEX(&scull_p_devices[i].sem);scull_p_setup_cdev(scull_p_devices + i, i);}printk(KERN_NOTICE "scull_p_setup_cdev success\n");return scull_p_nr_devs;
}int scull_init_module(void)
{dev_t dev = 0;return scull_p_init(dev) ? 0 : -1;
}module_init(scull_init_module);
module_exit(scull_cleanup_module);
这是一个字符设备驱动程序,实现了类似管道的功能,支持异步通知机制
1. 核心数据结构
struct scull_pipe {wait_queue_head_t inq, outq; /* 读写等待队列 */char *buffer, *end; /* 缓冲区起始和结束 */int buffersize; /* 缓冲区大小 */char *rp, *wp; /* 读指针和写指针 */int nreaders, nwriters; /* 读写者计数 */struct fasync_struct *async_queue; /* 异步读者队列 */struct semaphore sem; /* 互斥信号量 */struct cdev cdev; /* 字符设备结构 */
};
2. 函数功能详解
2.1. 设备管理函数
scull_p_init()
- 设备初始化
- 分配设备号区域
- 分配设备内存
- 初始化每个设备的等待队列和信号量
- 注册字符设备
scull_p_setup_cdev()
- 设置字符设备
- 初始化
cdev
结构 - 关联文件操作函数集
- 添加到系统
scull_p_cleanup()
- 清理函数
- 删除字符设备
- 释放缓冲区内存
- 释放设备内存
- 注销设备号
2.2. 文件操作函数
scull_p_open()
- 打开设备
static int scull_p_open(struct inode *inode, struct file *filp)
- 获取设备结构
- 分配缓冲区(首次打开时)
- 初始化读写指针
- 更新读者/写者计数
- 设置为不可定位设备
scull_p_release()
- 关闭设备
static int scull_p_release(struct inode *inode, struct file *filp)
- 移除异步通知注册
- 更新读者/写者计数
- 释放缓冲区(最后一个用户关闭时)
2.3. 读写操作函数
scull_p_write()
- 写数据
static ssize_t scull_p_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
- 获取信号量
- 检查写入空间(可能睡眠等待)
- 计算可写入数据量
- 从用户空间拷贝数据
- 更新写指针
- 唤醒等待的读者
- 发送异步通知
scull_p_read()
- 读数据
static ssize_t scull_p_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
- 获取信号量
- 等待数据可读(可能睡眠)
- 计算可读取数据量
- 拷贝数据到用户空间
- 更新读指针
- 唤醒等待的写者
2.4. 辅助函数
spacefree()
- 计算空闲空间
- 计算缓冲区中可用的空闲字节数
- 处理环形缓冲区边界情况
scull_getwritespace()
- 获取写入空间
- 等待直到有足够的写入空间
- 支持非阻塞模式
- 处理信号中断
scull_p_fasync()
- 异步通知注册
static int scull_p_fasync(int fd, struct file *filp, int mode)
{struct scull_pipe *dev = filp->private_data;return fasync_helper(fd, filp, mode, &dev->async_queue);
}
功能:
- 注册或取消注册异步通知
- 调用标准
fasync_helper
管理异步队列 mode=1
:启用异步通知mode=0
:禁用异步通知
调用时机:
- 用户空间调用
fcntl(fd, F_SETFL, flags | FASYNC)
时 - 设备关闭时自动取消注册
2.5. 异步通知触发 - 在写操作中
/* 在 scull_p_write() 函数中 */
if (dev->async_queue)kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
触发条件:
- 当有数据写入缓冲区时
- 检查
async_queue
不为空(有进程注册了异步通知) - 发送
SIGIO
信号,附带POLL_IN
事件(数据可读)
2.6. 异步通知清理 - 在释放操作中
/* 在 scull_p_release() 函数中 */
scull_p_fasync(-1, filp, 0); // 取消异步通知注册
3. 异步通知完整工作流程
4. 用户空间测试对应关系
-
注册阶段:
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | FASYNC);
→ 调用
scull_p_fasync(1)
-
通知阶段:
echo "data" > /dev/scullp
→ 调用
scull_p_write()
→kill_fasync()
-
清理阶段:
close(fd);
→ 调用
scull_p_release()
→scull_p_fasync(0)
五、模块编译文件Makefile
用于编译ko模块的配置文件
ifneq ($(KERNELRELEASE),)# 在内核构建系统中(由 kbuild 调用时)obj-m := scullp.o
elseKERNELDIR ?= /lib/modules/$(shell uname -r)/buildPWD := $(shell pwd)default:@echo "[DEBUG] 正在执行内核模块编译..."@echo "[DEBUG] MAKE = $(MAKE)" # 打印 MAKE 变量@echo "[DEBUG] uname -r = $(shell uname -r)" # 打印当前内核版本@echo "[DEBUG] KERNELRELEASE = $(KERNELRELEASE)"$(MAKE) -C $(KERNELDIR) M=$(PWD) modulesendifclean:@echo "[DEBUG] 正在清理编译文件..."$(MAKE) -C $(KERNELDIR) M=$(PWD) cleanrm -f *.ko *.mod.c *.mod.o *.o .tmp_versions
在源码同级目录创建Makefile
文件并执行make
命令,即可获得目标scullp.ko
文件,文件具体含义见文章开头的参考博客
六、模块加载和卸载脚本
1.加载模块
加载scullp.ko
模块的脚本scull_load
如下,因为我们如果简单使用insmod
命令进行加载的话还需要在/dev
目录下手动创建字符设备节点,现在这个脚本把这些工作一起完成了,记得给脚本赋予执行权限,执行命令:
sudo chmod 744 scull_load
sudo ./scull_load
#!/bin/sh
module="scullp"
device="scullp"
mode="666"if grep -q '^staff:' /etc/group; thengroup="staff"
elsegroup="wheel"
fi/sbin/insmod ./$module.ko $* || exit 1major=$(awk "\$2==\"$module\" {print \$1}" /proc/devices)rm -f /dev/${device}[0-3]
mknod /dev/${device}0 c $major 0
mknod /dev/${device}1 c $major 1
mknod /dev/${device}2 c $major 2
mknod /dev/${device}3 c $major 3
ln -sf ${device}0 /dev/${device}
chgrp $group /dev/${device}[0-3]
chmod $mode /dev/${device}[0-3]
下面对脚本的内容进行详细解释
变量定义
module="scullp"
module="scullp"
: 定义变量module
的值为"scullp"
,表示内核模块的名称(不含.ko
扩展名)
device="scullp"
device="scullp"
: 定义变量device
的值为"scullp"
,表示设备文件的名称前缀
mode="664"
mode="664"
: 定义变量mode
的值为"664"
,表示设备文件的权限位:6
(rw-
):所有者有读写权限6
(rw-
):组用户有读写权限4
(r--
):其他用户只有读权限
确定用户组
if grep -q '^staff:' /etc/group; then
grep -q '^staff:' /etc/group
: 使用grep
静默模式 (-q
) 查找/etc/group
文件中以staff:
开头的行^staff:
:^
表示行首,查找精确匹配staff:
的组- 如果找到返回真(条件成立)
group="staff"
- 如果找到
staff
组,设置group
变量为"staff"
elsegroup="wheel"
fi
-
如果没有找到
staff
组,设置group
变量为"wheel"
-
fi
: 结束 if 条件语句不同的 Linux 发行版使用不同的默认用户组,这里尝试兼容两种常见情况。
加载内核模块
/sbin/insmod ./$module.ko $* || exit 1
/sbin/insmod
: 使用绝对路径调用insmod
命令(加载内核模块)./$module.ko
:./scullp.ko
- 当前目录下的模块文件$*
: 所有传递给脚本的命令行参数(可以传递模块参数)|| exit 1
: 或操作,如果前面的命令失败(返回非零),则执行exit 1
退出脚本并返回错误码 1
获取主设备号
major=$(awk "\$2==\"$module\" {print \$1}" /proc/devices)
major=$(...)
: 命令替换,将命令输出赋值给major
变量awk "\$2==\"$module\" {print \$1}" /proc/devices
:- 解析
/proc/devices
文件(包含已注册的设备号) \$2==\"$module\"
: 当第二列等于 “scullp” 时{print \$1}
: 打印第一列(主设备号)- 反斜杠用于转义特殊字符,防止 shell 提前解释
- 解析
清理旧的设备文件
rm -f /dev/${device}[0-3]
rm -f
: 强制删除文件,不提示错误/dev/${device}[0-3]
:/dev/scullp[0-3]
- 删除/dev/scullp0
到/dev/scullp3
四个设备文件[0-3]
: shell 通配符,匹配 0,1,2,3
创建设备节点
mknod /dev/${device}0 c $major 0
mknod
: 创建设备特殊文件/dev/${device}0
:/dev/scullp0
- 设备文件路径c
: 字符设备类型$major
: 主设备号(从/proc/devices
获取)0
: 次设备号(第一个设备)
mknod /dev/${device}1 c $major 1
mknod /dev/${device}2 c $major 2
mknod /dev/${device}3 c $major 3
- 同样方式创建另外三个设备节点,次设备号分别为 1,2,3
创建符号链接
ln -sf ${device}0 /dev/${device}
ln -sf
: 创建软链接(符号链接),-f
强制覆盖已存在的链接${device}0
:scullp0
- 链接目标/dev/${device}
:/dev/scullp
- 链接名称- 作用: 创建
/dev/scullp
指向/dev/scullp0
,提供默认设备访问
设置设备文件权限
chgrp $group /dev/${device}[0-3]
chgrp
: 改变文件组所有权$group
:staff
或wheel
(之前确定的组)/dev/${device}[0-3]
:/dev/scullp[0-3]
- 所有四个设备文件- 作用: 让指定组的用户也能访问这些设备
chmod $mode /dev/${device}[0-3]
-
chmod
: 改变文件权限 -
$mode
:664
- 之前设置的权限 -
/dev/${device}[0-3]
: 所有四个设备文件 -
作用: 设置具体的读写权限
2.卸载模块
卸载`scullp.ko`模块的脚本`scull_unload`同样如此,记得给脚本赋予执行权限,执行命令:
sudo chmod 744 scull_unload
sudo ./scull_unload
#!/bin/sh
module="scullp"
device="scullp"/sbin/rmmod $module $* || exit 1rm -f /dev/${device} /dev/${device}[0-3]
3.验证模块
确认内核已创建字符设备scullp
cat /proc/devices | grep scullp
预期有类似如下输出
253 scullp
确认用户空间设备文件scullp0-3
已创建
ls /dev/scull*
预期有类似如下输出
/dev/scullp /dev/scullp0 /dev/scullp1 /dev/scullp2 /dev/scullp3
确认字符设备读写功能正常
echo "test" > /dev/scullp
cat /dev/scullp
预期有如下输出
test
七、scullp
模块fasync
功能测试
1.测试代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>volatile sig_atomic_t got_data = 0;void handle_sigio(int sig) {if (sig == SIGIO) got_data = 1;
}int main() {int fd;char buf[1024];ssize_t n;// 打开设备fd = open("/dev/scullp", O_RDONLY);if (fd < 0) {perror("open");exit(1);}// 设置信号处理signal(SIGIO, handle_sigio);fcntl(fd, F_SETOWN, getpid());fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | FASYNC);printf("Waiting for data on /dev/scullp...\n");while(1) {pause(); // 等待信号if (got_data) {while ((n = read(fd, buf, sizeof(buf)-1)) > 0) {buf[n] = 0;printf("Read: %s", buf);}got_data = 0;}}close(fd);return 0;
}
2.编译执行验证
编译
gcc test_scullp.c -o test_scullp
运行
./test_scullp
验证,另一个终端输入
echo "test" > /dev/scullp
当前终端预期看到如下输出
Waiting for data on /dev/scullp...
Read: test
八、标准输入输出fasync
功能测试
1.测试代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>int gotdata=0;
void sighandler(int signo)
{if (signo==SIGIO)gotdata++;return;
}char buffer[4096];int main(int argc, char **argv)
{int count;struct sigaction action;memset(&action, 0, sizeof(action));action.sa_handler = sighandler;action.sa_flags = 0;sigaction(SIGIO, &action, NULL);fcntl(STDIN_FILENO, F_SETOWN, getpid());fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | FASYNC);while(1) {/* this only returns if a signal arrives */sleep(86400); /* one day */if (!gotdata)continue;count=read(0, buffer, 4096);/* buggy: if avail data is more than 4kbytes... */write(1,buffer,count);memset(buffer, 0, 4096);gotdata=0;}
}
使用 SIGIO
信号来实现异步 I/O,当标准输入有数据可读时,信号处理函数会被调用,然后程序读取并回显数据
gotdata
:标志变量,表示有数据到达sighandler
:信号处理函数,收到SIGIO
时增加gotdata
计数buffer
:数据读取缓冲区
信号处理设置
- 使用
sigaction
注册SIGIO
信号的处理函数 sa_flags = 0
使用默认的信号处理行为
设置异步I/O
F_SETOWN
:设置接收SIGIO
信号的进程为当前进程F_SETFL
与FASYNC
:在标准输入上启用异步通知模式
工作流程:
- 睡眠很长时间(实际上会被信号中断)
- 如果
gotdata
被设置,读取输入数据 - 将数据写入标准输出
- 重置
gotdata
标志
2. 编译并执行
gcc fasync_test.c -o fasync_test
./fasync_test
终端输入
test
预期输出
test