深入 Linux 内核:系统调用机制完全解析

了解内核态调用可以更好地辅助开发人员理解操作系统工作机制,更好地完成一些生产问题的排查,所以本文将针对此话题展开更进一步的分析和探讨,希望对你有所帮助。

操作系统的诞生本质就是:

  • 计算机资源管理:透明后台自动完成
  • 应用程序提供抽象:针对系统文件进行创建、修改、写入、删除

这其中关于进程的内核调用在安全管理层面也有着非常出色的设计,了解内核态调用可以更好地辅助开发人员理解操作系统工作机制,更好地完成一些生产问题的排查,所以本文将针对此话题展开更进一步的分析和探讨,希望对你有所帮助。

一、基于open函数了解进程内核态调用执行步骤

1. 操作系统的本职工作

现代操作系统本质上就是对于多道处理程序(解决CPU单位时间内只能执行一条指令时阻塞带来的空转问题)的一种抽象,一种对于物理层面的一种封装,帮助人类完成计算机资源管理和应用程序的统一抽象管理。以本文要讲的内容为例,当要读取系统文件时,对应的程序需要发起一次系统调用,即通过syscall指令进入操作系统内核。 随后,内核代码针对该指令进行必要的参数检查后,执行文件读取调用,并将控制返回给系统调用后的指令,而这个过程也就是我们常说的内核调用。这种调用方式在系统调用和中断处理时都会触发,对于一般的用户态调用则不会进行上下文切换:

2. 详解操作系统中的I/O调用

针对操作系统的内核态调用,我们还是以最经典的I/O文件读取展开探讨,以下以笔者的read.c代码为例,可以看到针对Linux服务器下的txt文件读取,大体分为如下几步:

  • 要通过open函数执行调用获取文件描述符
  • 调用read函数读取文件中的数据,并返回读取到的字节数
  • 输出打印到系统终端
  • 关闭文件描述符资源

这其中open和read调用都涉及进程上下文切换调用阻塞等待结果并返回:

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
int main() {
   int fd = open("example.txt", O_RDONLY); // 打开文件
   if (fd == -1) {
       perror("Error opening file");
       return 1;
   }
   char buffer[128];
   ssize_t bytesRead;
   // 读取文件内容
   while ((bytesRead = read(fd, buffer, sizeof(buffer) - 1)) > 0) {
       buffer[bytesRead] = '\0'; // 确保字符串以 '\0' 结尾
       printf("bytesRead: %zd\n", bytesRead);
       printf("%s", buffer);
   }
   if (bytesRead == -1) {
       perror("Error reading file");
   }
   close(fd); // 关闭文件
   return 0;
}

考虑到代码示例的完整性,笔者也给出编译和执行的指令:

# 编译c程序
gcc read.c -o read
# 执行程序
./read

最终输出结果如下,可以看到对于hello c lang读取结果为13字节(c语言字符串末尾默认添加一个\0结束符),同时输出的字符也很预期结果一致:

bytesRead: 13
hello c lang
3. 深入剖析read调用的工作机制

上面这个程序对应的open和read都涉及内核态调用,以read为例,该调用返回的是文件读取的字节数。在执行该函数调用时,调用程序会先将函数参数存到RDI、RSI、RDX、R10、R8、R9这组寄存器上,一旦超过6个参数就会将多出来的参数直接压入堆栈。以我们的read调用为例,因为只有3个参数,所以只会用到RDI、RSI、RDX这几个寄存器。

一旦发起read系统调用读取磁盘数据时,read库函数就会将系统调用号存放到RAX寄存器上,因为考虑到应用程序安全层面的不稳定因素,操作系统会通过一个陷阱指令,对应我们的X86-64的CPU也就是syscall调用,指令让read调用的线程切换到内核态中,内核检查RAX寄存器中的系统调用号将请求分发给对应的内核函数。 在此期间该线程会因为IO调用而发起阻塞,所以在多道程序设计的理念上,为避免CPU因为IO阻塞而未能处理其它进程的程序的指令,操作系统在此时会将进程的上下文保存到其结构体中(本质就是内存),然后去执行其它程序的指令,将其它指令需要的数据存放到寄存器上执行。

我们的线程在内核完成文件读取后,内核将结果返回给用户程序,继续执行用户程序的下一条指令。 对应我们也给出read系统调用返回的完整流程图,可以看到我们会将read参数分别放到3个寄存器上,并将系统调用号存到RAX寄存器上,在随后进行的syscall指令的上下文切换中,内核代码会根据RAX中的系统调用号定位到对应的内核函数完成数据读取,并将结果以函数返回值的方式返回给用户程序:

二、内核态调用常见问题

1. 为什么调用要进入内核态调用

应用程序不受操作系统的完全控制,考虑到安全性,所有涉及系统资源的操作都封装成内核函数

2. 为什么不直接在open函数上配置指令地址

为避免应用程序识别内核函数地址直接违法调用,系统会变化指令地址,所以就需要通过映射表进行管理

三、小结

操作系统本质上是针对用户程序的抽象和系统资源的统一管理,对于日常的I/O调用,应用程序执行时会将参数存放到有限的寄存器上或堆栈上,然后陷阱指令(对应x86-64 CPU也就是syscall指令)进入Linux内核,此时内核代码就会根据RAX寄存器中的系统调用号定位到对应的内核函数执行系统调用,并将结果以函数返回值的方式返回给应用程序。

AI大模型学习福利

作为一名热心肠的互联网老兵,我决定把宝贵的AI知识分享给大家。 至于能学习到多少就看你的学习毅力和能力了 。我已将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

一、全套AGI大模型学习路线

AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获取

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

三、AI大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。


因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

四、AI大模型商业化落地方案

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值