找回密码
 注册
关于网站域名变更的通知
查看: 242|回复: 1
打印 上一主题 下一主题

Linux内核设计与实现之系统调用

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2020-10-26 11:24 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式

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
  • TA的每日心情

    2019-11-29 15:37
  • 签到天数: 1 天

    [LV.1]初来乍到

    2#
    发表于 2020-10-26 13:12 | 只看该作者
    Linux内核设计与实现之系统调用
    您需要登录后才可以回帖 登录 | 注册

    本版积分规则

    关闭

    推荐内容上一条 /1 下一条

    EDA365公众号

    关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

    GMT+8, 2025-11-24 20:48 , Processed in 0.171875 second(s), 26 queries , Gzip On.

    深圳市墨知创新科技有限公司

    地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

    快速回复 返回顶部 返回列表