五、系统调用-创新互联

5.1 与内核通信五、系统调用

系统调用主要作用:1、它为用户空间提供了一种硬件抽象层接口。例如,当需要读写文件的时候,应用程序就可以不去管磁盘介质和类型,甚至不用去管文件所在的文件系统是哪种类型。2、系统调用保证了系统的稳定和安全。作为硬件设备和应用程序之间的中间人,内核可以基于权限、用户类型和其他一些规则对需要进行访问进行裁决。3、如果应用程序可以随意访问硬件而内核对此一无所知的话,几乎无法实现多任务和虚拟内存,当然也不可能实现良好的稳定性和安全性。在linux中,系统调用是用户空间访问内核的唯一手段;除了异常和陷入外,他们是内核唯一的合法入口。

目前创新互联已为1000+的企业提供了网站建设、域名、网页空间、网站托管维护、企业网站设计、武汉网站维护等服务,公司将坚持客户导向、应用为本的策略,正道将秉承"和谐、参与、激情"的文化,与客户和合作伙伴齐心协力一起成长,共同发展。

5.2 API、POSIX和C库

一般情况下,应用程序通过在用户空间实现的应用编程接口(API)而不是直接通过系统调用来编程。应用程序使用API实际上并不需要知道内核提供的系统调用。API可以在各种不同的操作系统上实现,给应用程序提供完全相同的接口,而他们本身在这种系统上的实现却可能迥异。

5.3 系统调用

访问系统调用的访问方式:通常通过C库中定义的函数调用来进行,他们通常都需要定义0个或多个参数而且可能产生一些副作用(会使系统状态发生某种变化),系统调用会通过一个long型的返回值表示成功或错误。系统调用在出现错误的时候会C库会把错误码写入error全局变量。通过perror()库函数,可以把该变量翻译成用户可理解的错误字符串。

SYSCALL_DEFINE0只是一个宏,它定义一个无参数的系统调用因为数字0,SYSCALL_DEFINE0(getpid)展开后的代码如下:

asmlinkage long sys_getpid(void)

定义系统调用:asmlinkage限定词是一个编译指令,通知编译器仅从栈中提取该函数的参数,所有的系统调用都需要这个限定词。为了保证32位和64位的兼容,系统调用在用户空间和讷河空间有不同的返回值类型,在用户空间int,内核long。

5.3.1 系统调用号

在linux中,每个系统调用被赋予一个系统调用号。这样,通过这个独一无二的号就可以关联系统调用。系统调用号一旦分配就不能再有任何变更,如果一个系统调用被删除,它所占用的系统调用号不允许被回收利用。内核记录了系统调用表中所有已经注册过的系统调用的列表,存储在sys_call_table中。

5.3.2 系统调用的性能

linux系统调用比其他徐国操作系统执行得要快:很短的上下文切换时间,进出内核很高效;系统调用处理程序和系统调用本身很简洁。

5.4 系统调用处理程序

用户空间无法直接执行内核代码,因为内核驻留在受保护的地址空间上。应用程序应该以某种方式通知系统,告诉内核自己需要执行一个系统调用,希望切换到内核态,这样内核就可以代表应用程序在内核空间执行系统调用。

通知内核的机制是依靠软终端实现的:通过引发一个异常来促使系统切换到内核态去执行异常处理程序。此时的异常处理程序实际上就是系统调用处理程序。在x86系统上预定义的软中断中断号为128,通过int$0x80指令触发该中断。这条指令会触发一个异常导致系统切换到内核态并执行第128号异常处理程序,而该程序正是系统调用处理程序。这个处理程序的名字叫system_call()。x86处理器增加了一条叫做sysenter的指令,与int中断指令相比,这条指令提供了更快、更专业的陷入内核执行系统调用的方式。

5.4.1 执行恰当的系统调用

仅仅陷入内核空间时不够的。必须把系统调用号一并传入给内核。x86上,系统调用号是通过eax寄存器传递给内核的。在陷入内核之前,用户空间就把相应系统调用所对应的号放入eax中。由于系统调用表中的表项是以64位8字节类型存放的,所以内核需要将给定的系统调用号乘以8,然后用所得结果在表中查询表项。

