一、fasync_helper异步通知注册和取消函数
        
            
            
              c
              
              
            
          
          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. 函数原型和参数
            
            
              c
              
              
            
          
          int fasync_helper(int fd, struct file *filp, int on, struct fasync_struct **fapp)
        参数:
fd:文件描述符filp:文件结构指针on:启用或禁用异步通知(1=启用,0=禁用)fapp:指向异步通知结构链表头指针的指针
2. 第1部分:变量声明和内存分配
            
            
              c
              
              
            
          
          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部分:加锁和链表遍历
            
            
              c
              
              
            
          
          write_lock_irq(&fasync_lock);
for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) {
        分析:
write_lock_irq(&fasync_lock):获取写锁并禁用中断,保护全局的异步通知链表- 遍历异步通知链表,查找是否已经为该文件注册过异步通知
 
4. 第3部分:找到现有条目的处理
            
            
              c
              
              
            
          
          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:启用通知,但条目已存在
            
            
              c
              
              
            
          
          if(on) {
    fa->fa_fd = fd;                    // 更新文件描述符
    kmem_cache_free(fasync_cache, new); // 释放预分配的内存
}
        - 只需更新现有的文件描述符
 - 释放之前预分配但未使用的内存
 
4.2. 情况2:禁用通知,且条目存在
            
            
              c
              
              
            
          
          } else {
    *fp = fa->fa_next;                // 从链表中移除
    kmem_cache_free(fasync_cache, fa); // 释放条目内存
    result = 1;                       // 返回成功
}
        - 将当前条目从链表中移除
 - 释放对应的内存
 - 返回 1 表示成功移除
 
5. 第4部分:添加新条目
            
            
              c
              
              
            
          
          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部分:清理和返回
            
            
              c
              
              
            
          
          out:
    write_unlock_irq(&fasync_lock);
    return result;
        分析:
- 释放保护锁并恢复中断
 - 返回操作结果
 
7. 数据结构分析
7.1. fasync_struct 结构
        
            
            
              c
              
              
            
          
          struct fasync_struct {
        int     magic;  // 魔术字 FASYNC_MAGIC
        int     fa_fd;  // 文件描述符
        struct  fasync_struct   *fa_next; /* 单链表 */
        struct  file            *fa_file; // 文件结构指针
};
        8.完整执行流程
是 否 是 否 是 否 是 否 开始 fasync_helper on=1? 分配新结构 不加分配 加锁遍历链表 找到filp条目? on=1? on=1? 更新fd, 释放新结构 移除条目, 释放内存 添加新条目到链表头 无操作 返回0 返回1 返回0 释放锁返回
这个函数是 Linux 异步 I/O 通知机制的基础构建块,确保了多个进程可以安全地注册和取消注册对同一文件的异步通知
二、kill_fasync异步通知信号发送函数
        
            
            
              c
              
              
            
          
          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 a
                   queued signum: SIGURG has its own default signalling
                   mechanism. */
                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() 函数详解
        
            
            
              c
              
              
            
          
          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 - 如果不匹配,打印错误信息并立即返回
 
            
            
              c
              
              
            
          
                          // 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有自己默认的信号处理机制 
            
            
              c
              
              
            
          
                          // 4. 移动到下一个节点
                fa = fa->fa_next;
        }
}
        链表遍历:
- 循环遍历整个 
fasync_struct链表 - 对每个注册的进程发送信号
 
