Linux:早期操作系统的系统调用

Linux:早期操作系统的系统调用

相关阅读

Linuxhttps://blog.ZEEKLOG.net/weixin_45791458/category_12234591.html?spm=1001.2014.3001.5482


简介

        本文将以Linux1.0为例说明早期操作系统的系统调用过程。

        Linux1.0总共提供了135个系统调用(其中一些是保留或未实现),可以在源码路径linux-1.0/include/linux/sys.h下找到系统调用函数声明,在源码路径linux-1.0/include/linux/unistd.h下找到系统调用号定义。

        下面列举出了一些系统调用的相关信息。

系统调用号系统调用函数名系统调用函数原型含义定义位置
0sys_setupasmlinkage int sys_setup(void * BIOS)完成系统设备初始化(磁盘)、加载 RAM 盘、挂载根文件系统。linux-1.0/drivers/block/genhd.c
1sys_exitasmlinkage int sys_exit(int error_code)终止当前进程的执行,并清理它所拥有的所有系统资源。linux-1.0/kernel/exit.c
2sys_forkasmlinkage int sys_fork(struct pt_regs regs)创建当前进程的一个子进程(即“复制进程”),子进程几乎完全复制父进程的执行上下文。linux-1.0/kernel/fork.c
3sys_readasmlinkage int sys_read(unsigned int fd,char * buf,unsigned int count)从文件描述符所表示的文件中读取数据,并将其存入用户空间缓冲区中。linux-1.0/fs/read_write.c
4sys_writeasmlinkage int sys_write(unsigned int fd,char * buf,unsigned int count)将用户空间缓冲区 中的数据写入由文件描述符所表示的文件中。linux-1.0/fs/read_write.c
5sys_openasmlinkage int sys_open(const char * filename,int flags,int mode)根据指定的路径 打开一个文件,按指定的方式访问,并在需要创建文件时指定权限。最终返回一个文件描述符。linux-1.0/fs/open.c
6sys_closeasmlinkage int sys_close(unsigned int fd)关闭一个打开的文件描述符,释放它所占用的内核资源,并使该描述符可被重新使用。linux-1.0/fs/open.c
7sys_waitpidasmlinkage int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options)使父进程等待其一个子进程状态发生变化(如退出或被信号停止),并获取该子进程的退出状态。linux-1.0/kernel/signal.c
8sys_creatasmlinkage int sys_creat(const char * pathname, int mode)创建一个新文件(如果文件不存在),或清空已有文件的内容(如果文件已存在),并返回一个用于后续读写操作的文件描述符。linux-1.0/fs/open.c
.....

使用C库

        以glibc1.09.1为例,对于系统调用sys_read(),glibc提供了一个系统调用包装器函数read(),其声明位于文件glibc-1.09.1\posix\unistd.h中(需要注意的是,它并不是ISO C标准库函数,而是POSIX标准扩展函数),函数原型如下所示。

extern ssize_t read __P ((int __fd, __ptr_t __buf, size_t __nbytes));

        其中__P是一个宏,其目的是在头文件中写一次函数声明,而能同时支持老旧编译器和新标准的编译器。

        接下来就是寻找read()函数的定义文件,可以在文件glibc-1.09.1\io\read.c中找到以下内容。

function_alias(read, __read, __ssize_t, (fd, buf, n), DEFUN(read, (fd, buf, n), int fd AND PTR buf AND size_t n))

        其中function_alias是一个宏,用于创建函数别名,DEFUN宏也是为了支持老旧编译器和新标准的编译器,功能上类似于以下写法。

__ssize_t read(int fd, void *buf, size_t n) { return __read(fd, buf, n); }

        接着可以在文件glibc-1.09.1\sysdeps\stub\__read.c中找到以下内容。

ssize_t DEFUN(__read, (fd, buf, nbytes), int fd AND PTR buf AND size_t nbytes) { if (nbytes == 0) return 0; if (fd < 0) { errno = EBADF; return -1; } if (buf == NULL) { errno = EINVAL; return -1; } errno = ENOSYS; return -1; }

        但遗憾的是,该函数看起来并没有任何与系统调用有关的内容,因为它只是一个stub(桩函数),也叫“占位函数”。

        继续寻找,可以在文件glibc-1.09.1\sysdeps\unix\__read.S中找到找到以下内容。

SYSCALL__ (read, 3) ret

        SYSCALL__是一个宏,定义如下所示。

