
相关阅读Linuxhttps://blog.csdn.net/weixin_45791458/category_12234591.html?spm1001.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.c1sys_exitasmlinkage int sys_exit(int error_code)终止当前进程的执行并清理它所拥有的所有系统资源。linux-1.0/kernel/exit.c2sys_forkasmlinkage int sys_fork(struct pt_regs regs)创建当前进程的一个子进程即“复制进程”子进程几乎完全复制父进程的执行上下文。linux-1.0/kernel/fork.c3sys_readasmlinkage int sys_read(unsigned int fd,char * buf,unsigned int count)从文件描述符所表示的文件中读取数据并将其存入用户空间缓冲区中。linux-1.0/fs/read_write.c4sys_writeasmlinkage int sys_write(unsigned int fd,char * buf,unsigned int count)将用户空间缓冲区 中的数据写入由文件描述符所表示的文件中。linux-1.0/fs/read_write.c5sys_openasmlinkage int sys_open(const char * filename,int flags,int mode)根据指定的路径 打开一个文件按指定的方式访问并在需要创建文件时指定权限。最终返回一个文件描述符。linux-1.0/fs/open.c6sys_closeasmlinkage int sys_close(unsigned int fd)关闭一个打开的文件描述符释放它所占用的内核资源并使该描述符可被重新使用。linux-1.0/fs/open.c7sys_waitpidasmlinkage int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options)使父进程等待其一个子进程状态发生变化如退出或被信号停止并获取该子进程的退出状态。linux-1.0/kernel/signal.c8sys_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) retSYSCALL__是一个宏定义如下所示。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 (__res0) \ 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 (__res0) \ 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 (__res0) \ 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指令被引入了。