mthoutai

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

一、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_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异步通知信号发送函数

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() 函数详解

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
#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

#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)
  1. 获取信号量
  2. 检查写入空间(可能睡眠等待)
  3. 计算可写入数据量
  4. 从用户空间拷贝数据
  5. 更新写指针
  6. 唤醒等待的读者
  7. 发送异步通知

scull_p_read() - 读数据

static ssize_t scull_p_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
  1. 获取信号量
  2. 等待数据可读(可能睡眠)
  3. 计算可读取数据量
  4. 拷贝数据到用户空间
  5. 更新读指针
  6. 唤醒等待的写者

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. 异步通知完整工作流程

用户进程内核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. 用户空间测试对应关系

  1. 注册阶段

    fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | FASYNC);

    → 调用 scull_p_fasync(1)

  2. 通知阶段

    echo "data" > /dev/scullp

    → 调用 scull_p_write()kill_fasync()

  3. 清理阶段

    close(fd);

    → 调用 scull_p_release()scull_p_fasync(0)

五、模块编译文件Makefile

​ 用于编译ko模块的配置文件

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目录下手动创建字符设备节点,现在这个脚本把这些工作一起完成了,记得给脚本赋予执行权限,执行命令:

sudo chmod 744 scull_load
sudo ./scull_load
#!/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]

​ 下面对脚本的内容进行详细解释

变量定义

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"
else
group="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: staffwheel(之前确定的组)
  • /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 1
rm -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_SETFLFASYNC:在标准输入上启用异步通知模式

工作流程

  1. 睡眠很长时间(实际上会被信号中断)
  2. 如果 gotdata 被设置,读取输入数据
  3. 将数据写入标准输出
  4. 重置 gotdata 标志

2. 编译并执行

gcc fasync_test.c -o fasync_test
./fasync_test

终端输入

test

预期输出

test
posted on 2025-11-04 17:03  mthoutai  阅读(32)  评论(0)    收藏  举报