标准库是如何发起系统调用的

来源:互联网 发布:手机windows主题下载 编辑:程序博客网 时间:2024/05/18 02:55

应用程序在发起某个系统调用时,是先调用标准库中的同名函数,标准库再根据不同体系结构来选择特定的方式陷入内核。

以socket库函数为例(以下均为uClibc中的实现),来说明标准库是如何一步步解析系统调用名并进入内核的:

socket系统调用号:

#define __NR_socket (4000 + 183)

socket()函数的实现就是下面的函数:

_syscall3(int, socket, int, family, int, type, int, protocol)

这个函数调用下面的宏:

SYSCALL_FUNC(3, int, socket, int, family, int, type, int, protocol)

宏展开就成了:

int socket(C_DECL_ARGS_3(int, family, int, type, int, protocol)) {                  \    return (type)INLINE_SYSCALL(socket, 3, C_ARGS_3(int, family, int, type, int, protocol));    \}

这里会把类型和参数名之间的逗号去掉,参数名增加两个:系统调用名字和参数个数,于是进一步展开得到:

int socket(int family, int type, int protocol) {                    \    return (int)INLINE_SYSCALL(socket, 3, family, type, protocol);  \}

INLINE_SYSCALL的定义仍是一个宏:
INLINE_SYSCALL_NCS(__NR_socket, 3, family, type, protocol)
宏展开,系统调用名改为系统调用号,增加一个err参数:

INTERNAL_SYSCALL_NCS(__NR_socket, err, 3, family, type, protocol)

根据参数个数3找到internal_syscall3,其定义如下,前三个参数用在内嵌汇编里所以看起来有点奇怪,实际上就是直接替换:

internal_syscall3( = __NR_socket, ,"r" (__v0), err, family, type, protocol)!({                                  \    long _sys_result;                       \                                    \    {                               \    register ARG_TYPE __v0 __asm__("$2") = __NR_socket;            \    register ARG_TYPE __a0 __asm__("$4") = (ARG_TYPE) arg1;        \    register ARG_TYPE __a1 __asm__("$5") = (ARG_TYPE) arg2;        \    register ARG_TYPE __a2 __asm__("$6") = (ARG_TYPE) arg3;        \    register ARG_TYPE __a3 __asm__("$7");              \    __asm__ __volatile__ (                      \    ".set\tnoreorder\n\t"                                               \    "syscall\n\t"                           \    ".set\treorder"                     \    : "=r" (__v0), "=r" (__a3)                  \    : input, "r" (__a0), "r" (__a1), "r" (__a2)         \    : __SYSCALL_CLOBBERS);                      \    err = __a3;                         \    _sys_result = __v0;                     \    }                               \    _sys_result;                            \})

上面的过程就是保存参数,传入系统调用号,执行syscall指令,进入内核态,并准备接收两个返回值:错误码和结果。这里陷入内核的过程是体系结构相关的,上述为MIPS的做法,而ARM中是通过swi指令陷入软中断来完成的。

进入到内核之后的工作以及如何返回到用户态,可以参考MIPS中的系统调用这篇文章中的介绍。

如果在标准库中没有定义某个系统调用的同名函数,可以直接通过syscall(),例如获取线程tid的系统调用gettid,其系统调用号为224,但uClibc中并没有定义gettid()库函数,则可以通过syscall(224)使用该系统调用。而对于用户自定义的系统调用(当然不建议这样做),也只能用syscall()。

阅读全文
0 0