飞道的博客

Linux(内核剖析):13---系统调用的实现与解析

378人阅读  评论(0)

一、系统调用概述

  • 系统调用在用户空间进程和硬件设备之间添加了一个中间层
  • 该层主要作用有三个:
    • 第一, 为用户空间提供了一种硬件的抽象接口。举例来说,当需要读写文件的时候,应用程序就可以不去管磁盘类型和介质,甚至不用去管文件所在的文件系统到底是哪种类型
    • 第二,系统调用保证了系统的稳定和安全。作为硬件设备和应用程序之间的中间人,内核可以基于权限、用户类型和其他一些规则对需要进行的访问进行裁决。举例来说,这样可以避免应用程序不正确地使用硬件设备,窃取其他进程的资源,或做出其他危害系统的事情
    • 第三,在前面介绍进程的文章中曾说过,每个进程都运行在虚拟系统中,而在用户空间和系统的其余部分提供这样一层公共接口,也是出于这种考虑。如果应用程序可以随意访问硬件而内核又对此一无所知的话,几乎就没法实现多任务和虚拟内存,当然也不可能实现良好的稳定性和安全性
  • 在Linux中,系统调用是用户空间访问内核的唯一手段;除异常和陷入外,它们是内核唯一的合法入口。实际上,其他的像设备文件和/proc之类的方式,最终也还是要通过系统调用进行访问的。而有趣的是,Linux提供的系统调用却比大部分操作系统都少得多

二、API、POSIX和C库

  • 一般情况下,应用程序通过在用户空间实现的应用编程接口(API)而不是直接通过系统调用来编程。下图给出了POSIX、API、C库以及系统调用之间的关系

POSIX

  • 在Unix中,最流行的应用编程接口是基于POSIX标准的。从纯技术的角度看,POSIX是由IEEE的一组标准组成,其目标是提供一套大体上基于Unix的可移植操作系统标准。在应用场合,Linux尽力与POSIX和SUSv3兼容
  • POSIX是说明API和系统调用之间关系的一个极好例子。在大多数Unix系统上,根据POSIX定义的API函数和系统调用之间有着直接关系。实际上,POSIX标准就是仿照早期Unix系统的接口建立的。另一方面,许多操作系统,像微软的Windows,尽管是非Unix系统,也提供了与POS1X 兼容的库

C库

  • Linux的系统调用像大多数Unix系统一样,作为C库的一部分提供。C库实现了Unix系统的主要API,包括标准C库函数和系统调用接口。所有的C程序都可以使用C库,而由于C 语言本身的特点,其他语言也吋以很方便地把它们封装起来使用。此外,C 库提供了 PO SIX的绝大部分API
  • 关于Unix的接口设计有一句格言“提供机制而不是策略”。换句话说,Unix的系统调用抽象出了用于完成某种确定的目的的函数。至于这些函数怎么用完全不需要内核去关心

三、系统调用的一些性质

  • 访问系统调用(Linux中常称作syscall),通常通过C 库中定义的函数调用来进行
  • 它们通常都需要定义零个、一个或几个参数(输 入)而且可能产生一些副作用,例如,写某个文件或向给定的指针拷贝数据等
  • 系统调用还会通过一个long类型的返回值来表示成功或者错误
    • 通常,但也不绝对,用一个负的返回值来表明错误。
    • 返回一个0值通 常 (当然仍不是绝对的)表明成功
    • 系统调用在出现错误的时候C 库会把错误码写入errno全局变量。通过调用perror()库函数,可以把该变量翻译成用户可以理解的错误字符串
  • 系统调用的性能:
    • Linux系统调用比其他许多操作系统执行得要快。Linux很短的上下文切换时间是一个重要原因,进出内核都被优化得简洁高效。另外一个原因是系统调用处理程序和每个系统调用本身也都非常简洁

系统调用的一般格式

  • 例如getpid()系统调用在内核中实现的形式如下:

   
  1. SYSCALL_DEFINE0(getpid)
  2. {
  3. return task_tgid_vnr(current); // returns current->tgid
  4. }
  • SYSCALL_DEFINE0只是一个宏,它定义了一个无参数的系统调用(因此这里为数字0,展开后的代码如下所示)
