驱动: file_operations、register_chrdev、/proc 文件系统概述
file_operations结构体
在 Linux 内核驱动开发中,file_operations结构体是一个至关重要的数据结构,它定义了一系列函数指针,用于实现对设备文件的各种操作,将用户空间对文件的操作请求与内核空间中对应的处理函数关联起来。
file_operations结构体的主要作用是作为用户空间文件操作和内核空间设备驱动程序之间的接口桥梁。当用户空间通过系统调用(如open、read、write等)对设备文件进行操作时,内核会根据文件对应的inode找到其关联的file_operations结构体,并调用结构体中相应的函数指针指向的函数,从而实现对设备的具体操作。
struct file_operations {struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);int (*readdir) (struct file *, void *, filldir_t);unsigned int (*poll) (struct file *, struct poll_table_struct *);int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);long (*compat_ioctl) (struct file *, unsigned int, unsigned long);int (*mmap) (struct file *, struct vm_area_struct *);int (*open) (struct inode *, struct file *);int (*flush) (struct file *);int (*release) (struct inode *, struct file *);int (*fsync) (struct file *, int datasync);int (*aio_fsync) (struct kiocb *, int datasync);int (*fasync) (int, struct file *, int);int (*lock) (struct file *, int, struct file_lock *);ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);int (*check_flags)(int);int (*flock) (struct file *, int, struct file_lock *);ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, size_t, unsigned int);ssize_t (*splice_read)(struct file *, struct pipe_inode_info *, size_t, unsigned int);
};
struct module *owner:指向拥有该结构体的模块,一般设置为THIS_MODULE,用于模块计数,确保在有文件操作正在使用该模块功能时,模块不会被意外卸载。
loff_t (*llseek) (struct file *, loff_t, int):实现文件的定位操作,用于改变文件读写指针的位置,返回新的文件偏移量。参数分别为文件描述符结构体指针、偏移量和偏移方式(如SEEK_SET、SEEK_CUR、SEEK_END)。
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *):从设备或文件中读取数据到用户空间,返回实际读取的字节数。参数依次为文件描述符结构体指针、指向用户空间的缓冲区指针、请求读取的字节数和文件偏移量指针 。
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *):将用户空间的数据写入设备或文件,返回实际写入的字节数。参数与read函数类似,只是缓冲区指针为写入数据的来源。
int (*open) (struct inode *, struct file *):打开设备或文件时调用,用于初始化设备相关资源,建立文件与设备的关联,成功时返回 0,失败返回负数错误码。参数为 inode 结构体指针和文件描述符结构体指针。
int (*release) (struct inode *, struct file *):关闭设备或文件时调用,用于释放open函数中分配的资源,如关闭设备、释放锁等,返回值规则与open函数相同。
int (*ioctl) (struct inode *,struct file *, unsigned int, unsigned long):实现设备的控制操作,用于执行一些自定义的命令,比如设置设备参数、获取设备状态等,成功返回 0,失败返回错误码。
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long):在非阻塞 I/O 情况下执行控制操作,是ioctl函数的非阻塞版本,功能类似但设计用于避免一些与阻塞相关的问题。
int (*mmap) (struct file *, struct vm_area_struct *):将文件映射到内存,使得用户可以通过内存操作的方式访问文件内容,常用于提高数据访问效率,成功返回 0,失败返回错误码。
字符设备驱动
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>#define DEVICE_NAME "my_char_device"
#define BUFFER_SIZE 100static dev_t dev_num;
static struct cdev cdev;
static char buffer[BUFFER_SIZE];// 打开设备函数
static int my_open(struct inode *inode, struct file *filp) {printk(KERN_INFO "Device opened\n");return 0;
}// 释放设备函数
static int my_release(struct inode *inode, struct file *filp) {printk(KERN_INFO "Device released\n");return 0;
}// 读取设备函数
static ssize_t my_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) {size_t bytes_to_read = min(count, (size_t)(BUFFER_SIZE - *f_pos));if (copy_to_user(buf, buffer + *f_pos, bytes_to_read)) {return -EFAULT;}*f_pos += bytes_to_read;return bytes_to_read;
}// 写入设备函数
static ssize_t my_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) {size_t bytes_to_write = min(count, (size_t)(BUFFER_SIZE - *f_pos));if (copy_from_user(buffer + *f_pos, buf, bytes_to_write)) {return -EFAULT;}*f_pos += bytes_to_write;return bytes_to_write;
}// 定义file_operations结构体实例
static struct file_operations my_fops = {.owner = THIS_MODULE,.open = my_open,.release = my_release,.read = my_read,.write = my_write,
};static int __init my_driver_init(void) {// 分配设备号if (alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME) < 0) {return -1;}// 初始化字符设备cdev_init(&cdev, &my_fops);if (cdev_add(&cdev, dev_num, 1) < 0) {unregister_chrdev_region(dev_num, 1);return -1;}printk(KERN_INFO "My char device driver loaded\n");return 0;
}static void __exit my_driver_exit(void) {cdev_del(&cdev);unregister_chrdev_region(dev_num, 1);printk(KERN_INFO "My char device driver unloaded\n");
}module_init(my_driver_init);
module_exit(my_driver_exit);MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple char device driver");
MODULE_LICENSE("GPL");
首先定义了字符设备相关的变量,包括设备号dev_num、字符设备结构体cdev以及用于数据存储的缓冲区buffer。
接着实现了open、release、read、write等函数,分别对应设备打开、关闭、读取和写入操作的具体逻辑。
然后创建了file_operations结构体实例my_fops,并将上述实现的函数指针赋值给my_fops的相应成员。
在my_driver_init函数中,通过alloc_chrdev_region分配设备号,使用cdev_init初始化字符设备并关联file_operations结构体,最后调用cdev_add将字符设备添加到系统中。
my_driver_exit函数则负责在模块卸载时删除字符设备并释放设备号。
register_chrdev介绍
在 Linux 内核开发中,register_chrdev是字符设备注册的核心函数。
register_chrdev函数定义在<linux/fs.h>头文件中,原型如下:
int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops);
参数说明:
major:主设备号(若为 0 则动态分配)
name:设备名(出现在 /proc/devices 中)
fops:指向 file_operations 结构体的指针
返回值:
成功:返回主设备号
失败:返回负错误码(如 - EINVAL)
register_chrdev实现原理
- 设备号分配机制
Linux 将设备号分为两部分:
主设备号(12 位):标识驱动程序
次设备号(20 位):标识具体设备实例
register_chrdev的分配逻辑:
静态分配(major≠0):直接注册指定主设备号
动态分配(major=0):内核自动分配可用主设备号
-
数据结构关系
函数内部主要操作以下数据结构:
chrdevs数组:全局字符设备表(255 个元素)
file_operations:设备操作方法集
inode结构体:存储设备号与 fops 映射关系 -
注册流程
检查主设备号有效性
分配 cdev 结构体内存
初始化 cdev 并关联 fops
将 cdev 添加到 chrdevs 数组
更新 /proc/devices 文件
register_chrdev的核心作用是:
建立设备号与驱动的映射
将用户指定的主设备号与对应的操作函数绑定
统一字符设备接口
通过 file_operations 结构体提供标准操作方法
支持设备发现机制
在 /proc/devices 中生成可查询的设备条目
兼容旧版驱动模型
为早期简单驱动提供便捷注册方式
1. 静态分配示例
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>#define MY_MAJOR 240
#define DEVICE_NAME "mychardev"static int my_open(struct inode *inode, struct file *file) {printk(KERN_INFO "Device opened\n");return 0;
}static int my_release(struct inode *inode, struct file *file) {printk(KERN_INFO "Device closed\n");return 0;
}static ssize_t my_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos) {char msg[] = "Hello from kernel!";int len = sizeof(msg);if (copy_to_user(buf, msg, len))return -EFAULT;return len;
}static struct file_operations fops = {.owner = THIS_MODULE,.open = my_open,.release = my_release,.read = my_read,
};static int __init my_init(void) {int ret;ret = register_chrdev(MY_MAJOR, DEVICE_NAME, &fops);if (ret < 0) {printk(KERN_ALERT "Registration failed\n");return ret;}printk(KERN_INFO "Registered with major number %d\n", MY_MAJOR);return 0;
}static void __exit my_exit(void) {unregister_chrdev(MY_MAJOR, DEVICE_NAME);printk(KERN_INFO "Device unregistered\n");
}module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
2. 动态分配示例
static dev_t dev_num;static int __init my_init(void) {int ret;ret = register_chrdev(0, DEVICE_NAME, &fops);if (ret < 0) {printk(KERN_ALERT "Registration failed\n");return ret;}dev_num = MKDEV(ret, 0);printk(KERN_INFO "Registered with dynamic major %d\n", ret);return 0;
}static void __exit my_exit(void) {unregister_chrdev(MAJOR(dev_num), DEVICE_NAME);printk(KERN_INFO "Device unregistered\n");
}
Linux /proc 文件系统
/proc 文件系统是 Linux 内核的重要组成部分,它提供了一个直观且强大的接口,让用户和应用程序能够方便地访问系统内部状态和控制内核行为。理解 /proc 的结构和工作原理,对于系统管理员进行性能调优、故障诊断,以及开发者编写系统工具和内核模块都具有重要意义。
尽管现代 Linux 系统引入了 sysfs 和 debugfs 等更先进的接口,但 /proc 因其简单性和广泛的兼容性,仍然是系统管理和开发中不可或缺的工具。
/proc 文件系统概述
虚拟文件系统:不占用磁盘空间,数据实时从内核中读取
内核接口:提供访问系统状态和控制内核参数的机制
进程信息枢纽:存储当前运行进程和系统内核的详细信息
根目录主要内容
进程相关目录
/proc/[pid]:每个进程的专属目录,包含:
/proc/[pid]/exe:可执行文件的符号链接
/proc/[pid]/cwd:当前工作目录的符号链接
/proc/[pid]/fd:文件描述符目录
/proc/[pid]/stat:进程状态信息
/proc/[pid]/status:更详细的进程状态
/proc/[pid]/cmdline:进程启动命令行参数
内核参数控制
/proc/sys:包含可调整的内核参数
/proc/sys/net:网络相关参数
/proc/sys/vm:虚拟内存相关参数
/proc/sys/kernel:内核通用参数
系统监控工具实现
# top命令核心数据来源
cat /proc/stat
cat /proc/meminfo# ps命令核心数据来源
ls /proc | grep '^[0-9]\+$'
cat /proc/[pid]/stat
性能调优
# 调整TCP窗口大小
echo 65536 > /proc/sys/net/ipv4/tcp_rmem
echo 65536 > /proc/sys/net/ipv4/tcp_wmem# 查看当前设置
cat /proc/sys/net/ipv4/tcp_rmem
进程调试
# 查看进程打开的文件
ls -l /proc/[pid]/fd# 获取进程启动命令
cat /proc/[pid]/cmdline | tr '\0' ' '# 查看进程内存映射
cat /proc/[pid]/maps