|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
主要内容:* p: ]8 o4 J# @2 a, O5 R8 K
. B5 L* ~, K+ P q" r7 y: Z- 什么是系统调用
- Linux上的系统调用实现原理
- 一个简单的系统调用的实现; K8 D: A4 A3 i5 s" y+ n
F7 r: w: d9 j7 f" i1 S- `7 _ Q
1. 什么是系统调用6 v6 p: w! x5 Z6 }9 y
简单来说,系统调用就是用户程序和硬件设备之间的桥梁。% f2 W3 \2 B; n* U3 F5 Y- E3 e9 S j, Z
/ O- \9 o- {7 t& r" U
用户程序在需要的时候,通过系统调用来使用硬件设备。8 d, n- m& E; I7 R
3 i( x$ u' r4 b/ t+ a8 \& K系统调用的存在,有以下重要的意义:
" l! \( i# |4 H0 S3 o" h
4 X* C+ n7 {+ I2 F1)用户程序通过系统调用来使用硬件,而不用关心具体的硬件设备,这样大大简化了用户程序的开发。
% x$ u/ W9 `% l
- @. k& a/ @" P1 s 比如:用户程序通过write()系统调用就可以将数据写入文件,而不必关心文件是在磁盘上还是软盘上,或者其他存储上。
' u$ X" J: r- n; w1 c9 }- u' S, }4 B$ q+ i, |2 D+ a! J: s
2)系统调用使得用户程序有更好的可移植性。
. U I# B, E$ j4 o' E9 `$ W! G. L6 ~4 ^" o) ~2 r7 q
只要操作系统提供的系统调用接口相同,用户程序就可在不用修改的情况下,从一个系统迁移到另一个操作系统。" h0 {' j. p( O
* |7 }4 g0 @$ t9 o5 R; o0 b
3)系统调用使得内核能更好的管理用户程序,增强了系统的稳定性。
: v6 k; u& t, S$ L# O
, T. G; s8 {7 [4 s$ |: Z+ G 因为系统调用是内核实现的,内核通过系统调用来控制开放什么功能及什么权限给用户程序。
5 f: `7 @) _4 g: B' O. k K
. W: I4 ^& A! ?& I 这样可以避免用户程序不正确的使用硬件设备,从而破坏了其他程序。
1 K# h2 A# d* E, x R
/ |; u7 R7 t& S4 [! T& C; {: t4)系统调用有效的分离了用户程序和内核的开发。
6 |, j w; ]& T# {% O0 c& X- A* K
4 X7 N5 V4 k" l) }, [ 用户程序只需关心系统调用API,通过这些API来开发自己的应用,不用关心API的具体实现。. Y. e4 Z$ a1 a. Y: q
! d$ ~6 T3 L2 [: a8 Y8 s
内核则只要关心系统调用API的实现,而不必管它们是被如何调用的。 U( }3 _1 {/ w: o/ w
9 Z5 S* ~: } e) O% l
9 k& q% s# m+ c4 n& a用户程序,系统调用,内核,硬件设备的调用关系如下图:
6 H B: @. i! F% X
- d, f q5 g$ T$ |
, @' K% M( k1 c0 X" ?2 e
( f$ ] e# P5 A' k2. Linux上的系统调用实现原理+ z; ?4 D3 Q4 i a k
要想实现系统调用,主要实现以下几个方面:/ E4 e0 B7 b& d; T3 G9 a
8 Y: l! A% Y6 G( q- i- 通知内核调用一个哪个系统调用
- 用户程序把系统调用的参数传递给内核
- 用户程序获取内核返回的系统调用返回值4 I3 z; I$ m1 _( m6 @) y
4 N* F8 T9 Q. R1 Y1 G4 `
下面看看Linux是如何实现上面3个功能的。# X; `7 x& c2 |5 d) d7 N* R
P& I r5 G3 Q" M2 q4 g& U, \2.1 通知内核调用一个哪个系统调用
% g1 ~7 Y4 q3 |每个系统调用都有一个系统调用号,系统调用发生时,内核就是根据传入的系统调用号来知道是哪个系统调用的。
% i6 \! \0 A4 X, _% Y4 `, e* N' K
' K) N1 a5 s1 h. X: Z O在x86架构中,用户空间将系统调用号是放在eax中的,系统调用处理程序通过eax取得系统调用号。
8 A- g6 v7 I5 Y3 ?0 R: a3 @# x$ }" s! g% N9 L6 b& {( S
系统调用号定义在内核代码:arch/alpha/include/asm/unistd.h 中,可以看出linux的系统调用不是很多。' o/ k3 L8 l8 r
6 |- I5 b$ P' R( u9 q) ^2.2 用户程序把系统调用的参数传递给内核
- o, T5 L/ }! @7 x) `4 l: g系统调用的参数也是通过寄存器传给内核的,在x86系统上,系统调用的前5个参数放在ebx,ecx,edx,esi和edi中,如果参数多的话,还需要用个单独的寄存器存放指向所有参数在用户空间地址的指针。, F5 l2 ]. O* h! V6 L K3 N4 b
" `8 k, S- b( p4 v. `一般的系统调用都是通过C库(最常用的是glibc库)来访问的,Linux内核提供一个从用户程序直接访问系统调用的方法。
n& ^$ @. ^4 c' P V
' @9 g, H. K7 R' a R: @% v参见内核代码:arch/cris/include/arch-v10/arch/unistd.h1 p# F( e9 Y) g0 |4 d' i# y
% c1 i6 k; J0 Y) U
里面定义了6个宏,分别可以调用参数个数为0~6的系统调用
! X/ P& ?; H$ F, ? T0 e; P( y, r0 @
- h" g/ E/ M E( O9 Y3 n/ _, a" f# b
_syscall0(type,name)5 ?, v9 `$ d4 s. n+ I) C
_syscall1(type,name,type1,arg1)
/ {$ G7 i: f& E: M_syscall2(type,name,type1,arg1,type2,arg2)4 P/ Y( d5 v4 P: X
_syscall3(type,name,type1,arg1,type2,arg2,type3,arg3), X( M4 y. q2 i- A
_syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4); _; ~8 `6 d* t& Q1 ]+ F
_syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,type5,arg5)
7 n1 z/ n+ r4 O2 D1 H8 G_syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,type5,arg5,type6,arg6)
2 j" w. K- x* O) ^3 c1 y, M' f) S- J' C+ |7 }
3 t2 ^" d! @( D3 }超过6个参数的系统调用很罕见,所以这里只定义了6个。
' o# @. E9 h7 H9 X6 B T$ P( ^$ p% l- ~- U t4 J% g+ @' I5 ?
2.3 用户程序获取内核返回的系统调用返回值
) ?# P, I0 C0 U9 V获取系统调用的返回值也是通过寄存器,在x86系统上,返回值放在eax中。
9 c, ]# A8 L# o8 }3 K) S
8 z) J! q% Y. j/ A" y, w4 M# A! P+ l* H
3. 一个简单的系统调用的实现' e' m3 M9 z3 X6 X2 V# U( Y' g
了解了Linux上系统调用的原理,下面就可以自己来实现一个简单的系统调用。, s# M7 q- E$ a, s9 w- x
' Z9 {) m) n0 F z& w4 h# {
3.1 环境准备3 L0 J) H7 t! ^5 y4 u
为了不破坏现有系统,我是用虚拟机来实验的。
) W9 h! r5 c2 W/ x m
7 U5 y. |3 h7 D" Q5 o' ]5 C3 ?) r$ _主机:fedora16 x86_64系统 + kvm(一种虚拟技术,就像virtualbox,vmware等)
; n8 h/ e2 s% O
5 ^' n; d3 z2 ^4 E虚拟机: 也是安装fedora16 x86_64系统(通过virt-manager很容易安装一个系统)
. y3 P: O: B: |+ f
7 u d5 E7 C1 ]& U
0 n$ [' ]5 G! [* I& {4 _- P3.2 修改内核源码中的相应文件$ m( h2 s5 G$ `9 x
主要修改以下文件:
$ b! o* P/ ^+ \8 o& n
% \. k- ]3 P- K8 o% \! d5 H6 Carch/x86/ia32/ia32entry.S
9 ~% G! o4 S; z/ u; y: P6 q& ]arch/x86/include/asm/unistd_32.h
) T. R" Y# W$ }, O8 @2 _8 [5 iarch/x86/include/asm/unistd_64.h o$ Q8 ^3 {; J8 @7 ~2 k
arch/x86/kernel/syscall_table_32.S
8 `( h2 k+ \' s, z5 v) M" I$ R( Zinclude/asm-generic/unistd.h
* \( S; P& Q8 cinclude/linux/syscalls.h( d, J* I4 y- e
kernel/sys.c. q2 B$ B! L. C6 P6 X
# m& |1 b% Y% c9 P- V
/ I0 C( x3 P4 Y4 [( t x5 R' f* X" R8 X3 w; p
我在sys.c中追加了2个函数:sys_foo和sys_bar+ S! R: G0 |8 O6 S4 n3 f
/ E8 P* m1 Y) E( f {6 {. R
如果是在x86_64的内核中增加一个系统调用,只需修改 arch/x86/include/asm/unistd_64.h,比如sys_bar。
8 F. m: n6 \ i$ j3 u* X z1 i. \" D; H+ b1 ~
修改内容参见下面的diff文件:
9 g$ U. X0 j: n
$ }! ` T% A4 f! }9 @# _diff -r new/arch/x86/ia32/ia32entry.S old/arch/x86/ia32/ia32entry.S
1 i! v7 z2 p) v; @855d854
/ {1 x3 D h# R2 w9 I< .quad sys_foo) v2 q$ Z: T- R: w; f
diff -r new/arch/x86/include/asm/unistd_32.h old/arch/x86/include/asm/unistd_32.h2 S7 S! F/ ^2 }! D% P
357d3567 i6 C2 c% g9 T! U' V& N/ O! B
< #define __NR_foo 349! n; `" w- h5 ~; z
361c360! t1 \; e# @& i& m5 c2 B
< #define NR_syscalls 350
( x$ {9 q% W2 E6 c" o' v7 U---
2 f' V! ^9 u/ s0 R, F; G& Q6 ^* H% x1 B> #define NR_syscalls 349
$ N. K0 X. C' g" [( B/ gdiff -r new/arch/x86/include/asm/unistd_64.h old/arch/x86/include/asm/unistd_64.h& R5 P! c9 i6 k; s
689,692d688. r1 b9 U7 T# P! _! c
< #define __NR_foo 312. I) N% ]1 y7 b% \3 z" w
< __SYSCALL(__NR_foo, sys_foo)2 ?) p* J2 M5 g' \; o# @
< #define __NR_bar 313
3 V1 G$ g- j2 f< __SYSCALL(__NR_bar, sys_bar): K/ Y3 K$ h$ x. F% z
diff -r new/arch/x86/kernel/syscall_table_32.S old/arch/x86/kernel/syscall_table_32.S; |' `3 e& G; r* r
351d350; N. S: z; D. |
< .long sys_foo
) p w/ }; ^7 j0 O" a8 Sdiff -r new/include/asm-generic/unistd.h old/include/asm-generic/unistd.h
$ z: p6 u; m# r) G694,695d693
, [2 n& o7 ~1 U+ J! w< #define __NR_foo 272
7 f: M& ^) z, z2 A$ F# u1 J* {/ M< __SYSCALL(__NR_foo, sys_foo)2 v9 X& W7 p3 f5 S: J9 S5 a
698c696, q- Q$ J/ B. t+ r! D/ ~
< #define __NR_syscalls 273
& E! G7 R. R: ~- }9 g$ }2 @---& E: r. }. d7 z2 h, @/ E
> #define __NR_syscalls 272: M, ~- X, k" S; [4 B
diff -r new/kernel/sys.c old/kernel/sys.c, T% w- O$ C M9 o
1920,1928d19196 w; Z5 x( X( t0 }# M
<
c4 R3 x6 U9 h6 b< asmlinkage long sys_foo(void)
0 t3 y6 E% T! K, u5 f5 S/ Z; H< {
5 Y* u @+ M! m& y: V; U. q6 c< return 1112223334444555;% \- ~4 L y1 [ {* z9 _; F: S
< }# {9 O4 u$ _. Z W0 c
< asmlinkage long sys_bar(void)% f( S4 b" s4 P2 c4 D
< {( \/ b0 _* Z3 q$ J
< return 1234567890;
) a' h2 z* _$ Q% l+ R% S$ q< }# X4 v2 p0 R7 h8 I9 X ~8 B# U# \
G) {' V5 R, e% |& N9 ]
. X% h9 ^! W; V0 x6 R8 O; O
3.3 编译内核
. t3 U* V$ E: J#cd linux-3.2.28
6 j5 q2 a7 |# X#make menuconfig (选择要编译参数,如果不熟悉内核编译,用默认选项即可)
; q* B2 Y- n/ g! g J#make all (这一步真的时间很长......)
/ w+ @' u5 @7 ?! p#make modules_install' }6 U6 r3 L! ~0 m3 m7 u7 \
#make install (这一步会把新的内核加到启动项中)! t2 L7 U% p, ], P. B
#reboot (重启系统进入新的内核)9 k; I8 @: z { N$ A7 o' z' q
' u1 l6 \, s, z {, w, h0 ^. m7 P- R; h: k$ @$ N4 _; r
3.4 编写调用的系统调用的代码/ u6 _, z! G* X% h* h/ ?+ k3 |
, D% ^0 {. c5 v7 [( s8 i#include <unistd.h>
8 I# C- P# x+ @ l5 O$ A9 I#include <sys/syscall.h>
6 T1 ^- q, o5 Q4 {/ O; C6 E#include <string.h>
0 ]+ W' {# @) G- z6 w% W#include <stdio.h>
; R; G" q5 B: a4 O1 c% m! {! J#include <errno.h>
- g& m0 K3 {3 z9 R" J
7 P+ i1 l$ }( ~* Y( [2 K
' _; ^% Z! {/ J; G% `8 Z0 O#define __NR_foo 3127 M u2 u+ t+ B$ _8 G
#define __NR_bar 313
# C5 g+ p6 y. T. S3 g+ f* G
1 S. ?, p* F0 Gint main()9 i( n# Y* I4 k( w
{
- v( V. Q; i& J$ q printf ("result foo is %ld\n", syscall(__NR_foo));
8 H# h! z1 X6 q! i8 e printf("%s\n", strerror(errno));
) s8 l. u$ W* a8 V( d4 l' [ printf ("result bar is %ld\n", syscall(__NR_bar));" C& Y. C% B6 F; c9 p! R& Z) q
printf("%s\n", strerror(errno));3 u: @2 o! y: Z9 l
return 0;7 ^2 O, t, ]# P/ f- W
}
' J8 l$ @' E, N0 j
M3 e5 s' @1 @0 _* Y( q8 ?8 R8 Q: J
编译运行上面的代码:6 ^8 |0 I2 m/ H9 W, P2 a5 S
7 B* W2 M. f2 Z. d# A
#gcc test.c -o test# a4 Z3 J9 t/ s2 w* V
#./test
+ s+ s& J0 p1 A8 o# S8 v- h+ x3 m- _8 F0 l# E2 E4 s w* p
运行结果如下:
( e. E1 L" r; R/ z3 {
7 L# K8 t# Y$ Cresult foo is 1112223334444555 Y- O" V8 U9 }
Success- O/ o, u: _; W
result bar is 1234567890
- {3 g7 R/ q' ~: h7 YSuccess
[6 F. R% `: q; w M4 ` |
|