3. kill_fasync() 函数详解
        
            
            
              c
              
              
            
          
          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. 快速路径检查
            
            
              c
              
              
            
          
          if (*fp) {
        - 在加锁前先检查链表是否为空
 - 这是重要的性能优化,因为大多数情况下异步通知链表是空的
 - 避免不必要的加锁操作
 
3.2. 锁保护
            
            
              c
              
              
            
          
          read_lock(&fasync_lock);
__kill_fasync(*fp, sig, band);
read_unlock(&fasync_lock);
        - 使用读锁(
read_lock)保护链表遍历 - 读锁允许多个 
kill_fasync调用并发执行,只要没有修改操作 - 在锁内重新读取 
*fp,确保获取的是最新值 
4. 参数说明
4.1. 信号参数
            
            
              c
              
              
            
          
          int sig, int band
        sig:要发送的信号,通常是:SIGIO:通用异步 I/O 通知SIGURG:紧急数据通知
band:事件类型,通常是:POLL_IN:数据可读POLL_OUT:数据可写POLL_PRI:紧急数据可读
三、案例文件之scull.h
        
            
            
              c
              
              
            
          
          #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
#endif
struct 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
        
            
            
              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. 核心数据结构
            
            
              c
              
              
            
          
          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() - 打开设备
            
            
              c
              
              
            
          
          static int scull_p_open(struct inode *inode, struct file *filp)
        - 获取设备结构
 - 分配缓冲区(首次打开时)
 - 初始化读写指针
 - 更新读者/写者计数
 - 设置为不可定位设备
 
scull_p_release() - 关闭设备
            
            
              c
              
              
            
          
          static int scull_p_release(struct inode *inode, struct file *filp)
        - 移除异步通知注册
 - 更新读者/写者计数
 - 释放缓冲区(最后一个用户关闭时)
 
2.3. 读写操作函数
scull_p_write() - 写数据
            
            
              c
              
              
            
          
          static ssize_t scull_p_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
        - 获取信号量
 - 检查写入空间(可能睡眠等待)
 - 计算可写入数据量
 - 从用户空间拷贝数据
 - 更新写指针
 - 唤醒等待的读者
 - 发送异步通知
 
scull_p_read() - 读数据
            
            
              c
              
              
            
          
          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() - 异步通知注册
            
            
              c
              
              
            
          
          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. 异步通知触发 - 在写操作中
            
            
              c
              
              
            
          
          /* 在 scull_p_write() 函数中 */
if (dev->async_queue)
    kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
        触发条件:
- 当有数据写入缓冲区时
 - 检查 
async_queue不为空(有进程注册了异步通知) - 发送 
SIGIO信号,附带POLL_IN事件(数据可读) 
2.6. 异步通知清理 - 在释放操作中
            
            
              c
              
              
            
          
          /* 在 scull_p_release() 函数中 */
scull_p_fasync(-1, filp, 0);  // 取消异步通知注册
        3. 异步通知完整工作流程
用户进程 内核 scullp驱动 写入进程 open("/dev/scullp") fcntl(F_SETFL | FASYNC) scull_p_fasync(1) 添加到async_queue 注册SIGIO处理函数 write(data) 数据写入缓冲区 kill_fasync(SIGIO, POLL_IN) 发送SIGIO信号 调用信号处理函数 read() 读取数据 用户进程 内核 scullp驱动 写入进程
4. 用户空间测试对应关系
- 
注册阶段:
cfcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | FASYNC);→ 调用
scull_p_fasync(1) - 
通知阶段:
cecho "data" > /dev/scullp→ 调用
scull_p_write()→kill_fasync() - 
清理阶段:
cclose(fd);→ 调用
scull_p_release()→scull_p_fasync(0) 
五、模块编译文件Makefile
 用于编译ko模块的配置文件
            
            
              makefile
              
              
            
          
          ifneq ($(KERNELRELEASE),)
        # 在内核构建系统中(由 kbuild 调用时)
        obj-m := scullp.o
else
        KERNELDIR ?= /lib/modules/$(shell uname -r)/build
        PWD := $(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) modules
endif
clean:
        @echo "[DEBUG] 正在清理编译文件..."
        $(MAKE) -C $(KERNELDIR) M=$(PWD) clean
        rm -f *.ko *.mod.c *.mod.o *.o .tmp_versions
         在源码同级目录创建Makefile文件并执行make命令,即可获得目标scullp.ko文件,文件具体含义见文章开头的参考博客
六、模块加载和卸载脚本
1.加载模块
 加载scullp.ko模块的脚本scull_load如下,因为我们如果简单使用insmod命令进行加载的话还需要在/dev目录下手动创建字符设备节点,现在这个脚本把这些工作一起完成了,记得给脚本赋予执行权限,执行命令:
            
            
              shell
              
              
            
          
          sudo chmod 744 scull_load
sudo ./scull_load
        
            
            
              shell
              
              
            
          
          #!/bin/sh
module="scullp"
device="scullp"
mode="666"
if grep -q '^staff:' /etc/group; then
    group="staff"
else
    group="wheel"
fi
/sbin/insmod ./$module.ko $* || exit 1
major=$(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]
         下面对脚本的内容进行详细解释
变量定义
            
            
              shell
              
              
            
          
          module="scullp"
        module="scullp": 定义变量module的值为"scullp",表示内核模块的名称(不含.ko扩展名)
            
            
              shell
              
              
            
          
          device="scullp"
        device="scullp": 定义变量device的值为"scullp",表示设备文件的名称前缀
            
            
              shell
              
              
            
          
          mode="664"
        mode="664": 定义变量mode的值为"664",表示设备文件的权限位:6(rw-):所有者有读写权限6(rw-):组用户有读写权限4(r--):其他用户只有读权限
确定用户组
            
            
              shell
              
              
            
          
          if grep -q '^staff:' /etc/group; then
        grep -q '^staff:' /etc/group: 使用grep静默模式 (-q) 查找/etc/group文件中以staff:开头的行^staff::^表示行首,查找精确匹配staff:的组- 如果找到返回真(条件成立)
 
            
            
              shell
              
              
            
          
              group="staff"
        - 如果找到 
