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

释放双眼,带上耳机,听听看~!

一、系统调用概述

  • 系统调用在

用户空间进程和硬件设备之间添加了一个中间层

  • 该层主要作用有三个:

  • 第一,

为用户空间提供了一种硬件的抽象接口。举例来说,当需要读写文件的时候,应用程序就可以不去管磁盘类型和介质,甚至不用去管文件所在的文件系统到底是哪种类型
* 第二,系统调用保证了系统的稳定和安全。作为硬件设备和应用程序之间的中间人,内核可以基于权限、用户类型和其他一些规则对需要进行的访问进行裁决。举例来说,这样可以避免应用程序不正确地使用硬件设备,窃取其他进程的资源,或做出其他危害系统的事情
* 第三,在前面介绍进程的文章中曾说过,每个进程都运行在虚拟系统中,而在用户空间和系统的其余部分提供这样一层公共接口,也是出于这种考虑。如果应用程序可以随意访问硬件而内核又对此一无所知的话,几乎就没法实现多任务和虚拟内存,当然也不可能实现良好的稳定性和安全性

  • 在Linux中,系统调用是用户空间

**访问内核的唯一手段;**除异常和陷入外,它们是内核唯一的合法入口。实际上,其他的像设备文件和/proc之类的方式,最终也还是要通过系统调用进行访问的。而有趣的是,Linux提供的系统调用却比大部分操作系统都少得多

二、API、POSIX和C库

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

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

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
2
3
4
5
1SYSCALL_DEFINE0(getpid)
2{
3    return task_tgid_vnr(current); // returns current->tgid
4}
5
  • SYSCALL_DEFINE0只是一个宏,它定义了一个无参数的系统调用(因此这里为数字0,展开后的代码如下所示)


1
2
1asmlinkage long sys_getpid(void)
2
  • **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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
1ENTRY(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
326

四、系统调用处理程序

  • **用户空间的程序无法直接执行内核代码。**它们不能直接调用内核空间中的函数,因为内核驻留在受保护的地址空间上。如果进程可以直接在内核的地址空间上读写的话,系统的安全性和稳定性将不复存在
  • 所以,应用程序应该以某种方式通知系统,

**告诉内核自己需要执行一个系统调用,希望系统切换到内核态,**这样内核就可以代表应用程序在内核空间执行系统调用

软中断机制(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。否则,就执行相应的系统调用:


1
2
1call *sys_call_table(,%rax,8)
2
  • 由于

系统调用表中的表项是以64位(8字节)类型存放的,所以内核需要将给定的系统调用号乘以4,然后用所得的结果在该表中查询其位置。在x86-32系统上,代码很类似,只是用4代替8,参见下图

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

参数、返回值传递

  • 参数传递:

  • 除了系统调用号以外,大部分系统调用都还需要一些外部的参数输入。所以,在发生陷入的时候,应该把这些参数从用户空间传给内核

    • 最简单的办法就是像传递系统调用号一样,把这些参数也存放在寄存器里。在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()毫无实际用处,它从第一个参数里拷贝数据到第二个参数。这种用途让人无法理解,它毫无必要地让内核空间作为中转站,把用户空间的数据从一个位置复制到另外 一个位置。但它却能演示出上述函数的用法

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

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

  • 最后一项检查针对是否有合法权限

  • suser():

  • 在老版本的Linux内核中,需要超级用户权限的系统调用才可以通过调用suser()函数这个标准动作来完成检査。这个函数只能检査用户是否为超级用户

    • 现在它已经被一个更细粒度的“权能”机制代替(见下)
  • capable():

  • 新的系统允许检査针对特定资源的特殊权限。调用者可以使用capable函数来检査是否有权能对指定的资源进行操作

    • 返回值:
  • 返回非0:调用者有权进行操作
    * 返回0:表示无权

  • 演示案例:

  • capable(CAP_SYS_NICE) 可以检査调用者是否有权改变其他进程的nice值

    • 默认情况下,属于超级用户的进程拥有所有权利而非超级用户没有任何权利

    • 例如,下面是reboot()系统调用,注意,第一步是如何确保调用进程具有CAP_SYS_REBOOT权能。如果那样一个条件语句被删除,任何进程都可以启动系统了

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

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


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

六、系统调用上下文

  • 在介绍进程管理的文章中我们说过,内核在执行系统调用的时候处于进程上下文。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个表项就加入一个调用号注释的习惯,这样可以方便你查找

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

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

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

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

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

从用户空间访问系统调用

  • 通常,系统调用靠C库支持。用户程序通过包含标准头文件并和C库链接,就可以使用系统调用(或者调用库函数,再由库函数实际调用)。但如果你仅仅写出系统调用,glibc库恐怕并不提供支持
  • 值得庆幸的是,

Linux本身提供了一组宏,用于直接对系统调用进行访问。它会设置好寄存器并调用陷入指令。这些宏是_syscalln,
**其中n的范围0到6,代表需要传递给系统调用的参数个数,**这是由于该宏必须了解到底有多少参数按照什么次序压入寄存器

  • 对于每个宏来说,都有2 + 2*n 个参数:

  • 第一个参数对应着系统调用的返回值类型

    • 第二个参数是系统调用的名称
    • 再以后是按照系统调用参数的顺序排列的每个参数的类型和名称
  • **举个例子,**open()系统调用的定义是:


1
2
1long open(const char *filename,int flags,int mode);
2
  • 而不靠库支持,直接调用此系统调用的宏的形式为,这样应用程序就可以直接使用open()了:


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

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

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

  • 虽然实现一个新的系统调用很容易,但是不提倡这么做。通常都会有更好的办法用来代替新建一个系统调用以作实现。让我们看看采用系统调用作为实现方式的利弊和替代的方法

  • 建立一个新的系统调用的好处:

  • 系统调用创建容易且使用方便

    • Linux系统调用的高性能显而易见
  • 缺点:

  • 你需要一个系统调用号,而这需要一个内核在处于开发版本的时候由官方分配给你

    • 系统调用被加入稳定内核后就被固化了,为了避免应用程序的崩溃,它的接口不允许做改动
    • 需要将系统调用分别注册到每个需要支持的体系结构中去
    • 在脚本中不容易调用系统调用,也不能从文件系统直接访问系统调用
    • 由于你需要系统调用号,因此在主内核树之外是很难维护和使用系统调用的 
    • 如果仅仅进行简单的信息交换,系统调用就大材小用了
  • 替代方法:

  • 实现一个设备节点,并对此实现read()和write()。使用 ioctl()对特定的设置进行操作或者对 特定的信息进行检索

    • 像信号量这样的某些接口,可以用文件描述符来表示,因此也就可以按上述方式对其进行操作
    • 把增加的信息作为一个文件放在sysfs的合适位置
  • 总结:

  • 低于许多接口来说,系统调用都被视为正确的解决之道。但Linux系统尽董避免每出现一种新的抽象就简单的加入一个新的系统调用。这使得它的系统调用接口简洁得令人叹为观止,也就避免了许多后悔和反对意见(系统调用再也不被使用或支持)。新系统调用增添频率很低也反映出Linux是一个相对较为稳定并且功能已经较为完善的操作系统

给TA打赏
共{{data.count}}人
人已打赏
安全运维

WordPress网站专用docker容器环境带Waf

2020-7-18 20:04:44

安全运维

运维安全-Gitlab管理员权限安全思考

2021-9-19 9:16:14

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索