【嵌入式Linux学习七步曲之第五篇 Linux内核及驱动编程】Linux系统调用的实现机制分析

Linux系统调用的实现机制分析

 

Sailor_forever  [email protected] 转载请注明

http://blog.csdn.net/sailor_8318/archive/2008/09/10/2906968.aspx

 

【摘要】本文介绍了系统调用的一些实现细节。首先分析了系统调用的意义,它们与库函数和应用程序接口(API)有怎样的关系。然后,我们考察了Linux内核如何实现系统调用,以及执行系统调用的连锁反应:陷入内核,传递系统调用号和参数,执行正确的系统调用函数,并把返回值带回用户空间。最后讨论了如何增加系统调用,并提供了从用户空间访问系统调用的简单例子。

 

【关键字】系统调用,软件中断,库函数,INT 80,系统调用号,参数检查,errnoperror

 

1       系统调用意义

linux内核中设置了一组用于实现系统功能的子程序,称为系统调用。系统调用和普通库函数调用非常相似,只是系统调用由操作系统核心提供,运行于核心态,而普通的函数调用由函数库或用户自己提供,运行于用户态。

 

一般的,进程是不能访问内核的。它不能访问内核所占内存空间也不能调用内核函数。CPU硬件决定了这些(这就是为什么它被称作"保护模式")。为了和用户空间上运行的进程进行交互,内核提供了一组接口。透过该接口,应用程序可以访问硬件设备和其他操作系统资源。这组接口在应用程序和内核之间扮演了使者的角色,应用程序发送各种请求,而内核负责满足这些请求(或者让应用程序暂时搁置)。实际上提供这组接口主要是为了保证系统稳定可靠,避免应用程序肆意妄行,惹出大麻烦。

 

系统调用在用户空间进程和硬件设备之间添加了一个中间层。该层主要作用有三个:

²      它为用户空间提供了一种统一的硬件的抽象接口。比如当需要读些文件的时候,应用程序就可以不去管磁盘类型和介质,甚至不用去管文件所在的文件系统到底是哪种类型。

²      系统调用保证了系统的稳定和安全。作为硬件设备和应用程序之间的中间人,内核可以基于权限和其他一些规则对需要进行的访问进行裁决。举例来说,这样可以避免应用程序不正确地使用硬件设备,窃取其他进程的资源,或做出其他什么危害系统的事情。

²      每个进程都运行在虚拟系统中,而在用户空间和系统的其余部分提供这样一层公共接口,也是出于这种考虑。如果应用程序可以随意访问硬件而内核又对此一无所知的话,几乎就没法实现多任务和虚拟内存,当然也不可能实现良好的稳定性和安全性。在Linux中,系统调用是用户空间访问内核的惟一手段;除异常和中断外,它们是内核惟一的合法入口。

 

2       API/POSIX/C库的关系

一般情况下,应用程序通过应用编程接口(API)而不是直接通过系统调用来编程。这点很重要,因为应用程序使用的这种编程接口实际上并不需要和内核提供的系统调用一一对应。一个API定义了一组应用程序使用的编程接口。它们可以实现成一个系统调用,也可以通过调用多个系统调用来实现,而完全不使用任何系统调用也不存在问题。实际上,API可以在各种不同的操作系统上实现,给应用程序提供完全相同的接口,而它们本身在这些系统上的实现却可能迥异。

 

Unix世界中,最流行的应用编程接口是基于POSIX标准的,其目标是提供一套大体上基于Unix的可移植操作系统标准。POSIX是说明API和系统调用之间关系的一个极好例子。在大多数Unix系统上,根据POSIX而定义的API函数和系统调用之间有着直接关系。

 

Linux的系统调用像大多数Unix系统一样,作为C库的一部分提供如下图所示。C库实现了 Unix系统的主要API,包括标准C库函数和系统调用。所有的C程序都可以使用C库,而由于C语言本身的特点,其他语言也可以很方便地把它们封装起来使用。

 

从程序员的角度看,系统调用无关紧要,他们只需要跟API打交道就可以了。相反,内核只跟系统调用打交道;库函数及应用程序是怎么使用系统调用不是内核所关心的。

 

关于Unix的界面设计有一句通用的格言“提供机制而不是策略”。换句话说,Unix系统调用抽象出了用于完成某种确定目的的函数。至干这些函数怎么用完全不需要内核去关心。区别对待机制(mechanism)和策略(policy)Unix设计中的一大亮点。大部分的编程问题都可以被切割成两个部分:“需要提供什么功能”(机制)和“怎样实现这些功能”(策略)

 