asmlinkage long sys_getpid(void)
  • asmlinkage:这是一个编译指令,通知编译器仅从栈中提取该函数的参数。所有的系统调用都需要这个限定词
  • long:函数的返回值。为了保证32位和64位系统的兼容,系统调用在用户空间和内核空间有不同的返回值类型,在用户空间为int,在内核空间为long
  • 所有的系统调用都以sys_开头

系统调用号

  • 概念:
    • 在Linux中,每个系统调用被赋予一个系统调用号。这样,通过这个独一无二的号就可以关联系统调用
    • 当用户空间的进程执行一个系统调用的时候,这个系统调用号就用来指明到底是要执行哪个系统调用;进程不会提及系统调用的名称
  • 特点:
    • 1.一旦分配就不能再有任何变更,否则编译好的应用程序就会崩溃
    • 2.如果一个系统调用被删除,它所占用的系统调用号也不允许被回收利用,如果将这个系统调用号回收给新的系统调用利用,则以前编译好的代码会调用这个新的系统调用,但事实上两个系统调用是不一致的
  • sys_ni_syscall()系统调用:
    • Linux有一个“未实现”系统调用sys_ni_syscall(),它除了返回-ENOSYS外不作任何其他工作,这个错误号就是专门针对无效的系统调用而设的
    • 常用的场景:如果一个系统调用被删除,或者变得不可用,就用这个函数“填补空缺”

