大家好,我是小风哥。在前面的两篇《为什么计算机需要操作系统?》《??系统调用与函数调用有什么区别??》中,我们了解了什么是系统调用,为什么需要系统调用,以及系统调用和函数调用的区别。那么在今天的文章中,我们就从理论走向现实,看看Linux中的系统。调用是如何实现的。首先,我们简单回顾一下之前讲解的知识。系统调用和普通函数调用没有本质区别。普通函数调用一般调用我们自己的函数或者其他库函数,而系统调用调用内核中的函数。更学术一点的说法是,所谓系统调用,是指用户态程序请求操作系统提供的服务。说到服务,大家首先想到的就是服务器。假设客户端是浏览器。浏览器发送http请求。服务端收到请求后,解析并调用相应的处理程序。本质上,客户端触发服务器。这时候我们就说客户端向服务器端请求了一个服务。系统调用与此类似,只不过用户态程序不是通过http,而是通过机器指令来触发操作系统中某个函数的运行,因为用户态App和操作系统运行在同一个计算机系统上,而客户端和服务端运行在不同的计算机系统上(大多数情况下),所以客户端只能通过网络协议http与服务端进行通信。比较通俗的说法是,所谓系统调用是指用户态的一个函数调用内核中的一个函数。接下来,让我们用一个简单的helloworld程序来看看系统调用。该程序需要在x86_64下运行:.datamsg:.ascii"Hello,world!\n"len=.-msg.text.global_start_start:movq$1,%raxmovq$1,%rdimovq$msg,%rsimovq$len,%rdxsyscallmovq$60,%raxxorq%rdi,%rdisyscall编译:$gcc-ctest。S$ld-o测试测试。o然后执行:./testHello,world!这段汇编代码成功打印了helloworld,这段代码是什么意思呢?注意.data部分。这里是关于程序定义的数据。.text部分是关于程序中包含的执行。我们提到进程的内存布局时,总是说数据段和代码段。这里的数据段是指程序集中的.data段,代码段是指程序集中的.text段。现在你应该明白了吧。在.text部分我们看到了一个稍微奇怪的指令,syscall,这个指令是什么意思呢?让我们看一下intel的开发手册:SYSCALL在特权级别0调用OS系统调用处理程序。它通过从IA32_LSTARMSR加载RIP来实现(在将SYSCALL之后的指令地址保存到RCX中之后)。(WRMSR指令确保IA32_LSTARMSR始终包含规范地址。)这段话告诉我们,intel处理器在执行syscall指令时,会在内核态调用操作系统的一个函数,即syscall-调用处理程序。这个过程就是所谓的systemCall,我们知道CPU在执行一个函数的时候,肯定知道这个函数在内存中的地址,那么CPU是怎么知道一个syscall-callhandler的内存地址的呢?原来syscall-callhandler所在的内存地址是存放在寄存器MSR中的,那么这个地址是谁存放在寄存器MSR中的呢?很明显是操作系统,那我们就以Linux为例来说明一下。Linux内核初始化时,syscall-callhandler,即entry_SYSCALL_64函数在Linux内核中的地址,被写入寄存器MSR:wrmsrl(MSR_LSTAR,entry_SYSCALL_64);其中syscall-callhandler,即entry_SYSCALL_64,在Linux源码中定义在arch/x86/entry/entry_64.S,上述初始化寄存器MSR的代码定义在arch/x86/kernel/cpu/common。C。现在我们知道,当CPU执行一个syscall时,会跳转到寄存器MSR中保存的函数地址,也就是entry_SYSCALL_64函数。很明显,所有系统调用的入口都是entry_SYSCALL_64函数,那么操作系统应该如何区分是read系统调用还是write系统调用呢?原来每个系统调用在操作系统中都分配了一个序号,就像在Linux中一样:0commonreadsys_read1commonwritesys_write2commonopensys_open3commonclosesys_close4commonstatsys_newstat5commonfstatsys_newfstat6commonlstatsys_newlstat7commonpollsys_poll8commonlseeksysmon_lseek9mmapsys_mmap...可以看出0号系统调用代表内核中的read函数,1号系统调用代表内核中的write函数。当进行系统调用时,将表示系统调用类别的序号写入通用寄存器。从上表我们可以看出write系统调用的序号是1,所以在helloworld程序中我们将1写入寄存器rax:movq$1,%rax这条指令的意思是我们要调用No.1系统调用,也就是sys_write,helloworld程序中接下来的3条机器指令的作用是:#写入文件描述符1movq$1,%rdi#保存指向字符串的指针movq$msg,%rsi#写入数据大小movq$len,%rdx其实这4条机器指令都是在为syscall的执行做铺垫,也就是执行syscall需要的参数。可见,我们在系统调用中传递参数时,都是通过寄存器来完成的。这样CPU在执行syscall的时候就会跳转到Linux内核中的write函数,同时可以知道write函数在执行函数时需要哪些参数。