3       系统调用的实现

3.1    系统调用处理程序

您或许疑惑: “当我输入 cat /proc/cpuinfo 时,cpuinfo() 函数是如何被调用的?内核完成引导后,控制流就从相对直观的接下来调用哪个函数?改变为取决于系统调用、异常和中断。

 

用户空间的程序无法直接执行内核代码。它们不能直接调用内核空间中的函数,因为内核驻留在受保护的地址空间上。如果进程可以直接在内核的地址空间上读写的话,系统安全就会失去控制。所以,应用程序应该以某种方式通知系统,告诉内核自己需要执行一个系统调用,希望系统切换到内核态,这样内核就可以代表应用程序来执行该系统调用了。

 

通知内核的机制是靠软件中断实现的。首先,用户程序为系统调用设置参数。其中一个参数是系统调用编号。参数设置完成后,程序执行系统调用指令。x86系统上的软中断由int产生。这个指令会导致一个异常:产生一个事件,这个事件会致使处理器切换到内核态并跳转到一个新的地址,并开始执行那里的异常处理程序。此时的异常处理程序实际上就是系统调用处理程序。它与硬件体系结构紧密相关。

 

新地址的指令会保存程序的状态,计算出应该调用哪个系统调用,调用内核中实现那个系统调用的函数,恢复用户程序状态,然后将控制权返还给用户程序。系统调用是设备驱动程序中定义的函数最终被调用的一种方式。

 

3.2    系统调用号

Linux中,每个系统调用被赋予一个系统调用号。这样,通过这个独一无二的号就可以关联系统调用。当用户空间的进程执行一个系统调用的时候,这个系统调用号就被用来指明到底是要执行哪个系统调用。进程不会提及系统调用的名称。

 

系统调用号相当关键,一旦分配就不能再有任何变更,否则编译好的应用程序就会崩溃。Linux有一个“未实现”系统调用sys_ni_syscall()它除了返回一ENOSYS外不做任何其他工作,这个错误号就是专门针对无效的系统调用而设的。

 

因为所有的系统调用陷入内核的方式都一样,所以仅仅是陷入内核空间是不够的。因此必须把系统调用号一并传给内核。在x86上,系统调用号是通过eax寄存器传递给内核的。在陷人内核之前,用户空间就把相应系统调用所对应的号放入eax中了。这样系统调用处理程序一旦运行,就可以从eax中得到数据。其他体系结构上的实现也都类似。

 

内核记录了系统调用表中的所有已注册过的系统调用的列表,存储在sys_call_table中。它与体系结构有关,一般在entry.s中定义。这个表中为每一个有效的系统调用指定了惟一的系统调用号。sys_call_table是一张由指向实现各种系统调用的内核函数的函数指针组成的表

ENTRY(sys_call_table)

.long SYMBOL_NAME(sys_ni_syscall) /* 0 - old "setup()" system call*/

.long SYMBOL_NAME(sys_exit)

.long SYMBOL_NAME(sys_fork)

.long SYMBOL_NAME(sys_read)

.long SYMBOL_NAME(sys_write)

.long SYMBOL_NAME(sys_open) /* 5 */

.long SYMBOL_NAME(sys_close)

.long SYMBOL_NAME(sys_waitpid)

。。。。。

.long SYMBOL_NAME(sys_capget)

.long SYMBOL_NAME(sys_capset)      /* 185 */

.long SYMBOL_NAME(sys_sigaltstack)

.long SYMBOL_NAME(sys_sendfile)

.long SYMBOL_NAME(sys_ni_syscall) /* streams1 */

.long SYMBOL_NAME(sys_ni_syscall) /* streams2 */

.long SYMBOL_NAME(sys_vfork)      /* 190 */

 

system_call()函数通过将给定的系统调用号与NR_syscalls做比较来检查其有效性。如果它大于或者等于NR syscalls,该函数就返回一ENOSYS。否则,就执行相应的系统调用。

      call *sys_ call-table(%eax, 4)

由于系统调用表中的表项是以32(4字节)类型存放的,所以内核需要将给定的系统调用号乘以4,然后用所得的结果在该表中查询其位置

 

3.3    参数传递

