|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
主要内容:
8 n4 `3 q' q7 n( R: K5 y8 l) w' T3 M4 \ |& ]8 b( q3 N
- 什么是系统调用
- Linux上的系统调用实现原理
- 一个简单的系统调用的实现
, T6 a6 [ x& j1 \ % N' A1 j" F2 m8 w; q! r6 b; r
1. 什么是系统调用$ t. C! J2 @ X8 Q+ v
简单来说,系统调用就是用户程序和硬件设备之间的桥梁。
2 }4 s" s7 r( J& l4 K3 V7 J( K" z
+ P8 N _) Y$ i# N% x" x用户程序在需要的时候,通过系统调用来使用硬件设备。
1 `, \% T' \5 y$ E$ a. @# B, m# n' H0 l6 _8 Z- R& s% r1 S5 D; o' N& o
系统调用的存在,有以下重要的意义:
' Z( A. G7 {, H7 e0 U9 O: h: T: O
1)用户程序通过系统调用来使用硬件,而不用关心具体的硬件设备,这样大大简化了用户程序的开发。7 [) u/ }7 T( \ H( Y$ E& {
6 w3 S+ a8 X2 d. `$ X9 n 比如:用户程序通过write()系统调用就可以将数据写入文件,而不必关心文件是在磁盘上还是软盘上,或者其他存储上。; U+ t8 V) z+ x5 m0 S
5 Q& }: k0 A; R) n& Z8 C5 O2 U2)系统调用使得用户程序有更好的可移植性。
- f+ S; |2 o7 J1 K8 e H }& s3 Y: h/ |) n4 u% s* N
只要操作系统提供的系统调用接口相同,用户程序就可在不用修改的情况下,从一个系统迁移到另一个操作系统。8 ]9 ?1 z4 s+ N5 h+ Z' ]! n& w
$ ]& I- c" N# G H. R3)系统调用使得内核能更好的管理用户程序,增强了系统的稳定性。2 Q6 b1 v' w# h7 I8 T
6 r7 f+ s4 x+ L; S1 y) ~ 因为系统调用是内核实现的,内核通过系统调用来控制开放什么功能及什么权限给用户程序。
# Q" I; U" i/ N' T
! }5 G6 I; ^$ z" p( t 这样可以避免用户程序不正确的使用硬件设备,从而破坏了其他程序。
- L1 N H4 F( Q% q: R
- a" e; z- w! s! J$ m6 ?4)系统调用有效的分离了用户程序和内核的开发。
0 @: P4 K7 D' Q/ i2 ]% p
$ W1 Y. ~: u; K5 p 用户程序只需关心系统调用API,通过这些API来开发自己的应用,不用关心API的具体实现。
! @/ [: k# N9 s" Z
! E$ a4 _/ I# A6 Y+ ?% q5 z6 S 内核则只要关心系统调用API的实现,而不必管它们是被如何调用的。
6 K! r2 P# Q3 c- m2 _ g
8 T7 {# a/ w, {/ V" A. a* h) X& M# g7 U$ ?9 l
用户程序,系统调用,内核,硬件设备的调用关系如下图:
`" ^. e+ V4 ~- G2 R
) M0 Z* J( E4 L
3 a5 q A. y& J, [5 P, b7 u) G ]% o$ h6 E- R4 W) M
2. Linux上的系统调用实现原理0 g- v8 H- d j9 ~! `
要想实现系统调用,主要实现以下几个方面:& G3 u# s, q. A+ d! o
, H* t! l3 b! @7 r' y
- 通知内核调用一个哪个系统调用
- 用户程序把系统调用的参数传递给内核
- 用户程序获取内核返回的系统调用返回值
- D% j7 W+ c4 s( d9 }# l& _
, M( F. J0 v6 r' y% w* x7 h. ~下面看看Linux是如何实现上面3个功能的。
% M: N; l; U3 D4 N: d. g; C) N- d
5 z7 [: m8 l# ?6 O( k( |: l, ]2.1 通知内核调用一个哪个系统调用
! O, I; U h/ Y0 h; d每个系统调用都有一个系统调用号,系统调用发生时,内核就是根据传入的系统调用号来知道是哪个系统调用的。
$ b; F: x/ N0 p9 q. O
. w) U8 {# T! o0 l在x86架构中,用户空间将系统调用号是放在eax中的,系统调用处理程序通过eax取得系统调用号。5 _8 m) a7 h- R; W8 `" m
/ u" L; p; M% y N系统调用号定义在内核代码:arch/alpha/include/asm/unistd.h 中,可以看出linux的系统调用不是很多。# B, U, D0 H, P
* [( Z b! b' c4 P/ z2.2 用户程序把系统调用的参数传递给内核- H, p6 n7 \4 z' W; r$ T
系统调用的参数也是通过寄存器传给内核的,在x86系统上,系统调用的前5个参数放在ebx,ecx,edx,esi和edi中,如果参数多的话,还需要用个单独的寄存器存放指向所有参数在用户空间地址的指针。
9 S, i( P2 L, [ ~2 n2 z7 J
: Q- r0 P! K/ M7 }3 Q: j一般的系统调用都是通过C库(最常用的是glibc库)来访问的,Linux内核提供一个从用户程序直接访问系统调用的方法。- h5 z5 \% n0 L, S1 {. Z+ t5 s; H0 W
3 H N+ a* t# x1 T; G- L
参见内核代码:arch/cris/include/arch-v10/arch/unistd.h
2 Z# I" @$ [: e! O1 r* B
4 }6 f* I7 T& q. q里面定义了6个宏,分别可以调用参数个数为0~6的系统调用% w0 F; p' b9 J
. Y7 P& j9 e: w, N3 `
/ U' z0 H _3 ]+ \; L% h4 f7 J+ O; |0 X_syscall0(type,name)
5 S4 U2 i ?* e; B- Q_syscall1(type,name,type1,arg1)
: v5 W" t1 y# y! v1 E_syscall2(type,name,type1,arg1,type2,arg2)& u0 _3 j: {6 R3 \$ m7 ~+ H9 W2 _
_syscall3(type,name,type1,arg1,type2,arg2,type3,arg3)
7 z+ W; j/ q1 Q2 F6 `_syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4), S( K2 m$ Z) f' J J' c
_syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,type5,arg5)" H O* j- z5 b" L- ~+ N- [8 \. }
_syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,type5,arg5,type6,arg6)
! N( e7 @" @5 k6 R; D0 d* U5 E
5 z/ p# a4 w: L) y/ m5 w) i$ K( T Q4 a9 j. r" V4 S
超过6个参数的系统调用很罕见,所以这里只定义了6个。
1 P9 [- a4 l4 g3 d' D- j% j( e! [0 u/ y8 k, n7 D* |8 L3 G
2.3 用户程序获取内核返回的系统调用返回值
& J) O0 d6 ?3 g) T获取系统调用的返回值也是通过寄存器,在x86系统上,返回值放在eax中。
w! |& Q7 _9 J- W/ T2 M( n6 t2 |8 l) \$ c) k( i# O
5 m, @3 b7 l; I: w B- o3. 一个简单的系统调用的实现 P% ~' y7 Y/ {) Q0 F
了解了Linux上系统调用的原理,下面就可以自己来实现一个简单的系统调用。* Q# k4 a- [3 _3 _
4 l' O) F% K3 K# O3.1 环境准备
' B% L7 y+ x7 n q为了不破坏现有系统,我是用虚拟机来实验的。) }5 c3 a5 f8 D( u
2 D3 f7 y3 q0 {9 u7 J+ F3 @9 k主机:fedora16 x86_64系统 + kvm(一种虚拟技术,就像virtualbox,vmware等)
# F' V! `7 \$ L9 I% k& w& O9 d1 L/ C/ p0 m j+ `0 l
虚拟机: 也是安装fedora16 x86_64系统(通过virt-manager很容易安装一个系统)
( p# W B+ U+ L2 ^1 [0 w6 O2 {0 L# O7 B- Q
; ]5 n# o# {/ r) _( A( A( J
3.2 修改内核源码中的相应文件
: T/ y+ t' K8 M主要修改以下文件:8 J8 D& r8 q! V
4 p+ D) U8 _# P* \
arch/x86/ia32/ia32entry.S
, V' y# }! U3 W2 x6 larch/x86/include/asm/unistd_32.h1 m2 a7 y' p J0 u
arch/x86/include/asm/unistd_64.h# m9 F8 L% }9 v7 Z5 G* @3 Q8 q* L
arch/x86/kernel/syscall_table_32.S
) Y9 H/ V; c9 w' ^1 b/ Minclude/asm-generic/unistd.h+ o3 w, |6 h( c( X8 f
include/linux/syscalls.h* G- z. |# F9 F& u& ]
kernel/sys.c. O) Q4 `% T( ?4 \
& i% a X" X) l [- {8 O, J
; O& P; A& s6 [' ^ W. i* C
* P4 a/ _7 |% f; }
我在sys.c中追加了2个函数:sys_foo和sys_bar, [) `0 `; k; Z/ `- s5 V
5 u* t5 f/ A" l* m如果是在x86_64的内核中增加一个系统调用,只需修改 arch/x86/include/asm/unistd_64.h,比如sys_bar。 ~) H* f; ?7 z/ o4 e
6 O9 h+ h8 [! S9 W2 u/ K修改内容参见下面的diff文件:: q% {6 [# j4 R9 C7 Q( ~
- q; f! ?3 p5 ?& e* S# v
diff -r new/arch/x86/ia32/ia32entry.S old/arch/x86/ia32/ia32entry.S5 K( N: ~6 H, d8 N* p; r; Q, p
855d8545 `5 R z( q( L; c
< .quad sys_foo
7 _3 E* @6 F+ _! ~ @diff -r new/arch/x86/include/asm/unistd_32.h old/arch/x86/include/asm/unistd_32.h
# K* c Y0 h. ]: j% @6 h' E357d356
" g" T: Y/ k3 `! j' P< #define __NR_foo 3499 r8 i4 F5 ~8 W+ _9 B1 z+ ]
361c3602 C# `2 m6 O) o% x; a# A* R) ^
< #define NR_syscalls 350
3 U- J; I0 u6 \+ ]---& m, r2 l# D6 X+ u: r- m
> #define NR_syscalls 349
/ B4 Q. L+ I6 h6 J3 H9 z4 bdiff -r new/arch/x86/include/asm/unistd_64.h old/arch/x86/include/asm/unistd_64.h
9 I S: S9 d1 C' M. B689,692d688
# Z) _# @/ V1 F$ J" S: Q< #define __NR_foo 3126 _) }% u! V5 w) c
< __SYSCALL(__NR_foo, sys_foo)
/ c7 _9 `: ]; r3 K3 a7 O7 N< #define __NR_bar 3133 ~8 T1 R* h5 y! @4 n+ u
< __SYSCALL(__NR_bar, sys_bar)+ c8 ~0 O+ D9 R
diff -r new/arch/x86/kernel/syscall_table_32.S old/arch/x86/kernel/syscall_table_32.S/ x. m2 I, L b2 B- i7 H6 X
351d350
6 o/ _1 z; X% ?+ Q- I" X) N* V) b< .long sys_foo: e6 v+ q$ B: f% D! b
diff -r new/include/asm-generic/unistd.h old/include/asm-generic/unistd.h
$ U, W; X, x" @! F* |694,695d693( ?0 E$ p2 q- D3 Y4 r- Y
< #define __NR_foo 272% `+ e7 L$ y4 i8 h" ~% Z; Q" k
< __SYSCALL(__NR_foo, sys_foo)0 f: @$ j9 r$ I' R
698c696/ i! V* V0 b8 @+ h1 Q/ x. O
< #define __NR_syscalls 273. W: x3 G# g, |9 `$ h8 h
---
6 O) X; m9 |( p( d% }- `> #define __NR_syscalls 272
- f1 s. B& S3 s2 Pdiff -r new/kernel/sys.c old/kernel/sys.c
4 h; C5 U5 T) d7 b3 Y' t1920,1928d1919! o8 _1 b5 \8 U; |1 F. K% E
<
1 U' m" C8 n6 D$ z< asmlinkage long sys_foo(void)
1 F* r7 `$ {# d! O; }, `0 ]! q< {
# l& ?+ R/ h( U# A< return 1112223334444555;
7 S2 W( v: r- I% l< }
& r) t9 _& `7 X T J7 A< asmlinkage long sys_bar(void)
% O0 n d- S0 X2 b5 a4 `% [< {
8 P' y. a1 w, R< return 1234567890;! R3 j0 ^( n a4 @8 b& \. ^
< }3 p3 W- X% e9 ]
9 c" K0 s% J; h& } v8 g6 K" D2 u4 U
3.3 编译内核 X: P; w4 k1 p- b6 T" }' F
#cd linux-3.2.285 T/ o4 S' z3 j/ a5 c" ?
#make menuconfig (选择要编译参数,如果不熟悉内核编译,用默认选项即可)
! n8 S) i2 v1 O% F \#make all (这一步真的时间很长......)
( V; g& x6 y, ~+ |#make modules_install
' D, j4 E$ T: s( {3 S' I; m+ q#make install (这一步会把新的内核加到启动项中)
. P! C# M" f/ l7 d#reboot (重启系统进入新的内核)( [2 o! u! j( t5 R( ?2 k
; a4 ^0 l$ [7 \) n I
4 J# I& M& P: u3.4 编写调用的系统调用的代码
. |6 k: N2 I7 T. W0 V& J2 y4 h" f r
4 V: N& P8 f3 I7 S7 C/ L- ^, r#include <unistd.h># u8 B, C8 f: J+ |" r
#include <sys/syscall.h>
' `5 C+ N/ ~8 {% N1 K#include <string.h>
8 D/ V0 H* s0 F6 e+ ~1 u#include <stdio.h>
. N6 X- s4 E/ j0 Q' L#include <errno.h>
) V8 j% c2 C% i2 i
$ Z4 t& y+ O( k. [ n% c+ u$ [6 _: n9 P5 f) R5 `8 @3 ~" P; m# ^
#define __NR_foo 312
) Q# O. ]; c* f2 z( I; k$ p ^6 C#define __NR_bar 313. T# c$ n5 `, U, X
0 i& j4 `3 M1 [% C
int main()
( \* u9 w) v8 ]1 q7 U{' ]& q/ e! w6 N- D4 M7 |( D6 Q# y
printf ("result foo is %ld\n", syscall(__NR_foo));& \7 ]# Q* z( _: L& a8 r; s
printf("%s\n", strerror(errno));$ X# E; n- ~( u, M3 |2 E
printf ("result bar is %ld\n", syscall(__NR_bar));
% X2 R4 d/ U% h) a9 }- B/ y* n2 M printf("%s\n", strerror(errno));0 T# C3 K e, p, W2 o% `
return 0;
$ k: H7 g& |8 w: B4 d2 F}
+ @+ B- j9 d X5 i! {& }- S; c" |) L# v3 T5 P0 l4 P
0 g/ B# }, r% y( j2 H
编译运行上面的代码:
/ k) W) D6 I: D3 ]% I
- O H5 @3 m& X1 ]4 Y A; D7 h) O#gcc test.c -o test
/ S) J% Q; ?) P7 k( z9 s3 p#./test
8 K3 X4 x j; a; }9 Y/ {2 m" n. P; R3 O: E
运行结果如下:
& G5 @$ P5 R. D+ j4 j8 R1 |6 O/ S( v1 I5 R6 j
result foo is 1112223334444555
- |+ _' V. o- J$ d2 J. w0 |: zSuccess& _ g0 c& b4 I
result bar is 12345678905 _& p& F& W' u
Success0 S; Y+ f o) ] _, y! O% a# `4 {' F
|
|