当前位置: 首页 > news >正文

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

相关阅读

Linuxhttps://blog.csdn.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中,函数原型如下所示。

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_##argsmovl $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指令被引入了。

http://www.lqws.cn/news/477973.html

相关文章:

  • 【论文阅读笔记】TransparentGS:当高斯溅射学会“看穿”玻璃,如何攻克透明物体重建难题?
  • Day56打卡 @浙大疏锦行
  • 【threejs】一天一个小案例讲解:控制面板(GUI)
  • 扩散模型与强化学习(1):字节Seedance中的人类偏好优化实践
  • 华为云Flexus+DeepSeek征文|基于Dify构建解析网页写入Notion笔记工作流
  • sqlite3 数据库反弹shell
  • 开发语言本身只是提供了一种解决问题的工具
  • 【AI智能体】Spring AI MCP 服务常用开发模式实战详解
  • TDengine 3.3.5.0 新功能——服务端查询内存管控
  • PaddleOCR + Flask 构建 Web OCR 服务实战
  • Flink Sink函数深度解析:从原理到实践的全流程探索
  • 63-Oracle LogMiner(23ai)-实操
  • 合成生物学与人工智能的融合:从生命编程到智能设计的IT新前沿
  • 华为云Flexus+DeepSeek征文|在Dify-LLM平台中开发童话故事精灵工作流AI Agent
  • Kafka动态配置深度解析
  • 测试用例原则之 FIRST/CORRECT/5C原则
  • 论文笔记:Large language model augmented narrative driven recommendations
  • 学习设计模式《十四》——组合模式
  • [计算机网络] 局域网内的网络传输
  • #### es相关内容的索引 ####
  • 【期末笔记】高频电子线路
  • 双向长短期记忆网络(BiLSTM)
  • 如何用AI开发完整的小程序<8>—让AI制作具体功能
  • KES数据库部署工具使用
  • 《HTTP权威指南》 第7章 缓存
  • uni-app项目实战笔记21--uniapp缓存的写入和读取
  • 药房智慧化升级:最优成本条件下开启地市级医院智慧医疗新变革
  • Ragflow 源码:ragflow_server.py
  • rust单体web项目模板搭建
  • vim学习流程,以及快捷键总结