除了系统调用号以外,大部分系统调用都还需要一些外部的参数输人。所以,在发生异常的时候,应该把这些参数从用户空间传给内核。最简单的办法就是像传递系统调用号一样把这些参数也存放在寄存器里。在x86系统上,ebx, ecx, edx, esiedi按照顺序存放前五个参数。需要六个或六个以上参数的情况不多见,此时,应该用一个单独的寄存器存放指向所有这些参数在用户空间地址的指针。

 

给用户空间的返回值也通过寄存器传递。在x86系统上,它存放在eax寄存器中。接下来许多关于系统调用处理程序的描述都是针对x86版本的。但不用担心,所有体系结构的实现都很类似。

 

3.4    参数验证

系统调用必须仔细检查它们所有的参数是否合法有效。举例来说,与文件I/O相关的系统调用必须检查文件描述符是否有效。与进程相关的函数必须检查提供的PID是否有效。必须检查每个参数,保证它们不但合法有效,而且正确。

 

最重要的一种检查就是检查用户提供的指针是否有效。试想,如果一个进程可以给内核传递指针而又无须被检查,那么它就可以给出一个它根本就没有访问权限的指针,哄骗内核去为它拷贝本不允许它访问的数据,如原本属于其他进程的数据。在接收一个用户空间的指针之前,内核必须保证:

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

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

²      如果是读,该内存应被标记为可读。如果是写,该内存应被标记为可写。进程决不能绕过内存访问限制。

 

内核提供了两个方法来完成必须的检查和内核空间与用户空间之间数据的来回拷贝。注意,内核无论何时都不能轻率地接受来自用户空间的指针!这两个方法中必须有一个被调用。为了向用户空间写入数据,内核提供了copy_to_user(),它需要三个参数。第一个参数是进程空间中的目的内存地址。第二个是内核空间内的源地址。最后一个参数是需要拷贝的数据长度(字节数)

 

为了从用户空间读取数据,内核提供了copy_from_ user(),它和copy-to-User()相似。该函数把第二个参数指定的位置上的数据拷贝到第一个参数指定的位置上,拷贝的数据长度由第三个参数决定。

 

如果执行失败,这两个函数返回的都是没能完成拷贝的数据的字节数。如果成功,返回0。当出现上述错误时,系统调用返回标准-EFAULT

 

注意copy_to_user()copy_from_user()都有可能引起阻塞。当包含用户数据的页被换出到硬盘上而不是在物理内存上的时候,这种情况就会发生。此时,进程就会休眠,直到缺页处理程序将该页从硬盘重新换回物理内存。

 

3.5    系统调用的返回值

系统调用(Linux中常称作syscalls)通常通过函数进行调用。它们通常都需要定义一个或几个参数(输入)而且可能产生一些副作用,例如写某个文件或向给定的指针拷贝数据等等。为防止和正常的返回值混淆,系统调用并不直接返回错误码,而是将错误码放入一个名为errno的全局变量中。通常用一个负的返回值来表明错误。返回一个0值通常表明成功。如果一个系统调用失败,你可以读出errno的值来确定问题所在。通过调用perror()库函数,可以把该变量翻译成用户可以理解的错误字符串。

 

errno不同数值所代表的错误消息定义在errno.h中,你也可以通过命令"man 3 errno"来察看它们。需要注意的是,errno的值只在函数发生错误时设置,如果函数不发生错误,errno的值就无定义,并不会被置为0。另外,在处理errno前最好先把它的值存入另一个变量,因为在错误处理过程中,即使像printf()这样的函数出错时也会改变errno的值。

 

当然,系统调用最终具有一种明确的操作。举例来说,如getpid()系统调用,根据定义它会返回当前进程的PID。内核中它的实现非常简单:

asmlinkage long sys_ getpid(void)

{

    return current-> tgid;

}

 

上述的系统调用尽管非常简单,但我们还是可以从中发现两个特别之处。首先,注意函数声明中的asmlinkage限定词,这是一个小戏法,用于通知编译器仅从栈中提取该函数的参数。所有的系统调用都需要这个限定词。其次,注意系统调用get_pid()在内核中被定义成sys_ getpid这是Linux中所有系统调用都应该遵守的命名规则

 

4       添加新系统调用

Linux添加一个新的系统调用是件相对容易的工作。怎样设计和实现一个系统调用是难题所在,而把它加到内核里却无须太多周折。让我们关注一下实现一个新的Linux系统调用所需的步骤。

 