sys_call_table(系统调用表)

  • 内核记录了系统调用表中的所有已注册过的系统调用的列表,存储在sys_call_table中
  • 每一种体系结构中,都明确定义了这个表,在x86-64中,它定义于arch/i386/kernel/syscall_64.c文件中
  • 这个表为每一个有效的系统调用指定了唯一的系统调用号
  • 以下代码来自Linux 2.6.22/arch/i386/kernel/syscall_table.S中

   
  1. ENTRY(sys_call_table)
  2. . long sys_restart_syscall /* 0 - old "setup()" system call, used for restarting */
  3. . long sys_exit
  4. . long sys_fork
  5. . long sys_read
  6. . long sys_write
  7. . long sys_open /* 5 */
  8. . long sys_close
  9. . long sys_waitpid
  10. . long sys_creat
  11. . long sys_link
  12. . long sys_unlink /* 10 */
  13. . long sys_execve
  14. . long sys_chdir
  15. . long sys_time
  16. . long sys_mknod
  17. . long sys_chmod /* 15 */
  18. . long sys_lchown16
  19. . long sys_ni_syscall /* old break syscall holder */
  20. . long sys_stat
  21. . long sys_lseek
  22. . long sys_getpid /* 20 */
  23. . long sys_mount
  24. . long sys_oldumount
  25. . long sys_setuid16
  26. . long sys_getuid16
  27. . long sys_stime /* 25 */
  28. . long sys_ptrace
  29. . long sys_alarm
  30. . long sys_fstat
  31. . long sys_pause
  32. . long sys_utime /* 30 */
  33. . long sys_ni_syscall /* old stty syscall holder */
  34. . long sys_ni_syscall /* old gtty syscall holder */
  35. . long sys_access
  36. . long sys_nice
  37. . long sys_ni_syscall /* 35 - old ftime syscall holder */
  38. . long sys_sync
  39. . long sys_kill
  40. . long sys_rename
  41. . long sys_mkdir
  42. . long sys_rmdir /* 40 */
  43. . long sys_dup
  44. . long sys_pipe
  45. . long sys_times
  46. . long sys_ni_syscall /* old prof syscall holder */
  47. . long sys_brk /* 45 */
  48. . long sys_setgid16
  49. . long sys_getgid16
  50. . long sys_signal
  51. . long sys_geteuid16
  52. . long sys_getegid16 /* 50 */
  53. . long sys_acct
  54. . long sys_umount /* recycled never used phys() */
  55. . long sys_ni_syscall /* old lock syscall holder */
  56. . long sys_ioctl
  57. . long sys_fcntl /* 55 */
  58. . long sys_ni_syscall /* old mpx syscall holder */
  59. . long sys_setpgid
  60. . long sys_ni_syscall /* old ulimit syscall holder */
  61. . long sys_olduname
  62. . long sys_umask /* 60 */
  63. . long sys_chroot
  64. . long sys_ustat
  65. . long sys_dup2
  66. . long sys_getppid
  67. . long sys_getpgrp /* 65 */
  68. . long sys_setsid
  69. . long sys_sigaction
  70. . long sys_sgetmask
  71. . long sys_ssetmask
  72. . long sys_setreuid16 /* 70 */
  73. . long sys_setregid16
  74. . long sys_sigsuspend
  75. . long sys_sigpending
  76. . long sys_sethostname
  77. . long sys_setrlimit /* 75 */
  78. . long sys_old_getrlimit
  79. . long sys_getrusage
  80. . long sys_gettimeofday
  81. . long sys_settimeofday
  82. . long sys_getgroups16 /* 80 */
  83. . long sys_setgroups16
  84. . long old_select
  85. . long sys_symlink
  86. . long sys_lstat
  87. . long sys_readlink /* 85 */
  88. . long sys_uselib
  89. . long sys_swapon
  90. . long sys_reboot
  91. . long old_readdir
  92. . long old_mmap /* 90 */
  93. . long sys_munmap
  94. . long sys_truncate
  95. . long sys_ftruncate
  96. . long sys_fchmod
  97. . long sys_fchown16 /* 95 */
  98. . long sys_getpriority
  99. . long sys_setpriority
  100. . long sys_ni_syscall /* old profil syscall holder */
  101. . long sys_statfs
  102. . long sys_fstatfs /* 100 */
  103. . long sys_ioperm
  104. . long sys_socketcall
  105. . long sys_syslog
  106. . long sys_setitimer
  107. . long sys_getitimer /* 105 */
  108. . long sys_newstat
  109. . long sys_newlstat
  110. . long sys_newfstat
  111. . long sys_uname
  112. . long sys_iopl /* 110 */
  113. . long sys_vhangup
  114. . long sys_ni_syscall /* old "idle" system call */
  115. . long sys_vm86old
  116. . long sys_wait4
  117. . long sys_swapoff /* 115 */
  118. . long sys_sysinfo
  119. . long sys_ipc
  120. . long sys_fsync
  121. . long sys_sigreturn
  122. . long sys_clone /* 120 */
  123. . long sys_setdomainname
  124. . long sys_newuname
  125. . long sys_modify_ldt
  126. . long sys_adjtimex
  127. . long sys_mprotect /* 125 */
  128. . long sys_sigprocmask
  129. . long sys_ni_syscall /* old "create_module" */
  130. . long sys_init_module
  131. . long sys_delete_module
  132. . long sys_ni_syscall /* 130: old "get_kernel_syms" */
  133. . long sys_quotactl
  134. . long sys_getpgid
  135. . long sys_fchdir
  136. . long sys_bdflush
  137. . long sys_sysfs /* 135 */
  138. . long sys_personality
  139. . long sys_ni_syscall /* reserved for afs_syscall */
  140. . long sys_setfsuid16
  141. . long sys_setfsgid16
  142. . long sys_llseek /* 140 */
  143. . long sys_getdents
  144. . long sys_select
  145. . long sys_flock
  146. . long sys_msync
  147. . long sys_readv /* 145 */
  148. . long sys_writev
  149. . long sys_getsid
  150. . long sys_fdatasync
  151. . long sys_sysctl
  152. . long sys_mlock /* 150 */
  153. . long sys_munlock
  154. . long sys_mlockall
  155. . long sys_munlockall
  156. . long sys_sched_setparam
  157. . long sys_sched_getparam /* 155 */
  158. . long sys_sched_setscheduler
  159. . long sys_sched_getscheduler
  160. . long sys_sched_yield
  161. . long sys_sched_get_priority_max
  162. . long sys_sched_get_priority_min /* 160 */
  163. . long sys_sched_rr_get_interval
  164. . long sys_nanosleep
  165. . long sys_mremap
  166. . long sys_setresuid16
  167. . long sys_getresuid16 /* 165 */
  168. . long sys_vm86
  169. . long sys_ni_syscall /* Old sys_query_module */
  170. . long sys_poll
  171. . long sys_nfsservctl
  172. . long sys_setresgid16 /* 170 */
  173. . long sys_getresgid16
  174. . long sys_prctl
  175. . long sys_rt_sigreturn
  176. . long sys_rt_sigaction
  177. . long sys_rt_sigprocmask /* 175 */
  178. . long sys_rt_sigpending
  179. . long sys_rt_sigtimedwait
  180. . long sys_rt_sigqueueinfo
  181. . long sys_rt_sigsuspend
  182. . long sys_pread64 /* 180 */
  183. . long sys_pwrite64
  184. . long sys_chown16
  185. . long sys_getcwd
  186. . long sys_capget
  187. . long sys_capset /* 185 */
  188. . long sys_sigaltstack
  189. . long sys_sendfile
  190. . long sys_ni_syscall /* reserved for streams1 */
  191. . long sys_ni_syscall /* reserved for streams2 */
  192. . long sys_vfork /* 190 */
  193. . long sys_getrlimit
  194. . long sys_mmap2
  195. . long sys_truncate64
  196. . long sys_ftruncate64
  197. . long sys_stat64 /* 195 */
  198. . long sys_lstat64
  199. . long sys_fstat64
  200. . long sys_lchown
  201. . long sys_getuid
  202. . long sys_getgid /* 200 */
  203. . long sys_geteuid
  204. . long sys_getegid
  205. . long sys_setreuid
  206. . long sys_setregid
  207. . long sys_getgroups /* 205 */
  208. . long sys_setgroups
  209. . long sys_fchown
  210. . long sys_setresuid
  211. . long sys_getresuid
  212. . long sys_setresgid /* 210 */
  213. . long sys_getresgid
  214. . long sys_chown
  215. . long sys_setuid
  216. . long sys_setgid
  217. . long sys_setfsuid /* 215 */
  218. . long sys_setfsgid
  219. . long sys_pivot_root
  220. . long sys_mincore
  221. . long sys_madvise
  222. . long sys_getdents64 /* 220 */
  223. . long sys_fcntl64
  224. . long sys_ni_syscall /* reserved for TUX */
  225. . long sys_ni_syscall
  226. . long sys_gettid
  227. . long sys_readahead /* 225 */
  228. . long sys_setxattr
  229. . long sys_lsetxattr
  230. . long sys_fsetxattr
  231. . long sys_getxattr
  232. . long sys_lgetxattr /* 230 */
  233. . long sys_fgetxattr
  234. . long sys_listxattr
  235. . long sys_llistxattr
  236. . long sys_flistxattr
  237. . long sys_removexattr /* 235 */
  238. . long sys_lremovexattr
  239. . long sys_fremovexattr
  240. . long sys_tkill
  241. . long sys_sendfile64
  242. . long sys_futex /* 240 */
  243. . long sys_sched_setaffinity
  244. . long sys_sched_getaffinity
  245. . long sys_set_thread_area
  246. . long sys_get_thread_area
  247. . long sys_io_setup /* 245 */
  248. . long sys_io_destroy
  249. . long sys_io_getevents
  250. . long sys_io_submit
  251. . long sys_io_cancel
  252. . long sys_fadvise64 /* 250 */
  253. . long sys_ni_syscall
  254. . long sys_exit_group
  255. . long sys_lookup_dcookie
  256. . long sys_epoll_create
  257. . long sys_epoll_ctl /* 255 */
  258. . long sys_epoll_wait
  259. . long sys_remap_file_pages
  260. . long sys_set_tid_address
  261. . long sys_timer_create
  262. . long sys_timer_settime /* 260 */
  263. . long sys_timer_gettime
  264. . long sys_timer_getoverrun
  265. . long sys_timer_delete
  266. . long sys_clock_settime
  267. . long sys_clock_gettime /* 265 */
  268. . long sys_clock_getres
  269. . long sys_clock_nanosleep
  270. . long sys_statfs64
  271. . long sys_fstatfs64
  272. . long sys_tgkill /* 270 */
  273. . long sys_utimes
  274. . long sys_fadvise64_64
  275. . long sys_ni_syscall /* sys_vserver */
  276. . long sys_mbind
  277. . long sys_get_mempolicy
  278. . long sys_set_mempolicy
  279. . long sys_mq_open
  280. . long sys_mq_unlink
  281. . long sys_mq_timedsend
  282. . long sys_mq_timedreceive /* 280 */
  283. . long sys_mq_notify
  284. . long sys_mq_getsetattr
  285. . long sys_kexec_load
  286. . long sys_waitid
  287. . long sys_ni_syscall /* 285 */ /* available */
  288. . long sys_add_key
  289. . long sys_request_key
  290. . long sys_keyctl
  291. . long sys_ioprio_set
  292. . long sys_ioprio_get /* 290 */
  293. . long sys_inotify_init
  294. . long sys_inotify_add_watch
  295. . long sys_inotify_rm_watch
  296. . long sys_migrate_pages
  297. . long sys_openat /* 295 */
  298. . long sys_mkdirat
  299. . long sys_mknodat
  300. . long sys_fchownat
  301. . long sys_futimesat
  302. . long sys_fstatat64 /* 300 */
  303. . long sys_unlinkat
  304. . long sys_renameat
  305. . long sys_linkat
  306. . long sys_symlinkat
  307. . long sys_readlinkat /* 305 */
  308. . long sys_fchmodat
  309. . long sys_faccessat
  310. . long sys_pselect6
  311. . long sys_ppoll
  312. . long sys_unshare /* 310 */
  313. . long sys_set_robust_list
  314. . long sys_get_robust_list
  315. . long sys_splice
  316. . long sys_sync_file_range
  317. . long sys_tee /* 315 */
  318. . long sys_vmsplice
  319. . long sys_move_pages
  320. . long sys_getcpu
  321. . long sys_epoll_pwait
  322. . long sys_utimensat /* 320 */
  323. . long sys_signalfd
  324. . long sys_timerfd
  325. . long sys_eventfd

