《程序员的自我修养》读书笔记——系统调用、API

这一篇介绍一下系统调用,熟悉一下流程。很多做客户端的同学根本不知道这些内容。建议花时间看看相关的知识。最好的方式还是去看源码,反汇编,才能深刻的理解。

系统调用

程序运行的时候,本身是没有权限访问多少系统资源的。系统资源有限,如果操作系统不进行控制,那么各个程序难免会产生冲突。线程操作系统都将可能产生冲突的系统资源保护起来,阻止程序直接访问。比如文件、网络、IO、各种设备等。

比如无论在Windows还是Linux中,程序员都不能直接去访问硬盘的某扇区上的数据,必须通过文件系统,也不能擅自修改任意文件。所有这些操作必须经过操作系统规定的方式进行。比如用fopen打开没有权限的文件就会失败。

比如:想要程序延迟执行一段时间,不借助操作系统就是使用循环,这样会白白消耗CPU,造成资源浪费。如果使用操作系统提供的定时器就可以方便有效。

系统调用涵盖的功能很广,有程序运行锁必须的支持,如创建和退出进程、线程,进程内存管理,对系统资源的访问等。

Linux系统调用

在x86下,系统调用是通过0x80中断完成,各个通用寄存器用于传递参数。EAX寄存器用于表示系统调用的接口号。

比如:EAX=1表示退出进程,EAX=2表示创建进程,EAX=3表示读文件或IO,EAX=4表示写文件或IO。每个系统调用对应到内核源码中的一个函数,他们都是以sys_开头的,比如exit调用对应内核中的sys_exit函数

Linux内核提供了几百个系统调用,下面列举部分


所以完全可以不使用glibc封装的fopen、fread、fclose操作文件,而直接使用系统函数open,read,close实现。

技巧:Linux中可以使用man查看系统调用详情,使用参数2表示系统调用手册(比如 man 2 read)

如果直接使系统调用会有非常多的问题:

  • 使用不方便,操作系统提供的系统调用接口往往过于原始。程序员需要了解很多跟操作系统相关的细节
  • 各个操作系统之间系统调用不兼容

于是增加一个层来解决,系统调用与程序之间增加一个抽象层。这个层就是前面所说的glibc,或者API.

系统调用原理

这里单单以Linux为例,至于Windows调用原理暂时省略。

用户态、内核态及中断

现代操作系统中有两种特权级别,分为用户模式和内核模式。

多个模式存在,那么操作系统就可以让不同代码运行在不同模式下,进而限制代码的权限,提高稳定性、安全性。一般普通程序在用户态,操作会受到限制。系统调用运行在内核态,应用程序基本都是运行在用户态被限制。

用户态的程序通过中断来运行内核态的代码,进而从用户态切换到内核态。

中断

中断是操作系统的一个概念。中断是一个硬件或者软件发出的请求,要求CPU暂停当前的工作转手去执行更加重要的事情。

比如在编辑文本的时候,敲击键盘那CPU如何得知道呢?一种是轮询,CPU每隔一小段时间就去询问键盘是否有键按下,但是除非一直打字,否则大部分轮训都是得到没有键按下的结果。这样就白白浪费掉了很多资源。

另一种方式就是当键盘按下的时候,键盘芯片发一个信号给CPU,然后CPU接收到信号之后就只到键盘按下了。然后再去询键盘具体是哪个键被按下,这样的信号就是一种中断。——硬件中断

中断一般有两个属性,一个是中断号(从0开始),一个称为中断处理程序(ISR),不同中断具有不同的中断号,一个中断号对应一个中断处理程序。在内核中保存了一个数组,叫做中断向量表,这个数组第n项包含了指向第n个中断号的中断处理程序的指针。当中断来的时候,CPU就会暂停当前执行的代码,根据中断的中断号,在中断向量表中找到对应的中断处理程序,并且调用他,处理完之后,CPU继续执行之前的代码。

中断有硬件中断,这种来至于硬件的异常或者其他时间的发生比如断电,键盘按下。另一种是软件中断,软件中断一般是一条指令(i386下是int),带有一个参数记录中断号,这个指令用户可以手动触发某个中断并执行中断处理程序。比如int 0x80这条指令就会调用第0x80号的中断处理程序。

中断号是有限的,不可能每一个系统调用都对应一个中断号,更加合理的是用一个或少数几个中断号来对应所有的系统滴啊用。比如Linux中int 0x80来触发所有的系统调用。