实现一个新的系统调用的第一步是决定它的用途。它要做些什么?每个系统调用都应该有一个明确的用途。Linux中不提倡采用多用途的系统调用(一个系统调用通过传递不同的参数值来选择完成不同的工作)ioctl()就应该被视为一个反例。

 

新系统调用的参数、返回值和错误码又该是什么呢?系统调用的接口应该力求简洁,参数尽可能少。设计接口的时候要尽量为将来多做考虑。你是不是对函数做了不必要的限制?系统调用设计得越通用越好。不要假设这个系统调用现在怎么用将来也一定就是这么用。系统调用的目的可能不变,但它的用法却可能改变。这个系统调用可移植吗?别对机器的字节长度和字节序做假设。当你写一个系统调用的时候,要时刻注意可移植性和健壮性,不但要考虑当前,还要为将来做打算。

 

当编写完一个系统调用后,把它注册成一个正式的系统调用是件琐碎的工作:

在系统调用表的最后加入一个表项。每种支持该系统调用的硬件体系都必须做这样的工作。从0开始算起,系统调用在该表中的位置就是它的系统调用号。

对于所支持的各种体系结构,系统调用号都必须定义于<asm/unistd.h>中。

系统调用必须被编译进内核映象(不能被编译成模块)。这只要把它放进kernel/下的一个相关文件中就可以。

 

让我们通过一个虚构的系统调用f00()来仔细观察一下这些步骤。首先,我们要把sys_foo加入到系统调用表中去。对于大多数体系结构来说,该表位干entry.s文件中,形式如下:

ENTRY(sys_ call_ table)

      ·long sys_ restart_ syscall/*0*/

      .long sys_ exit

      ·long sys_ fork

      ·long sys_ read

      .long sys_write

我们把新的系统调用加到这个表的末尾:

     .long sys_foo

虽然没有明确地指定编号,但我们加入的这个系统调用被按照次序分配给了283这个系统调用号。对于每种需要支持的体系结构,我们都必须将自己的系统调用加人到其系统调用表中去。每种体系结构不需要对应相同的系统调用号。

 

接下来,我们把系统调用号加入到<asm/unistd.h>中,它的格式如下:

/*本文件包含系统调用号*/

#define_ NR_ restart_ syscall

#define NR exit

#define NR fork

#define NR read

#define NR write

#define NR- mq getsetattr 282

然后,我们在该列表中加入下面这行:

#define_ NR_ foo 283

 

最后,我们来实现f00()系统调用。无论何种配置,该系统调用都必须编译到核心的内核映象中去,所以我们把它放进kernel/sys.c文件中。你也可以将其放到与其功能联系最紧密的代码中去

 

asmlinkage long sys-foo(void)

{

return THREAD SIZE

)

就是这样!严格说来,现在就可以在用户空间调用f00()系统调用了。

 

建立一个新的系统调用非常容易,但却绝不提倡这么做。通常模块可以更好的代替新建一个系统调用。

 

5       访问系统调用

5.1    系统调用上下文

内核在执行系统调用的时候处于进程上下文current指针指向当前任务,即引发系统调用的那个进程。

 

在进程上下文中,内核可以休眠并且可以被抢占。这两点都很重要。首先,能够休眠说明系统调用可以使用内核提供的绝大部分功能。休眠的能力会给内核编程带来极大便利。在进程上下文中能够被抢占,其实表明,像用户空间内的进程一样,当前的进程同样可以被其他进程抢占。因为新的进程可以使用相同的系统调用,所以必须小心,保证该系统调用是可重人的。当然,这也是在对称多处理中必须同样关心的问题。

 

当系统调用返回的时候,控制权仍然在system_call()中,它最终会负责切换到用户空间并让用户进程继续执行下去。

 

5.2    系统调用访问示例

操作系统使用系统调用表将系统调用编号翻译为特定的系统调用。系统调用表包含有实现每个系统调用的函数的地址。例如,read() 系统调用函数名为 sys_readread() 系统调用编号是 3,所以sys_read() 位于系统调用表的第四个条目中(因为系统调用起始编号为0)。从地址 sys_call_table + (3 * word_size) 读取数据,得到 sys_read() 的地址。

 

找到正确的系统调用地址后,它将控制权转交给那个系统调用。我们来看定义 sys_read() 的位置,即 fs/read_write.c 文件。这个函数会找到关联到 fd 编号(传递给 read() 函数的)的文件结构体。那个结构体包含指向用来读取特定类型文件数据的函数的指针。进行一些检查后,它调用与文件相关的 read() 函数,来真正从文件中读取数据并返回。与文件相关的函数是在其他地方定义的 —— 比如套接字代码、文件系统代码,或者设备驱动程序代码。这是特定内核子系统最终与内核其他部分协作的一个方面。

 

