购买与学习资源
- 京东购买链接:Yocto项目实战教程:高效定制嵌入式Linux系统
- B站配套视频:嵌入式Jerry
系统调用入门到精通:关键概念、原理剖析与实操演练
作者:嵌入式Jerry
视频教程请关注 B 站:“嵌入式 Jerry”
一、带着问题学习:系统调用到底是什么?有什么用?我们怎么用?
- 应用程序为啥不能直接操作硬件?
- 系统调用和普通函数有啥区别?为什么需要它?
- 系统调用到底做了什么,底层是怎么跑起来的?
- 作为驱动工程师,系统调用和我的驱动开发有什么关系?
- 我能不能动手体验一次“系统调用的全流程”?
二、什么是系统调用?为什么需要系统调用?
1. 概念简述
- 系统调用(System Call)是用户程序请求操作系统核心服务(如文件操作、内存管理、进程控制等)的唯一正规通道。
- 作用:安全、受控地让应用访问内核资源和硬件,防止系统崩溃和恶意操作。
2. 核心本质
- 系统调用是一种“受控异常”(Trap),是CPU提供的同步异常机制。
- 不是普通函数调用,而是特权级切换:用户态 → 内核态。
三、系统调用和普通函数的本质区别
- 普通函数只在当前权限级别(用户态)运行,不能越权。
- 系统调用必须切换到内核态,由内核以最高权限帮你完成敏感操作。
- 典型系统调用有:open、read、write、ioctl、fork、exec、mmap、close、socket 等。
四、系统调用工作流程全景图
1. 总体流程逻辑图
+--------------------+
| 应用程序代码 |
| write(fd, ...) |
+---------+----------+
|
v
+--------------------+
| 系统调用接口 |
| (libc包装函数) |
+---------+----------+
|
v
+--------------------+
| Trap/异常指令 |
| (如 svc/syscall) |
+---------+----------+
|
v
+--------------------+
| 内核Trap入口 |
| (异常向量表) |
+---------+----------+
|
v
+--------------------+
| 系统调用分发 |
| (sys_call_table) |
+---------+----------+
|
v
+--------------------+
| 具体内核服务 |
| (如 sys_write) |
+---------+----------+
|
v
+--------------------+
| VFS/驱动/文件系统 |
+--------------------+
五、通俗易懂的流程讲解
1. 用户空间
- 你写的C代码:
write(fd, buf, len);
- 这个
write
其实是库函数,最终通过syscall
或者类似机制触发系统调用指令。
2. 系统调用Trap
- CPU遇到系统调用指令(如 ARM 的
svc #0
,x86 的syscall
),直接进入内核态,找到异常向量表的Trap入口。
3. 内核分发
- 内核Trap入口(如
el0_sync
),把系统调用号、参数等找出来。 - 根据系统调用号查
sys_call_table
,转到正确的内核实现函数,比如sys_write()
。
4. 内核服务实现
sys_write()
做参数校验、安全检查,然后调用VFS或具体驱动代码。- 最终调用到你驱动里实现的
file_operations.write
函数。
5. 结果返回
- 操作完成后,把结果放回用户空间,程序继续执行。
六、驱动工程师视角:系统调用与驱动开发的关系
- 用户空间对
/dev/xxx
节点进行 open/read/write/ioctl 等操作,实质都是系统调用。 - 内核VFS根据设备号找到你的驱动,把请求交给你在
file_operations
里实现的函数。 - 你只需要实现 file_operations,VFS 和系统调用机制会帮你搞定用户到内核的所有中转。
七、实操演练:亲手体验系统调用全过程
1. 编写简单测试程序
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main() {
int fd = open("/dev/mydev", O_RDWR);
if (fd < 0) {
perror("open");
return 1;
}
char buf[8] = "hello";
write(fd, buf, 5);
close(fd);
return 0;
}
2. 编写简单字符设备驱动
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#define DEV_NAME "mydev"
static int major;
static ssize_t my_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
char kbuf[16] = {0};
copy_from_user(kbuf, buf, min(count, sizeof(kbuf)-1));
printk("my_write called, data: %s\n", kbuf);
return count;
}
static struct file_operations my_fops = {
.owner = THIS_MODULE,
.write = my_write,
};
static int __init my_init(void)
{
major = register_chrdev(0, DEV_NAME, &my_fops);
printk("mydev registered with major %d\n", major);
return 0;
}
static void __exit my_exit(void)
{
unregister_chrdev(major, DEV_NAME);
printk("mydev unregistered\n");
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
3. 创建设备节点并测试
sudo insmod mydev.ko
sudo mknod /dev/mydev c [major号] 0
./your_test_program
# 查看 dmesg 输出,能看到 "my_write called, data: hello"
4. 用 strace 跟踪系统调用
strace ./your_test_program
- 你会看到 open、write、close 等系统调用全过程。
八、核心知识点总结与问答
1. 系统调用是什么?
是应用程序请求内核完成敏感操作的唯一通道,通过Trap机制切换到内核态。
2. 为什么不能直接访问硬件?
为了操作系统的安全、稳定、资源隔离,必须受控访问。
3. 系统调用和普通函数的最大区别?
系统调用伴随特权切换,权限高,可以访问内核和硬件;普通函数不能。
4. VFS与file_operations的关系?
VFS负责中转,file_operations负责具体实现。你的驱动只要实现file_operations接口,系统调用的请求就会送达你这。
5. 怎么验证系统调用到了自己的驱动?
用printk打印、用strace跟踪应用、驱动里打断点。
九、一图看懂核心关系
十、结语
系统调用就是应用和内核/驱动之间唯一的高速通道。理解它的本质、掌握底层流程、动手验证、结合驱动开发,是你成为高级嵌入式/驱动工程师的必经之路!
购买与学习资源
- 京东购买链接:Yocto项目实战教程:高效定制嵌入式Linux系统
- B站配套视频:嵌入式Jerry