《Linux内核设计与实现》系统调用

C库、API和系统调用

一般情况下,应用程序通过用户空间实现的应用编程接口(API)而不是直接通过系统调用来编程。

LInux系统调用像大多数Unix系统一样,作为C库的一部分提供。

C库实现了Unix系统的主要API,包括标准C库函数和系统调用接口。

Unix中最流行的API是基于POSIX标准的。

示例与说明

getpid()系统调用:

SYSCALL_DEFINE0(getpid)
{
    return task_tgid_vnr(current);
}

SYSCALL_DEFINE0是一个宏,定义了一个无参数的系统调用,它是一系列宏中的一个:

#define SYSCALL_DEFINE0(name)      asmlinkage long sys_##name(void)
#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)

展开后的定义其实是这样的:

asmlinkage long sys_getpid(void)

这里:

asmlinkage是编译指令,用于通知编译器仅从栈中提取该函数的参数

long是为了保证兼容性,在用户空间返回的是int,在内核空间为long。

sys_xx是Linux中所有系统调用都应该遵守的命名规则。

系统调用列表

Linux有一张系统调用表记录所有已注册过的系统调用,并为每一个系统调用分配一个系统调用号来关联系统调用。

比如(arch\x86\kernel\syscall_table_32.S):

ENTRY(sys_call_table)
	.long sys_restart_syscall	/* 0 - old "setup()" system call, used for restarting */
	.long sys_exit
	.long ptregs_fork
	.long sys_read
	.long sys_write
	.long sys_open		/* 5 */
	.long sys_close
	.long sys_waitpid
	.long sys_creat
	.long sys_link
	.long sys_unlink	/* 10 */
	.long ptregs_execve
	.long sys_chdir
	.long sys_time
// 后面略

如果有些系统调用,没有实现,对应的位置(0开始,就是系统调用号)也不会被覆盖,还是用sys_ni_syscall()来替代,表示一个没有实现的系统调用,它只会返回-ENOSYS:

/*
 * Non-implemented system calls get redirected here.
 */
asmlinkage long sys_ni_syscall(void)
{
    return -ENOSYS;
}

触发系统调用

软中断:通过引发一个异常来促使系统切换到内核去执行异常处理程序,而这个异常处理程序就是系统调用处理程序(对应中断号128,system_call()),下面是x86_64平台的部分实现代码:

arch\x86\kernel\entry_64.S:
ENTRY(system_call)
    CFI_STARTPROC   simple
    CFI_SIGNAL_FRAME
    CFI_DEF_CFA rsp,KERNEL_STACK_OFFSET
    CFI_REGISTER    rip,rcx
    /*CFI_REGISTER  rflags,r11*/
    SWAPGS_UNSAFE_STACK
    /*
     * A hypervisor implementation might want to use a label
     * after the swapgs, so that it can do the swapgs
     * for the guest and jump here on syscall.
     */
ENTRY(system_call_after_swapgs)
    movq    %rsp,PER_CPU_VAR(old_rsp)
    movq    PER_CPU_VAR(kernel_stack),%rsp
    /*
     * No need to follow this irqs off/on section - it's straight
     * and short:
     */
    ENABLE_INTERRUPTS(CLBR_NONE)
    SAVE_ARGS 8,1
    movq  %rax,ORIG_RAX-ARGOFFSET(%rsp)
    movq  %rcx,RIP-ARGOFFSET(%rsp)
    CFI_REL_OFFSET rip,RIP-ARGOFFSET
    GET_THREAD_INFO(%rcx)
    testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%rcx)
    jnz tracesys
system_call_fastpath:
    cmpq $__NR_syscall_max,%rax
    ja badsys
    movq %r10,%rcx
    call *sys_call_table(,%rax,8)  # XXX:    rip relative
    movq %rax,RAX-ARGOFFSET(%rsp)

另一种触发系统调用的是指令:sysenter

系统调用号也需要传递给内核,在x86上通过eax寄存器来传递。

除了系统调用号,可能还需要额外的外部参数。

关于外部参数,不同平台应该不同,且书中的说法也跟代码中的有差别,也不必特别关注。

系统调用上下文

内核在执行系统调用的时候处于进程上下文

current指针指向当前任务,即引发系统调用的那个进程。

内核在执行系统调用的时候也可以休眠(相对应的,中断处理程序不能休眠),因此需要保证系统调用是可重入的。

避免实现新的系统调用

如果仅仅进行简单的信息交换,可以使用替代方法:实现设备节点,并对此实现read()和write()。使用ioctl()对特定的设置进行操作或者对特定的信息进行检索。

像信号量这样的接口,可以用文件描述符来表示。

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值