读取函数结束后,从 sys_read() 返回,它将控制权切换给 ret_from_sys。它会去检查那些在切换回用户空间之前需要完成的任务。如果没有需要做的事情,那么就恢复用户进程的状态,并将控制权交还给用户程序。

5.3    从用户空间直接访问系统调用

通常,系统调用靠C库支持。用户程序通过包含标准头文件并和C库链接,就可以使用系统调用(或者调用库函数,再由库函数实际调用)。但如果你仅仅写出系统调用,glibc库恐怕并不提供支持。值得庆幸的是,Linux本身提供了一组宏,用于直接对系统调用进行访问。它会设置好寄存器并调用陷人指令。这些宏是_syscalln(),其中n的范围从06。代表需要传递给系统调用的参数个数,这是由于该宏必须了解到底有多少参数按照什么次序压入寄存器。举个例子,open()系统调用的定义是:

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

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

#define NR_ open 5

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

这样,应用程序就可以直接使用open()

 

对于每个宏来说,都有2+ n个参数。第一个参数对应着系统调用的返回值类型。第二个参数是系统调用的名称。再以后是按照系统调用参数的顺序排列的每个参数的类型和名称。_NR_ open<asm/unistd.h>中定义,是系统调用号。该宏会被扩展成为内嵌汇编的C函数。由汇编语言执行前一节所讨论的步骤,将系统调用号和参数压入寄存器并触发软中断来陷入内核。调用open()系统调用直接把上面的宏放置在应用程序中就可以了。

 

让我们写一个宏来使用前面编写的foo()系统调用,然后再写出测试代码炫耀一下我们所做的努力。

#define NR foo 283

_sysca110(long, foo)

int main()