staff组,设置group变量为"staff" 
            
            
              shell
              
              
            
          
          else
    group="wheel"
fi
        - 
如果没有找到
staff组,设置group变量为"wheel" - 
fi: 结束 if 条件语句不同的 Linux 发行版使用不同的默认用户组,这里尝试兼容两种常见情况。
 
加载内核模块
            
            
              shell
              
              
            
          
          /sbin/insmod ./$module.ko $* || exit 1
        /sbin/insmod: 使用绝对路径调用insmod命令(加载内核模块)./$module.ko:./scullp.ko- 当前目录下的模块文件$*: 所有传递给脚本的命令行参数(可以传递模块参数)|| exit 1: 或操作 ,如果前面的命令失败(返回非零),则执行exit 1退出脚本并返回错误码 1
获取主设备号
            
            
              shell
              
              
            
          
          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 提前解释
 
- 解析 
 
清理旧的设备文件
            
            
              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
创建设备节点
            
            
              shell
              
              
            
          
          mknod /dev/${device}0 c $major 0
        mknod: 创建设备特殊文件/dev/${device}0:/dev/scullp0- 设备文件路径c: 字符设备类型$major: 主设备号(从/proc/devices获取)0: 次设备号(第一个设备)
            
            
              shell
              
              
            
          
          mknod /dev/${device}1 c $major 1
mknod /dev/${device}2 c $major 2  
mknod /dev/${device}3 c $major 3
        - 同样方式创建另外三个设备节点,次设备号分别为 1,2,3
 
创建符号链接
            
            
              shell
              
              
            
          
          ln -sf ${device}0 /dev/${device}
        ln -sf: 创建软链接(符号链接),-f强制覆盖已存在的链接${device}0:scullp0- 链接目标/dev/${device}:/dev/scullp- 链接名称- 作用 : 创建 
/dev/scullp指向/dev/scullp0,提供默认设备访问 
设置设备文件权限
            
            
              shell
              
              
            
          
          chgrp $group /dev/${device}[0-3] 
        chgrp: 改变文件组所有权$group:staff或wheel(之前确定的组)/dev/${device}[0-3]:/dev/scullp[0-3]- 所有四个设备文件- 作用: 让指定组的用户也能访问这些设备
 
            
            
              shell
              
              
            
          
          chmod $mode  /dev/${device}[0-3]
        - 
chmod: 改变文件权限 - 
$mode:664- 之前设置的权限 - 
/dev/${device}[0-3]: 所有四个设备文件 - 
作用: 设置具体的读写权限
 
2.卸载模块
	卸载`scullp.ko`模块的脚本`scull_unload`同样如此,记得给脚本赋予执行权限,执行命令:
        
            
            
              shell
              
              
            
          
          sudo chmod 744 scull_unload
sudo ./scull_unload
        
            
            
              shell
              
              
            
          
          #!/bin/sh
module="scullp"
device="scullp"
/sbin/rmmod $module $* || exit 1
rm -f /dev/${device} /dev/${device}[0-3] 
        3.验证模块
 确认内核已创建字符设备scullp
            
            
              shell
              
              
            
          
          cat /proc/devices  | grep scullp
         预期有类似如下输出
            
            
              text
              
              
            
          
          253 scullp
         确认用户空间设备文件scullp0-3已创建
            
            
              shell
              
              
            
          
          ls /dev/scull*
         预期有类似如下输出
            
            
              text
              
              
            
          
          /dev/scullp  /dev/scullp0  /dev/scullp1  /dev/scullp2  /dev/scullp3
         确认字符设备读写功能正常
            
            
              shell
              
              
            
          
          echo "test" > /dev/scullp
cat /dev/scullp
         预期有如下输出
            
            
              text
              
              
            
          
          test
        七、scullp模块fasync功能测试
1.测试代码
            
            
              c
              
              
            
          
          #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.编译执行验证
编译
            
            
              shell
              
              
            
          
          gcc test_scullp.c -o test_scullp
        运行
            
            
              shell
              
              
            
          
          ./test_scullp
        验证,另一个终端输入
            
            
              shell
              
              
            
          
          echo "test" >  /dev/scullp
        当前终端预期看到如下输出
            
            
              text
              
              
            
          
          Waiting for data on /dev/scullp...
Read: test
        八、标准输入输出fasync功能测试
1.测试代码
            
            
              c
              
              
            
          
          #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. 编译并执行
            
            
              shell
              
              
            
          
          gcc fasync_test.c -o fasync_test
./fasync_test
        终端输入
            
            
              text
              
              
            
          
          test
        预期输出
            
            
              test
              
              
            
          
          test