5.4.2 参数传递

x86-32系统上,ebx、ecx、edx、esi、edi按照顺序存放前五个参数,需要六个或六个以上的参数时,应该用一个单独的寄存器存放。

给用户空间的返回值也通过寄存器传递。x86上,它存放在eax寄存器中。

5.5 系统调用的实现

5.5.1 实现系统调用 明确系统调用的用途

5.5.2 参数验证 必须仔细检查他们所有的参数是否合法有效。举例来说与I/O相关的系统调用必须检查文件描述符是否有效。与进程相关的函数必须检查PID是否有效。检查用户提供的指针是否有效,在收到一个用户空间的指针前,内核必须保证:

1、指针指向的内存区域属于用户空间。进程绝不能哄骗内核去读取内核空间的数据。

2、指针指向的内存区域在进程的地址空间里。进程决不能哄骗内核去读取其他进程的数据。

3、如果是读,该内存应该被标记为可读;写则标记为可写;执行则标记为可执行。进程不能绕过内存访问限制。

内核提供了两个方法来完成必须的检查和内核空间与用户空间之间数据的来回拷贝。copy_to_user()/copy_from_user(),都可能引起阻塞,当包含用户数据的页被换出到硬盘上而不是在物理内存上的时候,这种情况就会发生。此时,进程就会休眠,直到缺页处理程序将该页从硬盘重新换回物理内存。

调用者可以使用capable()函数来检查是否有权能对指定的资源进行操作。

5.6 系统调用上下文

内核在处理系统调用的时候处于进程上下文。current指针指向当前任务,即引发系统调用的那个进程。在进程上下文中,内核可以休眠且可以被抢占。能够休眠说明系统调用可以使用内核提供的绝大部分功能。能够被抢占说明像用户空间的进程一样,当前的进程可以被其他进程抢占。因为新的进程可以使用相同的系统调用,所以必须小心,保证该系统调用是可重入的。

5.6.1 绑定一个系统调用的最后步骤

当编写完一个系统调用后,把它注册为一个正式的系统调用:

1、在系统调用表的最后加入一个表项

2、对于所支持的个中体系结构,系统调用号都必须定义于asm/unistd.h中

3、系统调用必须被编译进内核映像,不能编译成模块

5.6.2 从用户空间访问系统调用

通常,系统调用靠C库支持。用户程序通过包含标准头文件并和C库链接,就可以使用系统调用。但如果你仅仅写出系统调用,glibc库恐怕并不提供支持。

linux本身提供了一组宏,用于直接对系统调用进行访问。它会设置好寄存器并调用陷入指令。这些宏是_syscalln(),其中n的范围从0到6,代表需要传递给系统调用的参数个数。举例:open()系统调用的定义是:

long open(const char *filename,int flags,int mode)

而不靠库支持,直接调用此系统调用的宏的形式为:

#define NR_open 5

_syscall3(long,open,const char*,filename,int,flags,int,mode)

对于每个宏来说,都由2+2*n个参数,第一个参数对应系统调用返回值类型,第二个对应系统调用名称。再以后是按照系统调用参数的顺序排列的每个参数的类型和名称。

5.6.3 为什么不通过系统调用的方式实现

建立一个新的系统调用的好处:

1、系统调用的创建容易、使用方便

2、linux系统调用的系统高

问题:

1、需要系统调用号,需要官方分配

2、系统调用被固化了,接口不允许改动

3、需要将系统调用注册到每个支持的体系结构中去

4、在脚本中不容易调用系统调用,也不能从文件系统直接访问系统调用

5、由于你需要系统调用号,因此在主内核树之外是很难维护和使用系统调用的。

6、如果仅仅进行简单的信息交换,系统调用就大材小用了。

替代方法:

1、实现一个设备节点,并对此实现read()和write()方法。使用ioctl()对特定的设置进行操作或对特定的信息进行检索。

2、像信号量这样的某些接口,可以用文件描述符来标识,因此也就可以按照上述方式对其进行操作。

3、把增加的信息作为一个文件放在sysfs的合适位置。


网站名称:五、系统调用-创新互联
URL分享:http://myzitong.com/article/cssspi.html