SYSCALL__(name, args) PSEUDO (__##name, name, args)

        PSEUDO是一个与平台相关的宏,对于i386架构,定义如下所示。

#define PSEUDO(name, syscall_name, args) \ .text; \ .globl syscall_error; \ ENTRY (name) \ XCHG_##args movl $SYS_##syscall_name, %eax; \ int $0x80; \ test %eax, %eax; \ jl syscall_error; \ XCHG_##args

        这段汇编代码设置eax寄存器为系统调用号($SYS_##syscall_name将被宏展开为$SYS_read,而后者又在glibc-1.09.1\sysdeps\unix\sysv\linux\syscall.h文件中被宏定义为3),最后通过int $0x80软中断进入内核模式,执行系统调用。

直接使用系统调用宏

        以Linux1.0为例,Linux提供了六个宏用于定义系统调用包装器函数,位于文件linux-1.0/include/linux/unistd.h中,定义如下所示。

#define _syscall0(type,name) \ type name(void) \ { \ long __res; \ __asm__ volatile ("int $0x80" \ : "=a" (__res) \ : "0" (__NR_##name)); \ if (__res >= 0) \ return (type) __res; \ errno = -__res; \ return -1; \ } #define _syscall1(type,name,atype,a) \ type name(atype a) \ { \ long __res; \ __asm__ volatile ("int $0x80" \ : "=a" (__res) \ : "0" (__NR_##name),"b" ((long)(a))); \ if (__res >= 0) \ return (type) __res; \ errno = -__res; \ return -1; \ } #define _syscall2(type,name,atype,a,btype,b) \ type name(atype a,btype b) \ { \ long __res; \ __asm__ volatile ("int $0x80" \ : "=a" (__res) \ : "0" (__NR_##name),"b" ((long)(a)),"c" ((long)(b))); \ if (__res >= 0) \ return (type) __res; \ errno = -__res; \ return -1; \ } #define _syscall3(type,name,atype,a,btype,b,ctype,c) \ type name(atype a,btype b,ctype c) \ { \ long __res; \ __asm__ volatile ("int $0x80" \ : "=a" (__res) \ : "0" (__NR_##name),"b" ((long)(a)),"c" ((long)(b)),"d" ((long)(c))); \ if (__res>=0) \ return (type) __res; \ errno=-__res; \ return -1; \ } #define _syscall4(type,name,atype,a,btype,b,ctype,c,dtype,d) \ type name (atype a, btype b, ctype c, dtype d) \ { \ long __res; \ __asm__ volatile ("int $0x80" \ : "=a" (__res) \ : "0" (__NR_##name),"b" ((long)(a)),"c" ((long)(b)), \ "d" ((long)(c)),"S" ((long)(d))); \ if (__res>=0) \ return (type) __res; \ errno=-__res; \ return -1; \ } #define _syscall5(type,name,atype,a,btype,b,ctype,c,dtype,d,etype,e) \ type name (atype a,btype b,ctype c,dtype d,etype e) \ { \ long __res; \ __asm__ volatile ("int $0x80" \ : "=a" (__res) \ : "0" (__NR_##name),"b" ((long)(a)),"c" ((long)(b)), \ "d" ((long)(c)),"S" ((long)(d)),"D" ((long)(e))); \ if (__res>=0) \ return (type) __res; \ errno=-__res; \ return -1; \ }

        以_syscall1为例,它用于定义一个有一个参数的系统调用包装器函数,它的第一个参数是系统调用的返回值类型,第二个参数是系统调用名(去除前面的sys_前缀)。

        以系统调用sys_close为例,如果需要使用它,首先需要使用_syscall1宏进行声明,如下所示。

_syscall1(int, close, unsigned int, fd)

        它将宏展开为以下包装器函数定义。

int close(unsigned int fd) { long __res; __asm__ volatile ("int $0x80" : "=a" (__res) : "0" (__NR_close),"b" ((long)(fd))); if (__res >= 0) return (int) __res; errno = -__res; return -1; }

        这段汇编代码设置eax寄存器为系统调用号(__NR_##name将被宏展开为__NR_close,而后者又在linux-1.0/include/linux/unistd.h文件中被宏定义为6),设置eax寄存器为fd参数,最后通过int $0x80软中断进入内核模式,执行系统调用。

写在最后

        在Linux2.6.20后,_syscall0类的宏被废除了,转而使用glibc提供的函数syscall(),函数原型如下所示。

extern int syscall __P ((int __sysno, ...));

        syscall()函数可以根据系统调用号直接执行系统调用,就像是文中第一节中使用C库的方式进行系统调用那样。

        在Linux2.5版本时,为了更加高效地执行系统调用,对于Pentium II处理器支持了新的指令sysenter和sysexit,到目前的x86-64架构,更加高效的syscall和sysret指令被引入了。

Read more

【MySQL数据库基础】(三)MySQL 库的核心操作全解析:创建、修改、备份一条龙搞定

【MySQL数据库基础】(三)MySQL 库的核心操作全解析:创建、修改、备份一条龙搞定

前言         在 MySQL 的学习和实战中,数据库(库)的操作是最基础也是最核心的环节,无论是项目开发、数据管理还是运维维护,都绕不开库的创建、配置、修改、备份等一系列操作。很多刚接触 MySQL 的小伙伴容易在字符集、校验规则、备份恢复这些细节上踩坑,今天这篇文章就结合实战案例,把 MySQL 库的全套操作讲透,从基础语法到高级技巧,从避坑指南到实战演示,让你一文掌握 MySQL 库操作的精髓! 一、创建数据库:基础语法与个性化配置         创建数据库是操作 MySQL 的第一步,看似简单的一句命令,背后却藏着字符集、校验规则的关键配置,选对配置能让后续的开发和数据管理少走很多弯路。 1. 核心创建语法         MySQL 中创建数据库的官方语法如下,其中大写部分为关键字,中括号[]内的为可选项,也是实际开发中需要重点关注的部分: CREATE DATABASE [IF NOT EXISTS]

By Ne0inhk
Flutter 组件 chopper_built_value 适配鸿蒙 HarmonyOS 实战:强类型网络层架构,构建不可变模型与高性能序列化闭环

Flutter 组件 chopper_built_value 适配鸿蒙 HarmonyOS 实战:强类型网络层架构,构建不可变模型与高性能序列化闭环

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 chopper_built_value 适配鸿蒙 HarmonyOS 实战:强类型网络层架构,构建不可变模型与高性能序列化闭环 前言 在鸿蒙(OpenHarmony)生态迈向大规模企业级应用、涉及高频网络数据交互、复杂业务模型及严苛运行时稳定性的背景下,如何确保网络请求返回的数据在进入 UI 层前具备绝对的类型安全,已成为衡量应用架构“护城河”深度的核心标准。在鸿蒙设备这类强调 AOT 极致性能与低容错率的环境下,如果应用依然依赖动态类型的 Map<String, dynamic> 进行数据传递,由于由于后端字段变更或类型溢出,极易由于由于运行时强转失败导致应用在关键业务路径上的红屏崩溃。 我们需要一种能够实现自动化代码生成、支持不可变(Immutable)模型且具备拦截器解耦能力的序列化粘合层。 chopper_built_value 为 Flutter 开发者引入了将 Chopper

By Ne0inhk
基于 Rust 与 DeepSeek 大模型的智能 API Mock 生成器构建实录:从环境搭建到架构解析

基于 Rust 与 DeepSeek 大模型的智能 API Mock 生成器构建实录:从环境搭建到架构解析

前言 在现代软件工程中,API 接口的开发与前端联调往往存在时间差。为了解耦前后端开发进度,Mock 数据(模拟数据)的生成显得尤为关键。传统的 Mock 数据生成依赖于静态 JSON 文件或简单的规则引擎,难以覆盖复杂的业务逻辑与语义关联。随着大语言模型(LLM)的兴起,利用 AI 根据 Schema 定义动态生成高保真的模拟数据成为可能。本文详细记录了使用 Rust 语言结合 DeepSeek-V3.2 模型构建智能 Mock 生成器的完整技术路径,涵盖操作系统层面的环境准备、Rust 工具链的深度配置、代码层面的异步架构设计以及编译期的版本兼容性处理。 第一部分:Linux 系统底层的构建环境初始化 Rust 语言的编译与链接过程高度依赖于底层的系统工具链。Rust 编译器 rustc 在生成二进制文件时,需要调用链接器(Linker)将编译后的对象文件(Object Files)与系统库(

By Ne0inhk
Flutter for OpenHarmony 实战:Flutter Rust Bridge — 极致计算性能方案

Flutter for OpenHarmony 实战:Flutter Rust Bridge — 极致计算性能方案

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter for OpenHarmony 实战:Flutter Rust Bridge — 极致计算性能方案 前言 在 Flutter for OpenHarmony 的高性能应用场景中(如:超大数据量加密、实时音视频处理、复杂物理模拟),Dart 的性能虽然出色,但在面对 CPU 密集型任务时,往往需要更底层的语言辅助。 Rust 凭借其内存安全与极致性能,成为了移动端计算的“无冕之王”。而 Flutter Rust Bridge (FRB) 则是将 Dart 与 Rust 缝合在一起的顶尖架构。它能自动生成繁琐的 FFI 胶水代码,并支持异步、流式传输以及复杂的对象映射。本文将带你在鸿蒙系统上构建一套“双擎驱动”

By Ne0inhk