四、系统调用处理程序

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

软中断机制(int $0x80指令、system_call())

  • 通知内核的机制是靠软中断实现的:通过引发一个异常来促使系统切换到内核态去执行异常处理程序。此时的异常处理程序实际上就是系统调用处理程序
  • 软中断的触发:
    • 在x86系统上预定义的软终断是中断号128,通过int $0x80指令触发该中断。这条指令会触发一个异常导致系统切换到内核态并执行第128号异常处理程序,而该程序正是系统调用处理程序。这个处理程序就是system_call()。它与硬件体系结紧密相关 ,x86-64的系统上在entry_64.S文件中用汇编语言编写
  • 最近,x86处理器增加了一条叫做sysenter的指令。与int终断指令相比,这条指令提供了更快、更专业的陷入内核执行系统调用的方式。对这条指令的支持很快被加入内核。且不管系统调用处理程序被如何调用,用户空间引起异常或陷入内核就是一个重要的概念

指定系统调用号

  • 因为所有的系统调用陷入内核的方式都一样,所以仅仅是陷入内核空间是不够的。因此必须把系统调用号一并传给内核
  • 在x86上,系统调用号是通过eax寄存器传递给内核的。在陷入内核之前,用户空间就把相应系统调用所对应的号放入eax中。这样系统调用处理程序一旦运行,就可以从eax中得到数据。其他体系结构上的实现也都类似
  • 系统调用号的有效性检测:system_call()函数通过将给定的系统调用号与NR_syscalls做比较来检査其有效性。如果它大于或者等于NR_syscalls,该函数就返回-ENOSYS。否则,就执行相应的系统调用:
