linux-kernel 2.6源码阅读[1]系统调用

一. 概述

系统调用是用户态程序与内核之间的交互的直接接口,用户态程序通过系统调用来请求各种各样的服务。linux提供了约200多个系统调用,在实现上,所有系统调用都有着相同的入口,遵循着相同的执行框架。
这套框架的核心是对所有系统调用进行编号,所有系统调用都是从同一入口进入,该入口是一条能实现特权级提升的指令,该指令完成用户态到系统态的转变,并最终跳转到内核中一个叫做系统调用处理程序的函数中,根据提供的系统调用号,处理程序再跳转到相应的事务程序中。

二. 系统调用的分类

在2.6版本中,约有200余个系统调用,按照功能,可以分成以下几个大类。

    1. 进程管理
      fork, clone, vfork用于进程的创建,exit用于进程的退出,setrlimit用于设置对进程的资源限制,nice用于调整进程的优先级,execve装载一个新进程,此外还有大量用于查询进程属性的系统调用。
    1. 内存管理
      brk用于malloc内存分配,mmap、munmap用于映射和解除映射,swapon用于开启交换区。
    1. 文件操作
      open、 close、read、 write、chdir、mkdir、 rmdir、 rename、 link、 symlink、mount和umount等,这部分系统调用的名称与c库中的函数名基本可以对应。
    1. 信号处理
      signal设置处理函数、sigpending检查是否有需要处理的信号。
    1. 进程间通信和网络功能
      虽然该部分功能复杂,但只有两个系统调用,socketcall实现了非常多的功能,与网络有关的功能都是通过这一调用,ipc调用实现本地的通讯。
    1. 时间操作
      adjtimex调整内核中的时间变量,gettimeofday、settimeofday操作当前系统时间,time返回自1970年来经过的秒数。

三. 系统调用的实现

正如上文提到的,系统调用的执行过程分为系统调用处理程序和事务程序两个部分,事务程序用于实现具体的事务我们暂时不去关注,在本节中介绍系统调用处理程序的实现框架。
在早期版本中,linux使用int 0x80实现系统调用,int指令使用软件的方式来触发一次中断,中断号为0x80,使用0x80中断的中断处理程序来作为系统调用的入口。但int指令毕竟不是专门用于系统调用,x86在奔腾II中引入了sysenter指令用于实现快速系统调用,下面分别对两种方式进行介绍。

3.1 int 0x80方法

中断描述符初始化:在内核初始化阶段,使用set_system_gate(0x80,&system_call)语句来设置80号中断使用的中断门,中断门设置的过程中,需要指定中断处理函数的入口——即system_call函数,将DPL字段置为3,使得用户态下的代码可以访问这个中断门,这也是实现用户态到内核态跳转的关键一步,同时在中断门中,将type设置为15,代表其为陷进不可被屏蔽。

//@arch/i386/kernel/traps.c  设置系统调用所需的中断门
set_system_gate(0x80,&system_call);

//@arch/i386/kernel/traps.c
static void __init set_system_gate(unsigned int n, void *addr)
{
    _set_gate(idt_table+n,15,3,addr,__KERNEL_CS);
}

//@arch/i386/kernel/vsyscall-int80.S
__kernel_vsyscall:   //vsyscall方法,用于实现两种调用方法并存
    int $0x80        // 该指令是整个系统调用的核心
    ret

系统调用的进入:随后,一般是由c库来执行int 0x80指令,该指令在执行时,会将现eip现esp等信息压入内核栈中,并触发中断,系统调用号通过eax来传递。进入system_call函数后,1)首先保存eax中系统调用号,再调用SAVE_ALL宏保存架构寄存器的现场。2)system_call使用GET_THREAD_INFO宏来获得thread_info的地址,并对进程的属性做一些检查。3)随后使用一条cmpl语句来检查系统调用号是否合法,如果正确则使用 call *sys_call_table(,%eax,4)指令调用相应的系统调用事务程序, 4)在这里,内核使用一个称为sys_call_table的数组保存事务程序的入口地址,系统调用号就是数组的下标,所以使用数组的基址和下标就可以找到各个调用的事务程序。

//@arch/i386/kernel/entry.S //系统调用处理函数,int 80后执行的第一行代码
ENTRY(system_call)
    pushl %eax          # 系统调用号
    SAVE_ALL            # 保存用户态现场
    GET_THREAD_INFO(%ebp)
    testb $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT),TI_flags(%ebp)
    jnz syscall_trace_entry
    cmpl $(nr_syscalls), %eax  # 检查调用号是否合法
    jae syscall_badsys
syscall_call:
    call *sys_call_table(,%eax,4)  # 跳转到事务函数
    movl %eax,EAX(%esp)     # 保存事务函数的返回值

