浅析linux中open系统调用

从2.6.19的linux内核开始,内核的系统调用使用函数syscall,其函数原型为:int syscall(int number, ...)其中number是系统调用号,number后面应顺序接上该系统调用的所有参数。以x86平台为例,系统调用号在内核源码中的路径是/arch/x86/include/asm/unistd_32.h头文件中定义。其中大部分以__NR_开头,比如open的系统调用号是5。

本文以字符设备的驱动为例,分析系统调用的执行过程,内核版本为2.6.35.

1、用户空间到内核的转换

系统调用需要一个从用户空间到内核空间的转换,不同平台转换的指令不同,这种特定的指令称作操作系统的陷入(operating system trap)。X86结构中使用软中断x080来实现。即汇编指令 int $0x80.通过软中断0x80 ,系统就会跳到一个预设的内核空间地址。它指向系统调用处理程序system_call,在arch/x86/kernel/entry_32.S 中以汇编语言编写,该过程主要有2个步骤。

(1)系统启动时,对INT 0x80进行一定的初始化。

使用汇编子程序setup_idt(linux/arch/i386/kernel/head.S)初始化idt表(中断描述符表),这时所有的入口函数偏移地址都被设为ignore_int ,如下图所示。

浅析linux中open系统调用_第1张图片

(2)设置中断描述符表

start_kernel函数中(init/main.c)调用trap_init()(arch/x86/kernel/trap.c)函数,设置中断描述符表。在trap_init()该函数里,实际上是通过调用函数set_system_trap_gate(SYSCALL_VECTOR, &system_call);来完成该项的设置的。其中的SYSCALL_VECTOR就是0x80,而system_call则是一个汇编子函数,它即是中断0x80的处理函数,主要完成两项工作:寄存器上下文的保存、跳转到系统调用处理函数。

2、系统调用函数的入口

syscall_call函数到系统调用服务例程:在上面执行软终端0x80时,系统调用号会被放入eax寄存器,system_call()函数读取eax寄存器获取当前系统调用的调用号。然后将其乘以4生成偏移地址,然后再以sys_call_table为基址。基址+偏移地址=>系统调用服务例程的地址。其中sys_call_table基址在文件arch/x86/kernel/syscall_table_32.S中定义。同时table表中每一项例程的地址占用4个字节,所以上面乘以4。

浅析linux中open系统调用_第2张图片

到这儿system_call()就到服务例程的地址了。然后另一个问题-参数传递需要解决。由于系统调用例程在定义时时用 asmlinkage 标记了的,所以编译器仅从堆栈中获取该函数的参数。在进入system_call函数前,用户应用会把参数存放到寄存器中,system_call函数执行时会首先把这些寄存器压入堆栈。这样对系统调用服务例程可以直接从堆栈照片能够获取参数。

3、系统调用函数的执行

open会最终执行sys_open函数:

浅析linux中open系统调用_第3张图片

里面调用do_sys_open()函数:

浅析linux中open系统调用_第4张图片

在上图的L884行中,获取未被使用的文件描述符,在do_filp_open()函数中打开文件。其函数调用过程如下:

sys_open -> do_sys_open -> do_filp_open ->do_last-> nameidata_to_filp -> __dentry_open

4、驱动代码file_operations的调用

在 __dentry_open函数中:

浅析linux中open系统调用_第5张图片

L685中 open = f->f_op->open;即调用chrdev_open。

在file: fs/char_dev.c中static int chrdev_open(struct inode *inode, struct file *filp)调用ret = filp->f_op->open(inode,filp);即为真正的file_operations中的open函数。

当然,在VFS层中对open函数的操作远不止上文描述的这么简单,会进行权限和打开方式的判断等。普通的驱动开发者很少涉及对这部分代码的修改,主要还是学习和欣赏Linux内核。

你可能感兴趣的:(linux系统内核)