call *sys_call_table(,%rax,8)
  • 由于系统调用表中的表项是以64位(8字节)类型存放的,所以内核需要将给定的系统调用号乘以4,然后用所得的结果在该表中查询其位置。在x86-32系统上,代码很类似,只是用4代替8,参见下图

参数、返回值传递

  • 参数传递:
    • 除了系统调用号以外,大部分系统调用都还需要一些外部的参数输入。所以,在发生陷入的时候,应该把这些参数从用户空间传给内核
    • 最简单的办法就是像传递系统调用号一样,把这些参数也存放在寄存器里。在x86-32系统上,ebx、ecx、edx、esi和edi按照顺序存放前五个参数。 需要六个或六个以上参数的情况不多见,此时,应该用一个单独的寄存器存放指向所有这些参数 在用户空间地址的指针
  • 返回值传递:
    • 给用户空间的返回值也通过寄存器传递。在x86系统上,它存放在eax寄存器中。

五、系统调用的参数验证

  • 系统调用必须仔细检査它们所有的参数是否合法有效。系统调用在内核空间执行,如果任由用户将不合法的输入传递给内核,那么系统的安全和稳定将面临极大的考验
  • 举例来说,与文件I/O相关的系统调用必须检査文件描述符是否有效。与进程相关的函数必须检查提供的PID是否有效。必须检査每个参数,保证它们不但合法有效,而且正确。进程不应当让内核去访问那些它无权访问的资源

