Linux内核学习笔记 -30 Linux系统调用机制

系统调用实际上是内核的出口,系统调用顾名思义就是操作系统提供给用户程序调用的一组特殊接口,从逻辑上来说,系统调用可以被看成是一个内核与用户空间程序交互的接口,它好比一个中间人,把用户空间的请求传达给内核,在内核把请求处理完以后,再将处理的结果返回给用户。上图就可以看出,open系统调用跟内核交互的过程

如何知道一个进程到底调用了哪些系统调用?这里给出一张图,这张图是linux系统中各个子系统相关的工具,左上角是strace工具,可以通过这个命令,我们就可以查看一个应用它所调用的所有的系统调用。比如我们要查看ls命令到底调用了哪些系统调用?可以用strace ls,去查看ls所调用的系统调用。

book@100ask:~$ strace ls
execve("/bin/ls", ["ls"], 0x7fffad1a34a0 /* 21 vars */) = 0
brk(NULL)                               = 0x55bafdec0000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=87984, ...}) = 0
mmap(NULL, 87984, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f67ce992000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20b\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0644, st_size=154832, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f67ce990000
mmap(NULL, 2259152, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f67ce55a000
mprotect(0x7f67ce57f000, 2093056, PROT_NONE) = 0
mmap(0x7f67ce77e000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x24000) = 0x7f67ce77e000
mmap(0x7f67ce780000, 6352, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f67ce780000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260\34\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2030544, ...}) = 0
mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f67ce162000
mprotect(0x7f67ce349000, 2097152, PROT_NONE) = 0
mmap(0x7f67ce549000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f67ce549000
mmap(0x7f67ce54f000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f67ce54f000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libpcre.so.3", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0 \25\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0644, st_size=464824, ...}) = 0
mmap(NULL, 2560264, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f67cdeea000
mprotect(0x7f67cdf5a000, 2097152, PROT_NONE) = 0
mmap(0x7f67ce15a000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x70000) = 0x7f67ce15a000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\16\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0644, st_size=14560, ...}) = 0
mmap(NULL, 2109712, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f67cdce2000
mprotect(0x7f67cdce5000, 2093056, PROT_NONE) = 0
mmap(0x7f67cdee4000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2000) = 0x7f67cdee4000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0000b\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=144976, ...}) = 0
mmap(NULL, 2221184, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f67cdac2000
mprotect(0x7f67cdadc000, 2093056, PROT_NONE) = 0
mmap(0x7f67cdcdb000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19000) = 0x7f67cdcdb000
mmap(0x7f67cdcdd000, 13440, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f67cdcdd000
close(3)                                = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f67ce98e000
arch_prctl(ARCH_SET_FS, 0x7f67ce98f040) = 0
mprotect(0x7f67ce549000, 16384, PROT_READ) = 0
mprotect(0x7f67cdcdb000, 4096, PROT_READ) = 0
mprotect(0x7f67cdee4000, 4096, PROT_READ) = 0
mprotect(0x7f67ce15a000, 4096, PROT_READ) = 0
mprotect(0x7f67ce77e000, 4096, PROT_READ) = 0
mprotect(0x55bafd7c1000, 8192, PROT_READ) = 0
mprotect(0x7f67ce9a9000, 4096, PROT_READ) = 0
munmap(0x7f67ce992000, 87984)           = 0
set_tid_address(0x7f67ce98f310)         = 30111
set_robust_list(0x7f67ce98f320, 24)     = 0
rt_sigaction(SIGRTMIN, {sa_handler=0x7f67cdac7cb0, sa_mask=[], sa_flags=SA_RESTORER|SA_SIGINFO, sa_restorer=0x7f67cdad48a0}, NULL, 8) = 0
rt_sigaction(SIGRT_1, {sa_handler=0x7f67cdac7d50, sa_mask=[], sa_flags=SA_RESTORER|SA_RESTART|SA_SIGINFO, sa_restorer=0x7f67cdad48a0}, NULL, 8) = 0
rt_sigprocmask(SIG_UNBLOCK, [RTMIN RT_1], NULL, 8) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
statfs("/sys/fs/selinux", 0x7ffcdc308490) = -1 ENOENT (No such file or directory)
statfs("/selinux", 0x7ffcdc308490)      = -1 ENOENT (No such file or directory)
brk(NULL)                               = 0x55bafdec0000
brk(0x55bafdee1000)                     = 0x55bafdee1000
openat(AT_FDCWD, "/proc/filesystems", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
read(3, "nodev\tsysfs\nnodev\ttmpfs\nnodev\tbd"..., 1024) = 450
read(3, "", 1024)                       = 0
close(3)                                = 0
access("/etc/selinux/config", F_OK)     = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=3004224, ...}) = 0
mmap(NULL, 3004224, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f67cd7e2000
close(3)                                = 0
ioctl(1, TCGETS, {B38400 opost isig icanon echo ...}) = 0
ioctl(1, TIOCGWINSZ, {ws_row=24, ws_col=79, ws_xpixel=0, ws_ypixel=0}) = 0
openat(AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
fstat(3, {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
getdents(3, /* 26 entries */, 32768)    = 816
getdents(3, /* 0 entries */, 32768)     = 0
close(3)                                = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
write(1, "C_coding  Documents  EDK2\t      "..., 64C_coding  Documents  EDK2         Mooc   Pictures   snap       Videos
) = 64
write(1, "Desktop   Downloads  examples.de"..., 63Desktop   Downloads  examples.desktop  Music  Public Templates
) = 63
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

前面介绍了中断和异常,中断/异常/系统调用它们之间到底是什么关系?实际上这三者本质上属于一类,那么处理方式也比较类型。它们之间的差异表现在哪些方面?在学了中断以后,对我们学系统调用有哪些帮助呢?我们从三个方面来看它们的差异性。

1- 源头不同:源头是不同的,中断是外设发出的请求;而异常是应用程序意想不到的行为产生的一种错误;系统调用是应用程序请求OS提供的服务。

2- 响应方式不同:中断是异步的,而异常是同步的,系统调用既可以是同步,也可以是异步。

3- 处理机制不同:中断服务程序整个是在内核态下运行的,对用户完全透明;异常出现时,实际上马上就要执行异常处理程序,此时或者杀死进程,或者重新执行引起异常的指令;而系统调用是用户发出请求以后,就在那里等待OS提供服务

通过一根例子来看一下从用户态函数到系统调用这样一个路径

比如,在程序中调用了一个fwrite这个函数,这个函数实际上就是libc库中调用系统调用write,然后从用户态陷入到内核态,查找系统调用表,对应的系统服务例程是sys_write

系统调用的一般处理过程是:当用户态的进程调用一个系统调用时,它在libc的封装例程中实际上会调用int的0x80,或者syscall的汇编指令就切换到了内核态,而且就开始执行一根内核的syscall系统调用处理程序,这里关键的就是系统调用处理程序。它做什么工作呢?

首先它在内核栈,包含大多数寄存器的内容,也就是压栈操作;然后调用系统调用服务例程处理系统调用;最后通过中断的返回指令从系统调用返回

 

系统调用号:用于唯一标识每个系统调用。它作为系统调用表的下标,当用户空间的进程执行系统调用的时候,该系统调用号就被用来指明到底执行哪个系统调用服务例程

系统调用表:用来把系统调用号和相应的服务例程关联起来,该表存放在sys_call_table数组中,特别说明,内核版本不同,这些系统调用号,系统调用所在的头文件也会有所差别。

上表给出了部分系统调用,它的系统调用号,在内核的服务例程以及所在的头文件和参数

从表中可以看出,系统调用号存放在eax寄存器中,每个系统调用在内核中对应的服务例程都是以sys开头的,它们的实现所在的源文件也各不相同。系统调用表的参数存放在寄存器中,一般参数不超过6个,包括系统调用号。

从上表可以看出,如何从用户态跟踪一个系统调用到内核

第一步,我们在用户程序中调用fork系统调用,

第二步,在libc库中,就把fork对应的系统调用号放入寄存器eax中

第三步,通过int 0x80就陷入到内核中

第四步,在中断描述符表IDT中查找到系统调用的入口0x80,

第五版,进入linux内核的int_32(64).s文件中,从系统调用表sys_table中就找到了fork的入口地址,然后执行fork.c中的do_fork代码,这个时候就真正的进入到内核来执行这个系统调用了。

当这个系统调用执行完之后,就通过ret指令返回用户态

前面对系统调用机制做了简单介绍,实际上如果要去看源代码的话,那么它的整个过程是比较复杂的。此处对系统调用机制如何优化做简要的概述

在2.6以前的版本中,系统调用的实现是用int 0x86或ret指令,因为系统调用的实现是从用户态切换到内核态,执行完系统调用后又从内核态切换回用户态,代价很大,为了加快系统调用的速度,先后引入了两种机制,vsycalls和vDSO。这两种机制都是从机制上对系统调用的速度进行了优化,但是使用软中断来进行系统调用,还是需要进行特权级的切换,这一根本问题并没有得到解决

为了解决“特权级的切换"这样根本问题,intel x86 CPU从Pentium II以后,开始支持快速系统调用指令sysenter/sysexit,这两条指令是intel在32位下提出的,而AMD提出syscall/sysret,64位统一使用这两条指令。

前面对系统调用的机制做了介绍之后,那么系统调用到底如何应用起来?我们给一个实例,系统日志收集系统。

系统调用是用户程序与系统打交道的入口,系统调用的安全直接关系到系统的安全,如果一个用户恶意不断地调用fork,就会导致系统负载增加,所以,如果能收集到谁调用了一些有危险的系统调用,以及系统调用的时间和其它信息的话,将有助于管理员进行事后的追踪,从而提高系统的安全性。本实例的实现将在下节介绍,并且阐明如何添加一个系统调用。

通过前面的介绍,我们对本章做一个简要的总结。

系统调用实际上是应用与内核之间的一个接口,linux内核中系统调用的具体实现是与体系结构相关的,因此x86下和arm下系统调用的源代码,它实际上是不同的。对不同的体系架构,要有针对性的去看相关代码,这里只是给出了一般性的原理。

下一讲介绍如何添加系统调用,实际系统中,是否需要添加,需要仔细评估

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值