系统调用会有一个系统调用号,表明是哪一个系统调用,这个系统调用通常就是系统调用在系统调用表中的位置。比如Linux中的fork函数调用号是2,这个系统调用号在执行int指令前就会被放到某个固定的寄存器中,对应的中断代码会取得这个系统调用号,并且调用正确的函数。

比如Linux中int 0x80为例,系统调用号使用eax来传入,用户将系统调用号保存在eax,然后使用int 0x80调用中断,中断服务传给信号就可以从eax取得系统调用号,进行调用相应的函数。

基于int的Linux经典系统调用

下面以fork为例

触发中断

首先当程序在代码里面调用一个系统调用时,是以一个函数的形式调用的,比如fork:

int main() {
 fork();
}

fork函数是对系统调用fork的封装,可以用下面的宏定义:

_syscall0(pid_t, fork)

_syscall0是一个宏,用于定义一个没有参数的系统调用封装。他的第一个参数为这个系统调用的返回值类型,pid_t代表进程的id。第二个参数是系统调用的名字。_syscall0展开之后 会形成一个与系统调用名称同名的函数。下面是i386的syscall0

汇编解释:

  • __asm__是一个gcc关键字,表示接下来要嵌入汇编代码,volatile关键字告诉GCC对这段代码不进行任何优化
  • __asm__第一个参数是一个字符串,代表汇编代码的文本,这里汇编代煤制油int $0x80,表示要调用0x80号中断
  • =a(__res)表示用eax(a表示eax)输出返回数据并存储到_res中
  • 0(_NR ##name)表示用_NR ##name为输入,0指示由编译器选择和输出相同的寄存器(eax)来传递参数。

__syscal_return是另外一个宏

最终fork函数汇编之后


当系统调用如果有参数的话会用syscall1


相比syscall0多了个b,它表示把arg1强制转换为long,然后保存在ebx最为输入。

所以如果系统内调用如果有1个参数,则参数通过ebx来传递。x86下的linux系统调用参数最多有6个。分别使用6个寄存器来传递。分别是ebx,ecx,ed想,esi,edi和ebp。

当进行系统调用的时候,CPU执行到int $0x80时,会保存现场以便恢复。接着切换到内核态,然后CPU查找中断向量表低0x80元素。

切换堆栈

在实际执行中断向量表中的第0x80好中断之前,CPU还要进行堆栈的奇幻,在Linx中,用户态和内核态使用的是不同的栈,两者各自负责各自的函数调用,互不干扰。

在执行0x80中断的时候,程序从用户态切换到内核态。这时相应的栈也必须切换到内核栈,从中断处理函数中返回时,程序当前栈需要从内核栈切换到用户栈。

当前栈是指ESP值所在的栈空间,如果ESP的值位于用户栈范围能,那么程序的当前栈就是用户栈。内核栈同理。并且SS的值需要指向当前栈所在的页。所以用户栈切换到内核栈就是:

  • 保存当前ESP、SS的值
  • 将ESP、SS的值设置为内核栈的值

(反过来同理)

  • 恢复原理ESP、SS的值
  • 用户态的ESP和SS的值保存在内核栈上,

当发生中断的时候,CPU除了进入内核态之外还会做如下事情:

  • 找到当前进程的内核栈(每一个进程都有一个内核栈)
  • 在内核栈中一猜压入用户态的寄存器SS、ESP、EFLAGS、CS、EIP

当内核从系统调用中返回,则调动iret指令回到用户态,iret指令会从内核栈里面弹出SS、ESP、EFLAGS、CS、EIP的值。使得栈恢复到用户态的状态。


中断处理程序

在int指令切换了栈之后,程序就切换到中断向量表转给你记录0x80号中断处理陈旭。下面是linux i386中断服务流程

内核的系统调用函数往往以sys_加上系统调用函数名了,比如sys_fork,sys_open等。

关于sys开头的系统内调用函数如何从用户那里获得参数的。是通过寄存实现的。我们知道用户调用系统调用时,根据系统电泳参数数量不同,一次将参数放入EBX,EXC,EDX,ESI,EBP这6个寄存器。如果一个参数的系统调用就是EBX,两个参数的系统调用就是EBX和ECX

小结

通过阅读,归根结底还是要懂汇编并且去看源码才能把整个过程分析正确。平时所使用的把所有的细节都已经屏蔽了。地下还深藏着许多玄机。——而这一点确实大都数开发人员的短板

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容