1. 引言
在 Linux 操作系统中,系统调用(System Call)是用户空间与内核通信的唯一方式。read()
是最常见的系统调用之一,它允许用户进程从文件、设备或其他数据流中读取数据。
在本文中,我们将深入讲解 read()
的整个执行流程,从 用户态 到 内核态 的转换,并结合 真实代码示例 来剖析 read()
如何最终调用设备驱动程序。同时,我们还会整理相关的 高频面试题及专业解答,帮助你更好地理解 Linux 内核系统调用机制。
2. read()
到底做了什么?
2.1 read()
是如何工作的?
让我们从一个最简单的用户程序开始:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
int fd = open("/dev/my_device", O_RDONLY);
if (fd < 0) {
perror("open");
return 1;
}
char buffer[128];
ssize_t bytes_read = read(fd, buffer, sizeof(buffer));
if (bytes_read > 0) {
buffer[bytes_read] = '\0'; // 确保字符串正确终止
printf("Read from device: %s\n", buffer);
} else {
perror("read");
}
close(fd);
return 0;
}
这段代码的 read()
函数到底做了什么呢?我们来拆解其工作原理。
3. read()
调用内核的过程
read()
是一个 系统调用,整个过程如下:
-
用户态(User Space):
- 用户进程调用
read(fd, buffer, len)
glibc
封装syscall(SYS_read, fd, buffer, len)
- 用户进程调用
-
进入内核态(Kernel Space):
- 触发
sys_read()
系统调用 - VFS(虚拟文件系统) 解析
/dev/my_device
- 调用 设备驱动的
read()
- 触发
-
设备驱动层(Driver Layer):
- 执行
my_read()
copy_to_user()
将数据从内核空间复制到用户空间
- 执行
-
返回用户态(Back to User Space):
read()
返回读取的字节数- 用户进程获取数据
🚀 简单理解:
read()
让 CPU 从 用户态 切换到 内核态,完成数据传输后再切换回来。
4. 代码示例:实现一个字符设备驱动
4.1 注册字符设备
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#define DEVICE_NAME "my_device"
static dev_t dev_num;
static struct cdev my_cdev;
static struct class *my_class;
static struct device *my_device;
static char kernel_buffer[] = "Hello from kernel!\n";
// `read()` 的驱动实现
static ssize_t my_read(struct file *filep, char __user *buffer, size_t len, loff_t *offset) {
int msg_len = sizeof(kernel_buffer);
if (*offset >= msg_len) return 0; // 文件读取结束
if (len > msg_len - *offset)
len = msg_len - *offset;
if (copy_to_user(buffer, kernel_buffer + *offset, len))
return -EFAULT; // 复制失败
*offset += len;
return len;
}
// `file_operations` 结构体
static struct file_operations fops = {
.owner = THIS_MODULE,
.read = my_read,
};
// 模块初始化
static int __init my_module_init(void) {
if (alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME) < 0)
return -1;
my_class = class_create(THIS_MODULE, "my_class");
if (IS_ERR(my_class))
return PTR_ERR(my_class);
cdev_init(&my_cdev, &fops);
cdev_add(&my_cdev, dev_num, 1);
my_device = device_create(my_class, NULL, dev_num, NULL, DEVICE_NAME);
return 0;
}
// 模块卸载
static void __exit my_module_exit(void) {
device_destroy(my_class, dev_num);
class_destroy(my_class);
cdev_del(&my_cdev);
unregister_chrdev_region(dev_num, 1);
}
module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");
5. read()
的完整流程解析
- 用户调用
read(fd, buffer, len)
glibc
通过syscall(SYS_read, fd, buffer, len)
进入内核- 内核中的
sys_read()
解析fd
- VFS 发现
fd
关联/dev/my_device
- 内核调用
file_operations.read
,即my_read()
copy_to_user()
把数据拷贝到用户空间read()
返回读取的字节数,数据已存入buffer
6. 高频面试题及答案
Q1: read()
是如何进入内核的?
回答:
glibc
使用syscall(SYS_read, fd, buffer, len)
- 触发
sys_read()
系统调用 sys_read()
通过 VFS 解析/dev
设备- 调用
file_operations.read
Q2: 为什么 copy_to_user()
必须使用?
回答:
- 内核态数据不能直接访问用户态
copy_to_user()
负责安全拷贝数据,防止非法访问
Q3: file_operations
的作用是什么?
回答:
file_operations
连接 VFS 与设备驱动read()
、write()
、open()
等操作都要注册
static struct file_operations fops = {
.owner = THIS_MODULE,
.read = my_read,
};
Q4: read()
调用 copy_to_user()
,那 write()
呢?
回答:
read()
用copy_to_user()
,从内核拷贝到用户write()
用copy_from_user()
,从用户拷贝到内核
copy_from_user(kernel_buffer, buffer, len);
7. 总结
✅ read()
是完整的系统调用,涉及用户态和内核态切换
✅ VFS 解析 /dev
,调用 file_operations.read
✅ 驱动程序的 my_read()
负责 copy_to_user()
数据拷贝
✅ 面试重点:系统调用流程、file_operations
、copy_to_user()
通过理解
read()
,你不仅掌握了 Linux 内核模块的核心机制,还能深入理解设备驱动开发的原理! 🚀