//@arch/i386/kernel/entry.S 由系统调用事务函数入口组成的数组
.data
ENTRY(sys_call_table)
    .long sys_restart_syscall   /* 0 - old "setup()" system call, used for restarting */
    .long sys_exit
    .long sys_fork
    .long sys_read
    .long sys_write
    .long sys_open      /* 5 */
    .long sys_close

系统调用的返回:从事务程序返回后,处理程序保存返回值,1)如果没有异常,则调用RESTORE_ALL宏来回复寄存器现场,并使用一条iret指令来返回用户态程序,iret指令使用栈顶的一个地址填入eip,并恢复esp和eflag。2)如果还有一些标志被设置,则退出前还有一些工作需要做,resume_userspace和work_pending检查调度请求、v86模式、挂起信号等,这些工作完成后返回RESTORE_ALL处退出。
事务程序的参数传递:对于一般的程序,在调用前将用到的参数压栈即可,而系统调用横跨内核栈和用户栈,同时操作两个栈不切实际,所以事务程序的参数采用寄存器和用户空间变量传递,x86有7个通用寄存器,除eax用于传递调用号之外,其余6个均可用于参数传递,在进入服务程序后,先将这些寄存器压入内核栈中,随后跳转到事务程序,事务程序就可以像普通函数那样使用参数。如果有超过6个参数,可以在寄存器中传递指向用户空间的指针,使用指针来从用户空间获取更多参数,内核提供了get_user()和put_user()方法操作用户空间中的变量。

3.2 sysenter方法

引入sysenter的目的:系统调用实现方式是计算机系统结构中,系统软件与硬件协同发展一个例子。linux 0.11设计的目标机型是i386,这是第一款实现保护模式的x86处理器,所以硬件和软件的仍然有许多需要磨合的地方,系统调用在事务密集型的应用中大量出现,所以要尽量缩短系统调用的时间。使用软中断的方式并不适合,在执行int 0x80指令时会进行一些安全检查和一致性检查,对于系统调用来说,这是没有必要的。
sysenter指令:因此intel在奔腾II中引入了sysenter指令,用来快速从用户态切换到系统态。该指令配合SYSENTER_CS、SYSENTER_ESP、SYSENTER_EIP 3个寄存器使用,1)在执行sysenter指令时,处理器会将特权级由3提升到0,并分别将这3个寄存器的值压入cs、esp、eip中,同时,将cs的下一个段描述符自动压入ss寄存器;2)在内核初始化时,enable_sep_cpu函数会将SYSENTER_CS、SYSENTER_EIP寄存器的值进行初始化,但SYSENTER_ESP是动态的,所以在每次调用时由处理函数手动计算。

//@arch/i386/kernel/sysenter.c 该函数设置sysenter指令所需的3个寄存器
void enable_sep_cpu(void *info)
{           
          .......
    wrmsr(MSR_IA32_SYSENTER_CS, __KERNEL_CS, 0);   //设定sysenter指令跳转的目的地
    wrmsr(MSR_IA32_SYSENTER_ESP, tss->esp1, 0);
    wrmsr(MSR_IA32_SYSENTER_EIP, (unsigned long) sysenter_entry, 0);
          .......
}

sysenter的进入:当执行sysenter指令时,提升特权级并进入sysenter_entry函数,1)进去后从TSS中取出esp0的值,完成内核栈的切换,2)因为执行完成后还是从原来int 80处理程序的iret返回用户态,所以需要将eip、esp等的值压入栈中供iret指令使用,3)随后保存用户态的现场,执行一些检查后,call *sys_call_table(,%eax,4) 调用事务函数。

//@arch/i386/kernel/entry.S //系统调用处理函数,sysenter后执行的第一行代码
ENTRY(sysenter_entry)
    movl TSS_sysenter_esp0(%esp),%esp   # 从TSS中取出esp的值放入esp寄存器
sysenter_past_esp:
    sti
    pushl $(__USER_DS)                 # push5个寄存器的值,为iret做准备
    pushl %ebp
    pushfl
    pushl $(__USER_CS)
    pushl $SYSENTER_RETURN
    SAVE_ALL
    ......
    call *sys_call_table(,%eax,4)

四. vsyscall处理兼容性问题

我们目前有两种系统调用的实现方式,就必须要坚决CPU、系统调用接口、c库之间的匹配问题。2.6中的解决方案是,c库不直接执行int 80或sysenter指令,而是将它们封装到一个vsyscall接口中,c库调用vsyscall,而vsyscall再根据cpu的支持情况调用int 80或者sysenter。实现的方式类似于动态连接,将vsyscall函数在装载时动态链接,只不过vsyscall是在内核初始化阶段设置地址的。

Reference:
[1] Daniel P. Bovet, Marco Cesati. Understanding the Linux kernel, Third Edition[M]. O'Reilly & Associates Inc, 2005.
[2] Robert Love. Linux kernel development [M]. China Machine Press, 2011.
[3] Wolfgang Mauerer. Professional Linux kernel architecture[M]. 人民邮电出版社, 2010.

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

推荐阅读更多精彩内容