检查用户提供的指针

  • 最重要的一种检査就是检査用户提供的指针是否有效。试想,如果一个进程可以给内核传递指针而又无须检査,那么它就可以给出一个它根本就没有访问权限的指针,哄骗内核去为它拷贝本不允许它访问的数据,如原本属于其他进程的数据或者不可读的映射数据
  • 在接收一个用户空间的指针之前,内核必须验证:
    • 1.指针指向的内存区域属于用户空间。进程决不能哄骗内核去读内核空间的数据
    • 2.指针指向的内存区域在进程的地址空间里。进程决不能哄骗内核去读其他进程的数据
    • 3.如果是读,该内核应被标记为可读;如果是写,该内核应被标记为可写;如果是可执行,该内存被标记为可执行。进程决不能绕过内核访问限制

copy_to_user()、copy_from_user()

  • 内核提供了两个方法来完成必须的检杳和内核空间与用户空间之间数据的来回拷贝。注意,内核无论何时都不能轻率地接受来自用户空间的指针!这两个方法中必须经常有一个被使用
  • copy_to_user()
    • 为了向用户空间写入数据,内核提供了copy_to_user()
    • 三个参数:
      • 第一个参数是进程空间中的目的内存地址
      • 第二个是内核空间内的源地址
      • 最后一个参数是需要拷贝的数据长度(字节数)
  • copy_from_user()
    • 为了从用户空间读取数据,内核提供了copy_from_user()
    • 三个参数:
      • 与copy_to_user()类似,该函数把第二个参数指定的位置上的数据拷贝到第一个参数指定的位置上,拷贝的数据长度由第三个参数决定
  • 两个函数的返回值:
    • 失败,返回没能完成拷贝的数据的字节数。系统调用返回标准-EFAULT
    • 成功,返回0
  • 两个函数的阻塞:注意,这两个函数都有可能引起阻塞。当包含用户数据的页被换出到硬盘上而不是在物理内存上的时候,这种情况就会发生。此时,进程就会休眠,直到缺页处理程序将该页从硬盘重新换回物理内存
  • 演示案例:以一个既用了copy_from_user()又用了copy_to_user()的系统调用作例子进行考察。 这个系统调用silly_copy()毫无实际用处,它从第一个参数里拷贝数据到第二个参数。这种用途让人无法理解,它毫无必要地让内核空间作为中转站,把用户空间的数据从一个位置复制到另外 一个位置。但它却能演示出上述函数的用法