{

long stack size;

stack_ size=foo();

printf("The kernel stack

size is 81d/n"stack_ size);

return;

}

 

6       参考文档

linux系统调用实现代码分析

理解Linux的系统调用

什么是系统调用

Linux内核设计与实现

 

附录:2.4 内核Linux系统调用的列表

 

以下是Linux系统调用的一个列表,包含了大部分常用系统调用和由系统调用派生出的的函数。其中有一些函数的作用完全相同,只是参数不同。可能很多熟悉C++朋友马上就能联想起函数重载,但是别忘了Linux核心是用C语言写的,所以只能取成不同的函数名。

 

一、进程控制:

 

fork

创建一个新进程

clone

按指定条件创建子进程

execve

运行可执行文件

exit

中止进程

_exit

立即中止当前进程

getdtablesize

进程所能打开的最大文件数

getpgid

获取指定进程组标识号

setpgid

设置指定进程组标志号

getpgrp

获取当前进程组标识号

setpgrp

设置当前进程组标志号

getpid

获取进程标识号

getppid

获取父进程标识号

getpriority

获取调度优先级

setpriority

设置调度优先级

modify_ldt

读写进程的本地描述表

nanosleep

使进程睡眠指定的时间

nice

改变分时进程的优先级

pause

挂起进程,等待信号

personality

设置进程运行域

prctl

对进程进行特定操作

ptrace

进程跟踪

sched_get_priority_max

取得静态优先级的上限

sched_get_priority_min

取得静态优先级的下限

sched_getparam

取得进程的调度参数

sched_getscheduler

取得指定进程的调度策略

sched_rr_get_interval

取得按RR算法调度的实时进程的时间片长度

sched_setparam

设置进程的调度参数

sched_setscheduler

设置指定进程的调度策略和参数

sched_yield

进程主动让出处理器,并将自己等候调度队列队尾

vfork

创建一个子进程,以供执行新程序,常与execve等同时使用

wait

等待子进程终止

wait3

参见wait

waitpid

等待指定子进程终止

wait4

参见waitpid

capget

获取进程权限

capset

设置进程权限

getsid

获取会晤标识号

setsid

设置会晤标识号

 

二、文件系统控制

1、文件读写操作

fcntl

文件控制

open

打开文件

creat

创建新文件

close

关闭文件描述字

read

读文件

write

写文件

readv

从文件读入数据到缓冲数组中

writev

将缓冲数组里的数据写入文件

pread

对文件随机读

pwrite

对文件随机写

lseek

移动文件指针

_llseek

64位地址空间里移动文件指针

dup

复制已打开的文件描述字

dup2

按指定条件复制文件描述字

flock

文件加/解锁

poll

I/O多路转换

truncate

截断文件

ftruncate

参见truncate

umask

设置文件权限掩码

fsync

把文件在内存中的部分写回磁盘

 

2、文件系统操作

access

确定文件的可存取性

chdir

改变当前工作目录

fchdir

参见chdir

chmod

改变文件方式

fchmod

参见chmod

chown

改变文件的属主或用户组

fchown

参见chown

lchown

参见chown

chroot

改变根目录

stat

取文件状态信息

lstat

参见stat

fstat

参见stat

statfs

取文件系统信息

fstatfs

参见statfs

readdir

读取目录项

getdents

读取目录项

mkdir

创建目录

mknod

创建索引节点

rmdir

删除目录

rename

文件改名

link

创建链接

symlink

创建符号链接

unlink

删除链接

readlink

读符号链接的值

mount

安装文件系统

umount

卸下文件系统

ustat

取文件系统信息

utime

改变文件的访问修改时间

utimes

参见utime

quotactl

控制磁盘配额

 

三、系统控制

ioctl

I/O总控制函数

_sysctl

/写系统参数

acct

启用或禁止进程记账

getrlimit

获取系统资源上限

setrlimit

设置系统资源上限

getrusage

获取系统资源使用情况

uselib

选择要使用的二进制函数库

ioperm

设置端口I/O权限

iopl

改变进程I/O权限级别

outb

低级端口操作

reboot

重新启动

swapon

打开交换文件和设备

swapoff

关闭交换文件和设备

bdflush

控制bdflush守护进程

sysfs

取核心支持的文件系统类型

sysinfo

取得系统信息

adjtimex

调整系统时钟

alarm

设置进程的闹钟

getitimer

获取计时器值

setitimer

设置计时器值

gettimeofday

取时间和时区

settimeofday

设置时间和时区

stime

设置系统日期和时间

time

取得系统时间

times

取进程运行时间

uname

获取当前UNIX系统的名称、版本和主机等信息

vhangup

挂起当前终端

nfsservctl

NFS守护进程进行控制

vm86

进入模拟8086模式

create_module

创建可装载的模块项

delete_module

删除可装载的模块项

init_module

初始化模块

query_module

查询模块信息

*get_kernel_syms

取得核心符号,已被query_module代替

 

 

四、内存管理

brk

改变数据段空间的分配

sbrk

参见brk

mlock

内存页面加锁

munlock

内存页面解锁

mlockall

调用进程所有内存页面加锁

munlockall

调用进程所有内存页面解锁

mmap

映射虚拟内存页

munmap

去除内存页映射

mremap

重新映射虚拟内存地址

msync

将映射内存中的数据写回磁盘

mprotect

设置内存映像保护

getpagesize

获取页面大小

sync

将内存缓冲区数据写回硬盘

cacheflush

将指定缓冲区中的内容写回磁盘

 

五、网络管理

getdomainname

取域名

setdomainname

设置域名

gethostid

获取主机标识号

sethostid

设置主机标识号

gethostname

获取本主机名称

sethostname

设置主机名称

 

六、socket控制

socketcall

socket系统调用

socket

建立socket

bind

绑定socket到端口

connect

连接远程主机

accept

响应socket连接请求

send

通过socket发送信息

sendto

发送UDP信息

sendmsg

参见send

recv

通过socket接收信息

recvfrom

接收UDP信息

recvmsg

参见recv

listen

监听socket端口

select

对多路同步I/O进行轮询

shutdown

关闭socket上的连接

getsockname

取得本地socket名字

getpeername

获取通信对方的socket名字

getsockopt

取端口设置

setsockopt

设置端口参数

sendfile

在文件或端口间传输数据

socketpair

创建一对已联接的无名socket

 

 

七、用户管理

getuid

获取用户标识号

setuid

设置用户标志号

getgid

获取组标识号

setgid

设置组标志号

getegid

获取有效组标识号

setegid

设置有效组标识号

geteuid

获取有效用户标识号

seteuid

设置有效用户标识号

setregid

分别设置真实和有效的的组标识号

setreuid

分别设置真实和有效的用户标识号

getresgid

分别获取真实的,有效的和保存过的组标识号

setresgid

分别设置真实的,有效的和保存过的组标识号

getresuid

分别获取真实的,有效的和保存过的用户标识号

setresuid

分别设置真实的,有效的和保存过的用户标识号

setfsgid

设置文件系统检查时使用的组标识号

setfsuid

设置文件系统检查时使用的用户标识号

getgroups

获取后补组标志清单

setgroups

设置后补组标志清单

 

八、进程间通信

ipc

进程间通信总控制调用

 

1、信号

sigaction

设置对指定信号的处理方法

sigprocmask

根据参数对信号集中的信号执行阻塞/解除阻塞等操作

sigpending

为指定的被阻塞信号设置队列

sigsuspend

挂起进程等待特定信号

signal

参见signal

kill

向进程或进程组发信号

*sigblock

向被阻塞信号掩码中添加信号,已被sigprocmask代替

*siggetmask

取得现有阻塞信号掩码,已被sigprocmask代替

*sigsetmask

用给定信号掩码替换现有阻塞信号掩码,已被sigprocmask代替

*sigmask

将给定的信号转化为掩码,已被sigprocmask代替

*sigpause

作用同sigsuspend,已被sigsuspend代替

sigvec

为兼容BSD而设的信号处理函数,作用类似sigaction

ssetmask

ANSI C的信号处理函数,作用类似sigaction

 

2、消息

msgctl

消息控制操作

msgget

获取消息队列

msgsnd

发消息

msgrcv

取消息

 

3、管道

pipe

创建管道

 

4、信号量

semctl

信号量控制

semget

获取一组信号量

semop

信号量操作

 

5、共享内存

shmctl

控制共享内存

shmget

获取共享内存

shmat

连接共享内存

shmdt

拆卸共享内存

 

 

 

/linux+v2.6.19/arch/arm/kernel/entry-common.S

 114        .align  5

 115ENTRY(vector_swi)

 116        sub     sp, sp, #S_FRAME_SIZE

 117        stmia   sp, {r0 - r12}                  @ Calling r0 - r12

 118        add     r8, sp, #S_PC

 119        stmdb   r8, {sp, lr}^                   @ Calling sp, lr

 120        mrs     r8, spsr                        @ called from non-FIQ mode, so ok.

 121        str     lr, [sp, #S_PC]                 @ Save calling PC

 122        str     r8, [sp, #S_PSR]                @ Save CPSR

 123        str     r0, [sp, #S_OLD_R0]             @ Save OLD_R0

 124        zero_fp

 125

 126        /*

 127         * Get the system call number.

 128         */

 129

 130#if defined(CONFIG_OABI_COMPAT)

 131

 149        /*

 150         * Pure EABI user space always put syscall number into scno (r7).

 151         */

 152  A710( ldr     ip, [lr, #-4]                   @ get SWI instruction   )

 153  A710( and     ip, ip, #0x0f000000             @ check for SWI         )

 154  A710( teq     ip, #0x0f000000                                         )

 155  A710( bne     .Larm710bug                                             )

 156

 157#elif defined(CONFIG_ARM_THUMB)

 158

 159        /* Legacy ABI only, possibly thumb mode. */

 160        tst     r8, #PSR_T_BIT                  @ this is SPSR from save_user_regs

 161        addne   scno, r7, #__NR_SYSCALL_BASE    @ put OS number in

 162        ldreq   scno, [lr, #-4]

 163

 164#else

 165

 166        /* Legacy ABI only. */

 167        ldr     scno, [lr, #-4]                 @ get SWI instruction

 168  A710( and     ip, scno, #0x0f000000           @ check for SWI         )

 169  A710( teq     ip, #0x0f000000                                         )

 170  A710( bne     .Larm710bug                                             )

 171

 172#endif

 173

 179        enable_irq

 180

 181        get_thread_info tsk

 182        adr     tbl, sys_call_table             @ load syscall table pointer

 183        ldr     ip, [tsk, #TI_FLAGS]            @ check for syscall tracing

 184

 185#if defined(CONFIG_OABI_COMPAT)

 186        /*

 187         * If the swi argument is zero, this is an EABI call and we do nothing.

 188         *

 189         * If this is an old ABI call, get the syscall number into scno and

 190         * get the old ABI syscall table address.

 191         */

 192        bics    r10, r10, #0xff000000

 193        eorne   scno, r10, #__NR_OABI_SYSCALL_BASE

 194        ldrne   tbl, =sys_oabi_call_table

 199

 200        stmdb   sp!, {r4, r5}                   @ push fifth and sixth args

 201        tst     ip, #_TIF_SYSCALL_TRACE         @ are we tracing syscalls?

 202        bne     __sys_trace

 203

 204        cmp     scno, #NR_syscalls              @ check upper syscall limit

 205        adr     lr, ret_fast_syscall            @ return address

 206        ldrcc   pc, [tbl, scno, lsl #2]         @ call sys_* routine

 207

 208        add     r1, sp, #S_OFF

 2092:      mov     why, #0                         @ no longer a real syscall

 210        cmp     scno, #(__ARM_NR_BASE - __NR_SYSCALL_BASE)

 211        eor     r0, scno, #__NR_SYSCALL_BASE    @ put OS number back

 212        bcs     arm_syscall    

 213        b       sys_ni_syscall                  @ not private func

 214

 215        /*

 216         * This is the really slow path.  We're going to be doing

 217         * context switches, and waiting for our parent to respond.

 218         */

 219__sys_trace:

 220        mov     r2, scno

 221        add     r1, sp, #S_OFF

 222        mov     r0, #0                          @ trace entry [IP = 0]

 223        bl      syscall_trace

 224

 225        adr     lr, __sys_trace_return          @ return address

 226        mov     scno, r0                        @ syscall number (possibly new)

 227        add     r1, sp, #S_R0 + S_OFF           @ pointer to regs

 228        cmp     scno, #NR_syscalls              @ check upper syscall limit

 229        ldmccia r1, {r0 - r3}                   @ have to reload r0 - r3

 230        ldrcc   pc, [tbl, scno, lsl #2]         @ call sys_* routine

 231        b       2b

 232

 233__sys_trace_return:

 234        str     r0, [sp, #S_R0 + S_OFF]!        @ save returned r0

 235        mov     r2, scno

 236        mov     r1, sp

 237        mov     r0, #1                          @ trace exit [IP = 1]

 238        bl      syscall_trace

 239        b       ret_slow_syscall

 240

 247        .ltorg

 248

 249/*

 250 * This is the syscall table declaration for native ABI syscalls.

 251 * With EABI a couple syscalls are obsolete and defined as sys_ni_syscall.

 252 */

 253#define ABI(native, compat) native

 254#ifdef CONFIG_AEABI

 255#define OBSOLETE(syscall) sys_ni_syscall

 256#else

 257#define OBSOLETE(syscall) syscall

 258#endif

 259

 260        .type   sys_call_table, #object

 261ENTRY(sys_call_table)

 262#include "calls.S"

 263#undef ABI

 264#undef OBSOLETE

 265

 266/*============================================================================

 267 * Special system call wrappers

 268 */

 269@ r0 = syscall number

 270@ r8 = syscall table

 271                .type   sys_syscall, #function

 272sys_syscall:

 273                bic     scno, r0, #__NR_OABI_SYSCALL_BASE

 274                cmp     scno, #__NR_syscall - __NR_SYSCALL_BASE

 275                cmpne   scno, #NR_syscalls      @ check range

 276                stmloia sp, {r5, r6}            @ shuffle args

 277                movlo   r0, r1

 278                movlo   r1, r2

 279                movlo   r2, r3

 280                movlo   r3, r4

 281                ldrlo   pc, [tbl, scno, lsl #2]

 282                b       sys_ni_syscall

 283

 284sys_fork_wrapper:

 285                add     r0, sp, #S_OFF

 286                b       sys_fork

 287

。。。。。

 

/linux+v2.6.19/arch/arm/kernel/entry-armv.S

1058.LCvswi:

1059        .word   vector_swi

1060

1061        .globl  __stubs_end

1062__stubs_end:

1063

1064        .equ    stubs_offset, __vectors_start + 0x200 - __stubs_start

1065

1066        .globl  __vectors_start

1067__vectors_start:

1068        swi     SYS_ERROR0

1069        b       vector_und + stubs_offset

1070        ldr     pc, .LCvswi + stubs_offset

1071        b       vector_pabt + stubs_offset

1072        b       vector_dabt + stubs_offset

1073        b       vector_addrexcptn + stubs_offset

1074        b       vector_irq + stubs_offset

1075        b       vector_fiq + stubs_offset

1076

1077        .globl  __vectors_end

1078__vectors_end:

 

你可能感兴趣的:(【嵌入式Linux学习七步曲之第五篇 Linux内核及驱动编程】Linux系统调用的实现机制分析)