6.6 KiB
6.6 KiB
实验五 设备管理——Linux 设备驱动程序安装
一、实验内容
认识 Linux 设备种类与工作方式,理解设备驱动原理,掌握驱动编写规范,安装简单字符设备驱动。
编写字符设备驱动模块,实现 open/close/read/write,动态注册/卸载,创建设备文件 /dev/mydev。
二、实验设计
2.1 字符设备驱动结构
+------------------+
| 字符设备驱动模块 |
+------------------+
| file_operations |
| - open |
| - release |
| - read |
| - write |
+------------------+
| 设备号 (Major) |
+------------------+
| 设备缓冲区 |
+------------------+
2.2 核心数据结构
#define DEVICE_NAME "kueng_char_dev" // 设备名
#define BUF_LEN 50 // 缓冲区大小
static int Device_Open = 0; // 设备打开标志
static char Message[BUF_LEN]; // 内核缓冲区
static int Major; // 主设备号
static struct file_operations fops = {
.open = mydev_open,
.release = mydev_release,
.read = mydev_read,
.write = mydev_write,
};
2.3 函数设计
| 函数 | 功能 |
|---|---|
mydev_open() |
打开设备,防止重复打开 |
mydev_release() |
关闭设备,重置标志 |
mydev_read() |
从内核读取数据到用户 |
mydev_write() |
从用户写入数据到内核 |
init_module() |
初始化模块,注册设备 |
cleanup_module() |
清理模块,注销设备 |
2.4 调用关系
加载驱动 (insmod)
└── init_module()
└── register_chrdev(0, DEVICE_NAME, &fops)
└── 动态分配主设备号
打开设备 (/dev/mydev)
└── mydev_open()
└── 检查 Device_Open,设置标志
读写操作
├── mydev_read() → copy_to_user()
└── mydev_write() → copy_from_user()
关闭设备
└── mydev_release()
└── 重置 Device_Open
卸载驱动 (rmmod)
└── cleanup_module()
└── unregister_chrdev(Major, DEVICE_NAME)
三、编码实现
3.1 设备打开实现
static int mydev_open(struct inode *inode, struct file *file) {
if (Device_Open) return -EBUSY; // 设备忙
Device_Open = 1;
return 0;
}
要点: 使用静态变量防止并发打开
3.2 设备释放实现
static int mydev_release(struct inode *inode, struct file *file) {
Device_Open = 0; // 重置标志,允许再次打开
return 0;
}
3.3 读操作实现
static ssize_t mydev_read(struct file *file, char *buffer, size_t length, loff_t *f_pos) {
// copy_to_user: 内核→用户
return copy_to_user(buffer, Message, length);
}
要点: 使用内核提供的 copy_to_user() 确保安全
3.4 写操作实现
static ssize_t mydev_write(struct file *file, const char *buffer, size_t length, loff_t *f_pos) {
int len = BUF_LEN < length ? BUF_LEN : length;
// copy_from_user: 用户→内核
return copy_from_user(Message, buffer, len);
}
要点: 写入长度不能超过缓冲区大小
3.5 模块初始化与清理
int init_module(void) {
// 注册字符设备,Major=0表示动态分配
Major = register_chrdev(0, DEVICE_NAME, &fops);
if (Major < 0) {
printk("Register char device failed\n");
return Major;
}
printk("Registered with major %d\n", Major);
return 0;
}
void cleanup_module(void) {
unregister_chrdev(Major, DEVICE_NAME); // 注销设备
}
3.6 编译与运行
# 编译驱动(需要内核头文件)
make -C /usr/src/linux-headers-$(uname -r) M=$(pwd) modules
# 加载模块
sudo insmod exp05_mydev.ko
# 查看主设备号
cat /proc/devices | grep kueng_char_dev
# 输出: 250 kueng_char_dev
# 创建设备文件
sudo mknod /dev/mydev c 250 0
# 编译测试程序
gcc exp05_test.c -o test
# 运行测试
sudo ./test
四、实验结果
4.0 测试命令
# 编译驱动(需要 root 权限)
sudo su
cd /home/e2hang/code/Operating-System/Experiment/experiment_05
make -C /usr/src/linux-headers-$(uname -r) M=$(pwd) modules
# 加载模块
sudo insmod exp05_mydev.ko
# 查看主设备号
cat /proc/devices | grep kueng_char_dev
# 创建设备文件(假设主设备号是250)
sudo mknod /dev/mydev c 250 0
# 编译测试程序
gcc exp05_test.c -o test
# 运行测试
sudo ./test
# 卸载驱动
sudo rmmod exp05_mydev
4.1 驱动加载
$ sudo insmod exp05_mydev.ko
$ dmesg | tail -1
Registered with major 250
说明: 内核动态分配了主设备号250
4.2 设备文件创建
$ sudo mknod /dev/mydev c 250 0
$ ls -l /dev/mydev
crw-r--r-- 1 root root 250 0 May 14 /dev/mydev
说明: c表示字符设备,250是主设备号,0是次设备号
4.3 测试程序运行
$ sudo ./test
write: pear to dev!
read: apple to dev!
说明:
- 写入 "pear to dev!" 到设备
- 读取设备内容得到 "apple to dev!"
- 原因:write 后 Message 为 "pear to dev!",read 读取但测试程序覆盖了 buf
4.4 驱动卸载
$ sudo rmmod exp05_mydev
$ dmesg | tail -1
(无输出,正常卸载)
五、实验结果思考与体会
5.1 字符设备驱动特点
| 特性 | 说明 |
|---|---|
| 与普通文件操作 | 通过 file_operations 映射 |
| 设备访问 | 通过设备文件 (/dev/xxx) |
| 内核缓冲区 | 驱动内部维护 |
| 安全传输 | 使用 copy_to/from_user |
5.2 思考问题解答
问题1:仿照 /dev/null 编写 sink 设备
sink 设备写入数据被丢弃,读取返回空:
static ssize_t sink_write(struct file *file, const char *buffer, size_t length, loff_t *f_pos) {
return length; // 丢弃数据,返回写入长度
}
static ssize_t sink_read(struct file *file, char *buffer, size_t length, loff_t *f_pos) {
return 0; // 返回0表示EOF
}
问题2:源码级编译 vs 模块方式
- 模块方式:灵活,可动态加载/卸载,适合开发调试
- 源码级编译:编译进内核,启动即生效,适合正式发布
5.3 实验体会
-
设备驱动本质:是内核代码,访问硬件,为用户程序提供接口
-
字符设备 vs 块设备:
- 字符设备:按字节流访问(键盘、串口)
- 块设备:按块访问(磁盘)
-
file_operations 结构:将系统调用映射到驱动函数
-
用户/内核数据交换:必须使用 copy_to_user / copy_from_user,防止内核崩溃
-
设备号管理:主设备号标识设备类型,次设备号标识具体设备实例
-
模块编程:了解内核模块的基本框架,为后续深入学习驱动开发打下基础