检测合法权限(suser()、capable()、capability.h)

  • 最后一项检查针对是否有合法权限
  • suser():
    • 在老版本的Linux内核中,需要超级用户权限的系统调用才可以通过调用suser()函数这个标准动作来完成检査。这个函数只能检査用户是否为超级用户
    • 现在它已经被一个更细粒度的“权能”机制代替(见下)
  • capable():
    • 新的系统允许检査针对特定资源的特殊权限。调用者可以使用capable函数来检査是否有权能对指定的资源进行操作
    • 返回值:
      • 返回非0:调用者有权进行操作
      • 返回0:表示无权
  • 演示案例:
    • capable(CAP_SYS_NICE) 可以检査调用者是否有权改变其他进程的nice值
    • 默认情况下,属于超级用户的进程拥有所有权利而非超级用户没有任何权利
    • 例如,下面是reboot()系统调用,注意,第一步是如何确保调用进程具有CAP_SYS_REBOOT权能。如果那样一个条件语句被删除,任何进程都可以启动系统了

  • capability.h:这个文件包含一份所有这些权能和其对应的权限的列表。例如下面的部分代码来自于linux 2.6.22/include/linux/capaility.h

   
  1. #define CAP_SYS_RESOURCE 24
  2. /* Allow manipulation of system clock */
  3. /* Allow irix_stime on mips */
  4. /* Allow setting the real-time clock */
  5. #define CAP_SYS_TIME 25
  6. /* Allow configuration of tty devices */
  7. /* Allow vhangup() of tty */
  8. #define CAP_SYS_TTY_CONFIG 26
  9. /* Allow the privileged aspects of mknod() */
  10. #define CAP_MKNOD 27
  11. /* Allow taking of leases on files */
  12. #define CAP_LEASE 28
  13. #define CAP_AUDIT_WRITE 29
  14. #define CAP_AUDIT_CONTROL 30

六、系统调用上下文

  • 在介绍进程管理的文章中我们说过,内核在执行系统调用的时候处于进程上下文。current指针指向当前任务,即引发系统调用的那个进程
  • 在进程上下文中,内核可以休眠(比如在系统调用阻塞或显式调schedule()的时候)并且可以被抢占。这两点都很重要:
    • 首先,能够休眠说明系统调用可以使用内核提供的绝大部分功能。在后面介绍中断的文章中可以看到,休眠的能力会给内核编程带来极大便利
    • 在进程上下文中能够被抢占其实表明,像用户空间内的进程一样,当前的进程同样可以被其他进程抢占。因为新的进程可以使用相同的系统调用,所以必须小心,保证该系统调用是可重入的。当然,这也是在对称多处理中必须同样关心的问题
  • 当系统调用返回的时候,控制权仍然在system_call()中,它最终会负责切换到用户空间,并让用户进程继续执行下去

绑定新的系统调用(asm/unistd.h)

  • 当编写完一个系统调用后,把它注册成一个正式的系统调用是件琐碎的工作:
    • 1.首先,在系统调用表的最后加入一个表项。每种支持该系统调用的硬件体系都必须做这样的工作(大部分的系统调用都针对所有的体系结构)。从0开始算起,系统调用在该表中的位置就是它的系统调用号。如第10个系统调用分配到的系统调用号为9
    • 2.对于所支持的各种体系结构,系统调用号都必须定义于<asm/unistd.h>中
    • 3.系统调用必须被编译进内核映象(不能被编译成模块)。这只要把它放进kernel/下的一个相关文件中就可以了,比如sys.c,它包含了各种各样的系统调用
  • 第一步:下面我们以通过一个虚构的系统调用foo()来观察这些步骤。先把sys_foo加入到系统调用表的末尾。该表位于/arch/i386/kernel/syscall_table.S中文件中(上面也有介绍)
    • 如果没有明确指定编号,则系统会自动分配调用号,例如下面分配的就是338
    • 另外,不同的体系结构不需要对应相同的系统调用号。系统调用号是专属于体系结构ABI(应用程序二进制接口)的部分。通常,你需要让系统调用适应每种体系结构。你可以注意一下,每隔5个表项就加入一个调用号注释的习惯,这样可以方便你查找

  • 第二步:加下来,把系统调用号加入到<asm/unistd.h>的最后,其格式如下

  • 第三步:我们来实现foo()系统调用。无论何种配置,该系统调用都必须编译到核心的内核映象中去,所以在这个例子中我们把它放进kernel/sys.c文件中。你也可以将其放到与其功能联系最紧密的代码中去,假如它的功能与调度相关,那么你也可以把它放到kernel/ sched.c中去

从用户空间访问系统调用

  • 通常,系统调用靠C库支持。用户程序通过包含标准头文件并和C库链接,就可以使用系统调用(或者调用库函数,再由库函数实际调用)。但如果你仅仅写出系统调用,glibc库恐怕并不提供支持
  • 值得庆幸的是,Linux本身提供了一组宏,用于直接对系统调用进行访问。它会设置好寄存器并调用陷入指令。这些宏是_syscalln,其中n的范围0到6,代表需要传递给系统调用的参数个数,这是由于该宏必须了解到底有多少参数按照什么次序压入寄存器
  • 对于每个宏来说,都有2 + 2*n 个参数:
    • 第一个参数对应着系统调用的返回值类型
    • 第二个参数是系统调用的名称
    • 再以后是按照系统调用参数的顺序排列的每个参数的类型和名称
  • 举个例子,open()系统调用的定义是:
long open(const char *filename,int flags,int mode);
  • 而不靠库支持,直接调用此系统调用的宏的形式为,这样应用程序就可以直接使用open()了:

   
  1. #define NR_open 5
  2. _syscall3( long,open, const char*,filename, int,flags, int,mode);
  • _NR_open在<asm/unistd.h>中定义,是系统调用号。该宏会被扩展成为内嵌汇编的C函数;由汇编语言执行前面内容中所讨论的步骤,将系统调用号和参数压入寄存器并触发软中断来陷入内核。调用open()系统调用直接把上面的宏放置在应用程序中就可以了
  • 现在我们写一个宏来使用上面自己编写的foo()系统调用,然后再写出测试代码:

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

  • 虽然实现一个新的系统调用很容易,但是不提倡这么做。通常都会有更好的办法用来代替新建一个系统调用以作实现。让我们看看采用系统调用作为实现方式的利弊和替代的方法
  • 建立一个新的系统调用的好处:
    • 系统调用创建容易且使用方便
    • Linux系统调用的高性能显而易见
  • 缺点:
    • 你需要一个系统调用号,而这需要一个内核在处于开发版本的时候由官方分配给你
    • 系统调用被加入稳定内核后就被固化了,为了避免应用程序的崩溃,它的接口不允许做改动
    • 需要将系统调用分别注册到每个需要支持的体系结构中去
    • 在脚本中不容易调用系统调用,也不能从文件系统直接访问系统调用
    • 由于你需要系统调用号,因此在主内核树之外是很难维护和使用系统调用的 
    • 如果仅仅进行简单的信息交换,系统调用就大材小用了
  • 替代方法:
    • 实现一个设备节点,并对此实现read()和write()。使用 ioctl()对特定的设置进行操作或者对 特定的信息进行检索
    • 像信号量这样的某些接口,可以用文件描述符来表示,因此也就可以按上述方式对其进行操作
    • 把增加的信息作为一个文件放在sysfs的合适位置
  • 总结:
    • 低于许多接口来说,系统调用都被视为正确的解决之道。但Linux系统尽董避免每出现一种新的抽象就简单的加入一个新的系统调用。这使得它的系统调用接口简洁得令人叹为观止,也就避免了许多后悔和反对意见(系统调用再也不被使用或支持)。新系统调用增添频率很低也反映出Linux是一个相对较为稳定并且功能已经较为完善的操作系统

转载:https://blog.csdn.net/qq_41453285/article/details/103802375
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场