Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1690342
  • 博文数量: 76
  • 博客积分: 2175
  • 博客等级: 大尉
  • 技术积分: 2481
  • 用 户 组: 普通用户
  • 注册时间: 2010-04-20 20:49
个人简介

欢迎光临我的博客

文章分类

全部博文(76)

文章存档

2018年(4)

2017年(1)

2016年(2)

2015年(2)

2013年(5)

2012年(29)

2010年(33)

分类:

2010-08-12 14:36:25

系统调用(system call)是由操作系统提供的一组接口,以供应用程序调用来访问内核资源,请求所需服务。我们也可称之为系统调用接口或系统接口。由于操作系统接管了计算机中所有硬件的管理,而且自身也抽象出很多资源,比如文件、进程、管道、信号等,它们都位于内核中,任何对这些资源的直接使用都是不可能的,需要通过操作系统才能实现。作为系统中唯一的可信软件,操作系统在实现应用程序所请求的服务时,采取有效策略来保证系统资源在各个进程间合理、安全有效地分配使用,同时确保系统的安全性。

12.1 

如果把操作系统比作一辆汽车,那么系统调用就像点火开关、方向盘、油门踏板、制动踏板和离合器等,离开了它们,用户就无法操纵汽车,汽车只能永远停在原地,尽管它的内部系统是完整的,有发动机、传动系和液压刹车系统等。这是因为用户并没有一个很好的“接口”来使用这些系统。没有了系统调用的操作系统也是一样,尽管它在内部提供了很多丰富的资源,并有一套很好的机制管理它们,但如果应用程序无法使用它们,也没有多少实际意义。因为对于计算机用户而言,他们更关心的是应用软件,而不是操作系统。

由此可见,系统调用对于操作系统的必要性是不言而喻的,系统调用实现的优劣直接影响应用软件的优劣,正如没有人会喜欢方向盘操作很费力或者打火很麻烦的汽车(精力过剩者例外),没有人会喜欢烦琐难用的系统调用。

系统调用在某些操作系统比如Windows中又称为应用编程接口(API——Application Programming Interface)。另外还有一些软件开发包(SDK——Software Development Kit),它们可以是操作系统自身提供的,也可以是第三方提供的,它们多数是为了满足某些特定功能需求而在系统调用基础上进行的二次开发,严格意义上讲这些并不属于系统调用。除此之外,还有一些标准接口,比如strcpymemmovesin等,它们只是实现一些常用的功能来方便程序员的开发,也不属于系统调用。

那么,系统调用和普通函数的区别究竟在哪里呢?

1.系统调用是由操作系统实现的,而普通函数可以由任何软件实现;

2.系统调用用来访问内核资源,而普通函数则访问其他资源(不包括封装系统调用的函数)或实现某些特定功能;

3.系统调用会引起程序从用户态到内核态的转变,而普通函数则不会。

常见的系统调用有:文件访问接口creatopenreadwriteclose,子进程创建接口fork,系统时间接口time,信号处理接口signalkill等。

12.2  统调用的实现

系统调用既然由用户程序调用来访问内核资源,那么它就必然会引起当前模式的切换——从用户模式切换到内核模式,这就需要特殊的指令,在第7章中已经提到过,这个指令就是traptrap是唯一引起“合法”自陷的指令,在执行完trap指令后,当前模式从用户模式切换到内核模式;在内核完成应用程序所请求的服务后,再执行rttrti指令从内核模式返回到用户模式。trap指令引起模式切换的情况如图12-1所示。


12-1  trap指令引起模式切换


和一般指令不同,trap指令对应的机器码是一段范围,而不是唯一的,这样做考虑到操作系统一般有多个系统调用的需求,这样每一个机器码可以标识一个系统调用。PDP 11/40trap指令对应的机器码是0o1044000o104777,共256个,这里我们把指令的低8位值称为系统调用号,它的范围是0255

当公司中某工程师需要出差时,他会向部门秘书提出交通订票申请,秘书一般会向指定的订票公司订票。在订票成功后,订票公司把票交给秘书,秘书再交给工程师,员工订票流程如图12-2所示。

12-2  员工订票流程图

这里的工程师就相当于应用程序,订票申请就相当于系统调用。和订票的过程相似,系统调用也分为两个部分:用户实现和内核实现,秘书向订票公司订票的过程就相当于用户实现,而订票公司出票的过程则相当于内核实现。用户实现是应用程序所能看到的接口的实现,它最重要的指令就是trap。在做完一些必要处理(比如设置好用户传入的参数)后,用户实现执行对应的trap指令,引起自陷,这时已经进入到内核模式。自陷服务函数再根据trap指令的类型,在系统调用表中找到其对应的内核实现并调用。内核实现处理完后,首先返回到用户实现中,最后用户实现再返回到应用程序中,系统调用处理的流程如图12-3所示。



12-3  系统调用处理流程图

用户实现一般以库和头文件的方式提供给程序员,参与应用程序的编译,最终被链接到应用程序空间,当然也运行在用户空间。内核实现和用户实现相对应,提供系统调用内核部分的实现,包括对各类资源的访问和处理等。它运行在操作系统的内核中,对于应用程序是不可见的。第8.3节中的文件访问接口准确地说应该是文件访问接口的内核实现,而前几章所举例子中的文件系统调用都是用户实现接口,而不是第8.3节中的内核实现接口。因此我们也就明白了为什么内核实现的参数类型和实际系统调用不一样,甚至有些名称都不同,这是因为它们并不暴露给应用程序员,它和用户实现只是通过系统调用表产生关联。那么,系统调用的参数是如何传递给内核实现的呢?通过寄存器或内存传递,之所以不使用栈传递,是因为用户栈和内核栈是独立的,没法直接传递。该详细过程还是比较复杂的,请参见第12.3节。

 

12.2.1  用户实现

UNIX的用户实现并没有使用PDP 11/40trap汇编指令,而是使用自定义的sys汇编指令。比如,系统调用time的用户实现是:

/ C library -- time

/ tvec = time(tvec);

/

/ tvec[0], tvec[1] contain the time

 

.globl    _time

_time:

   mov    r5,-(sp)

   mov    sp,r5

   sys    time

   mov    r2,-(sp)

   mov    4(r5),r2

   mov    r0,(r2)+

   mov    r1,(r2)+

   mov    (sp)+,r2

   mov    (sp)+,r5

   rts    pc

其中的自陷指令是sys time,而不是trap time,当然它翻译后的机器码值肯定在trap指令的机器码0o1044000o104777范围内,具体是哪一个,编译器会根据后面的值time翻译。程序中之所以不使用trap而使用sys,可能是不想引起歧义,标明这是系统调用而不是其他自陷吧,但本质上它们是相同的,本章中的sys指令和trap指令含义是相同的。

12.2.2  系统调用表和trap自陷

根据第7章我们知道,trap指令的自陷向量位于地址28处,它的服务函数是trap,回顾一下它的如下代码。

-----------------------选自光盘文件/usr/sys/conf/m40.s----------------------

     /* -------------------------*/  

1.   .globl trap, call

     /* -------------------------*/

2. .globl _trap

3. trap:

4.          mov PS,-4(sp)

5.          tst nofault

6.          bne 1f

7.          mov SSR0,ssr

8.          mov SSR2,ssr+4

9.          mov $1,SSR0

10.          jsr r0,call1; _trap / no return

11.  1:

12.                mov $1,SSR0

13.                 mov nofault,(sp)

14.                rtt

trap不仅是系统调用自陷服务函数,还是其他所有自陷服务函数。它在调用call1函数后,call1再调用C语言的trap函数(trap.c)。在看trap函数之前,先看看系统调用表。

系统调用表定义了不同机器码的trap指令和内核实现之间的映射关系,它是sysent结构数组。sysent结构在trap.c中定义。

/*

 * structure of the system entry table (sysent.c)

 */

struct sysent    {

   int    count;        /* argument count */

   int    (*call)();    /* name of handler */

} sysent[64];

count定义内核实现的参数个数,准确地说是u.u_arg[5]中有效元素个数,而不是实际系统调用的参数个数或者内核实现函数的形参个数,因为内核实现函数都没有形参,用户实现的入参都通过u.u_arg[5]u.u_ar0传递。比如read内核实现的参数个数是2,但系统调用的参数个数是3,原因就在于有一个参数通过u.u_ar0传递了。

call是内核实现函数指针。下面是sysent[64]的具体值。

----------------------选自光盘文件/usr/sys/ken/sysent.c----------------------

/*

  * This table is the switch used to transfer

  * to the appropriate routine for processing a system call

  * Each row contains the number of arguments expected

  * and a pointer to the routine.

  */

 int sysent[]

 {

    0, &nullsys,/*  0 = indir */

    0, &rexit,/*  1 = exit */

    0, &fork,/*  2 = fork */

    2, &read,/*  3 = read */

    2, &write,/*  4 = write */

    2, &open,/*  5 = open */

    0, &close,/*  6 = close */

    0, &wait,/*  7 = wait */

    2, &creat,/*  8 = creat */

    2, &link,/*  9 = link */

    1, &unlink,/* 10 = ulink */

    2, &exec,/* 11 = exec */

    1, &chdir,/* 12 = chdir */

    0, >ime,/* 13 = time */

    3, &mknod,/* 14 = mknod */

    2, &chmod,/* 15 = chmod */

    2, &chown,/* 16 = chown */

    1, &sbreak,/* 17 = break */

    2, &stat,/* 18 = stat */

    2, &seek,/* 19 = seek */

    0, &getpid,/* 20 = getpid */

    3, &smount,/* 21 = mount */

    1, &sumount,/* 22 = unmount */

    0, &setuid,/* 23 = setuid */

    0, &getuid,/* 24 = getuid */

    0, &stime,/* 25 = stime */

    3, &ptrace,/* 26 = ptrace */

    0, &nosys,/* 27 = x */

    1, &fstat,/* 28 = fstat */

    0, &nosys,/* 29 = x */

    1, &nullsys,    /* inoperative  /* 30 = smdate */

    1, &stty,/* 31 = stty */

    1, >ty,/* 32 = gtty */

    0, &nosys,/* 33 = x */

    0, &nice,/* 34 = nice */

    0, &sslep,/* 35 = sleep */

    0, &sync,/* 36 = sync */

    1, &kill,/* 37 = kill */

    0, &getswit,/* 38 = switch */

    0, &nosys,/* 39 = x */

    0, &nosys,/* 40 = x */

    0, &dup,/* 41 = dup */

    0, &pipe,/* 42 = pipe */

    1, ×,/* 43 = times */

    4, &profil,/* 44 = prof */

    0, &nosys,/* 45 = tui */

    0, &setgid,/* 46 = setgid */

    0, &getgid,/* 47 = getgid */

    2, &ssig,/* 48 = sig */

    0, &nosys,/* 49 = x */

    0, &nosys,/* 50 = x */

    0, &nosys,/* 51 = x */

    0, &nosys,/* 52 = x */

    0, &nosys,/* 53 = x */

    0, &nosys,/* 54 = x */

    0, &nosys,/* 55 = x */

    0, &nosys,/* 56 = x */

    0, &nosys,/* 57 = x */

    0, &nosys,/* 58 = x */

    0, &nosys,/* 59 = x */

    0, &nosys,/* 60 = x */

    0, &nosys,/* 61 = x */

    0, &nosys,/* 62 = x */

    0, &nosys,/* 63 = x */

 };

sysent[]的准确类型应该是struct sysent。指令0o104400对应其第零个元素,它的内核实现是nullsys,当然实际上并不是,而是一个间接调用。指令X 对应第X – 0o104400个元素,它的内核实现是sysent[X–0o104400].call,对应u.u_arg[5]中有效参数个数是sysent[X–0o104400].count

回顾一下7.4.2节中C语言trap函数:

函数原型:void trap(int dev, int sp, int r1, int nps, int r0, int pc, int ps)

功能描述:自陷处理的C语言过程,如果是系统调用自陷,那它会根据指令的具体值,找到在系统调用表中的对应元素,进而设置参数并调用内核实现处理。

参数说明:在进入到本函数后,栈参数区分布如图12-4所示:

所以dev是当前PS5位的值。

sp是用户栈指针。



12-4  进入trap函数后,栈参数区分布图

r1R1

nps是当前PS的值。

r0是自陷前寄存器R0的值。

pc是返回PC

ps是自陷前PS值。

/*

  * Location of the users’ stored

  * registers relative to R0.

  * Usage is u.u_ar0[XX].

  */

 #define    R0   (0)

 #define    R1   (-2)

 #define    R2   (-9)

 #define    R3   (-8)

 #define    R4   (-7)

 #define    R5   (-6)

 #define    R6   (-3)

 #define    R7   (1)

 #define    RPS (2)

 

#define    EBIT    1/* user error bit in PS: C-bit */

 #define    UMODE   0170000 /* user-mode bits in PS word */

 #define    SETD    0170011 /* SETD instruction */

 #define    SYS     0104400 /* sys (trap) instruction */

 #define    USER    020     /* user-mode flag added to dev */

---------------------选自光盘文件/usr/sys/ken/trap.c------------------------

1. trap(dev, sp, r1, nps, r0, pc, ps)

2. {

3.        register i, a;

4.        register struct sysent *callp;

 

5.        savfp();

6.        if ((ps&UMODE) == UMODE)

7.               dev =| USER;

8.        u.u_ar0 = &r0;

9.        switch(dev) {

 

10.              /*

11.              Trap not expected.

12.              Usually a kernel mode bus error.

13.              The numbers printed are used to

14.              find the hardware PS/PC as follows.

15.              (all numbers in octal 18 bits)

16.              *address_of_saved_ps =

17.              *(ka6*0100) + aps - 0140000;

18.              *address_of_saved_pc =

19.              *address_of_saved_ps - 2;

20.              */

21.              default:

22.                printf("ka6 = %o\n", *ka6);

23.                printf("aps = %o\n", &ps);

24.                printf("trap type %o\n", dev);

25.                panic("trap");

 

26.              case 0+USER: /* bus error */

27.                i = SIGBUS;

28.                break;

 

29.              /*

30.              If illegal instructions are not

31.              being caught and the offending instruction

32.              is a SETD, the trap is ignored.

33.              This is because C produces a SETD at

34.              the beginning of every program which

35.              will trap on CPUs without 11/45 FPU.

36.              */

37.              case 1+USER: /* illegal instruction */

38.                if(fuiword(pc-2)==SETD && u.u_signal[SIGINS]==0)

39.                    goto out;

40.                i = SIGINS;

41.                break;

 

42.              case 2+USER: /* bpt or trace */

43.                i = SIGTRC;

44.                break;

 

45.              case 3+USER: /* iot */

46.                i = SIGIOT;

47.                break;

 

48.              case 5+USER: /* emt */

49.                i = SIGEMT;

50.                break;

51.              case 6+USER: /* sys call */

52.                u.u_error = 0;

53.                ps =& ~EBIT;      

54.         callp = &sysent[fuiword(pc-2)&077];

55.         if (callp == sysent) { /* indirect */ 

56.               a = fuiword(pc);  

57.               pc =+ 2;          

58.               i = fuword(a);

59.               if ((i & ~077) != SYS)

60.                       i = 077;/* illegal */

61.          callp = &sysent[i&077];

62.          for(i=0; icount; i++)

63.               u.u_arg[i] = fuword(a =+ 2);

64.         } else {

65.                for(i=0; icount; i++) {

66.                     u.u_arg[i] = fuiword(pc);

67.                      pc =+ 2;

68.                }

69.         }

70.        u.u_dirp = u.u_arg[0];

71.        trap1(callp->call);

72.        if(u.u_intflg)

73.             u.u_error = EINTR;

74.        if(u.u_error < 100) {

75.              if(u.u_error) {

76.                    ps =| EBIT;

77.                    r0 = u.u_error;

78.               }

79.               goto out;

80.        }

81.        i = SIGSYS;

82.        break;

 

83.         /*

84.         Since the floating exception is an

85.         imprecise trap, a user generated

86.         trap may actually come from kernel

87.         mode. In this case, a signal is sent

88.         to the current process to be picked

89.         up later.

90.         */

91.         case 8: /* floating exception */

92.          psignal(u.u_procp, SIGFPT);

93.          return;

 

94.         case 8+USER:

95.          i = SIGFPT;

96.          break;

97.         /*

98.         If the user SP is below the stack segment,

99.         grow the stack automatically.

100.  This relies on the ability of the hardware

101.  to restart a half executed instruction.

102.  On the 11/40 this is not the case and

103.  the routine backup/l40.s may fail.

104.  The classic example is on the instruction

105.  *cmp     -(sp),-(sp) 

106.  */

107.  case 9+USER: /* segmentation exception */

108.        a = sp;

109.        if(backup(u.u_ar0) == 0)

110.              if(grow(a))

111.                     goto out;

112.        i = SIGSEG;

113.        break;

114.  }

115.  psignal(u.u_procp, i);

116.out:

117.  if(issig())

118.         psig();

119.  setpri(u.u_procp);

120.}


R0R7是栈上各寄存器地址的相对于&r0的地址偏移,所以R0等于0R1等于-2,而R7(返回PC)等于1,这样就可以使用u.u_ar0[Rx]访问栈上寄存器Rx

这里主要关注第5182行,它是处理系统调用自陷的。

在进入到这部分代码时,之前模式必然是用户模式,因为只有用户程序才可能执行系统调用,所以返回PC必然在用户空间。

53行清除ps的错误位,也就是位0——进位(Carrier)。UNIX通过该位来通知系统调用的用户实现部分,内核实现是否出现错误:如果为1则表示内核实现出现错误,为0则表示没有错误。当然也可以通过返回值(寄存器R0)来传递正确错误信息,但有时并不是那么方便,会可能有混淆。比如使用返回值-1表示错误,但正确的返回值里也可能有-1,这就很难区分了。

54行取出sysenttrap指令的低6位值(系统调用号)对应的元素赋给callp,这是因为目前系统中最多支持64个系统调用,所以trap指令的低6位指示了当前具体是哪一个系统调用(open, read, write等)。之所以pc-2是因为PC指向返回地址——也就是 trap指令的下一条指令地址,因此pc-2就指向trap指令地址,又由于它处于用户空间,所以调用fuiword读取。

如果callp等于sysent,表明系统调用号是0,则这是一个间接调用,真正的trap指令的地址是PC所指向地址的内容,如图12-5所示。如果我们使用trap x来表示系统调用号是xtrap指令,则:



12-5  系统调用号是0时,trap x(x!=0)才是真正的系统调用指令

56行把返回地址的内容——12-5中的addr取到a中,即a=addr,这样第58行就把真正的trap指令,也就是trap x取出到i中。第57行更新返回地址PC,使其指向addr的下一条指令。第59行判断trap x是否是合法的trap指令,产生非法指令的这种情况一般是可能由于用户空间数据遭到破坏或者用户程序本身就是恶意程序,此时赋i为无效自陷指令077,它对应的内核实现将是空函数。

61行取出trap x对应的sysent元素。第6263行的循环把trap x紧邻的参数读取到u.u_arg[5]中,供内核实现callp->call使用,参数的个数就是callp->count。注意u.u_arg[i] = fuword(a =+ 2)中,a =+2是先计算再传给fuword的。

如果系统调用号不是0,则第55行判断不成立,那么pc-2处就是一个直接引用(真的)trap指令(如图12-6所示),所以第6568行直接取其紧邻的参数,当然这里也可以而仿照刚才写为u.u_arg[i] = fuiword(pc=+2);。

70行把trap x指令后的第一个参数即放到u.u_dirp中,因为它通常是缓存或字符串地址(在后面具体的例子中可以看到),而namei等函数会访问u.u_dirp来获得字符串,所以这里不管第一个参数是不是缓存或字符串地址,首先进行赋值,最多多了一次无谓的操作。

在这些都准备好后,第71行调用trap1,使它来调用内核实现函数callp->call,那为什么不在这里直接调用callp->call呢?要阐述清楚这里的原因还是比较麻烦的,用一句话来说就是:它使得在调用callp->call的过程中,当前进程如果收到信号,可以中止当前的处理,直接返回到trap而不是trap1,这样可以在trap的第117118行统一处理该信号,并设置错误标记——系统调用在内核中执行时被信号所打断。trap1函数(注意注释)如下:


12-6  系统调用号x不是0时,当前trap指令就是真正的系统调用指令

函数原型:void trap1(int (*f)())

功能描述:调用函数指针f,并在之前保存返回地址到u.u_qsav,这样如果在调用f的过程中产生信号,当前进程就可以在sleep函数中直接返回到trap(乍听起来似乎有点莫名其妙,在讲完后面的一个例子就清楚了)。

参数说明:f是某系统调用的内核实现函数指针,也就是sysent中记录的某个函数指针。

/*

  * Call the system-entry routine f (out of the

  * sysent table). This is a subroutine for trap, and

  * not in-line, because if a signal occurs

  * during processing, an (abnormal) return is simulated from

  * the last caller to savu(qsav); if this took place

  * inside of trap, it wouldn’t have a chance to clean up. 

  *

  * If this occurs, the return takes place without

  * clearing u_intflg; if it’s still set, trap

  * marks an error which means that a system

  * call (like read on a typewrite) got interrupted

  * by a signal.

  */

1. trap1(f)

2. int (*f)();

3. {

 

4.        u.u_intflg = 1;

5.        savu(u.u_qsav);

6.        (*f)();

7.        u.u_intflg = 0;

8.}

savu保存trap1的返回地址(也就是trap72行的地址。事实上保存的是栈地址,但由于栈空间包含了返回地址,所以相当于保存了返回地址,这里为了叙述上的简便)到u.u_qsav中,这样以后谁再调用aretu(u.u_qsav)后,就可以直接跳转到trap的第72行了。

这里举一个实例说明。假如进程A调用read从电传终端读取用户输入,read调用trap  0来间接执行trap 3,引起自陷。这样在trap调用trap1时,入参fsysent中的&read(注意不是刚才A调用的read,而是第8.3.4节中的read函数)。trap1在第6行调用read时,read会调用klread,假设当前缓存t_canq中并没有字符或者用户还没输入回车,则进程Acanon中调用sleep挂起,优先级是10(该过程细节请参见第9章的内容)。

-----------------------选自光盘文件/usr/sys/ken/slp.c-----------------------

1. sleep(chan, pri)

2. {

3.  register *rp, s;

 

4.  s = PS->integ;

5.  rp = u.u_procp;

6.  if(pri >= 0) {

7.          if(issig())

8.                      goto psig;

9.          spl6();

10.                rp->p_wchan = chan;

11.                rp->p_stat = SWAIT;

12.                rp->p_pri = pri;

13.                spl0();

14.                if(runin != 0) {

15.                       runin = 0;

16.                  wakeup(&runin);

17.           }

18.                swtch();   

19.                if(issig())

20.                       goto psig;

21. }

22. else {

23.              spl6();

24.              rp->p_wchan = chan;

25.              rp->p_stat = SSLEEP;

26.              rp->p_pri = pri;

27.              spl0();

28.              swtch();

29.  }

30.  PS->integ = s;

31.  return;

 

  /*

   * If priority was low (>=0) and

   * there has been a signal,

   * execute non-local goto to

   * the qsav location.

   * (see trap1/trap.c)

   */

32.       psig:

33.   aretu(u.u_qsav);

34. }

更准确一点,进程A挂起在sleep的第18行,若被唤醒,A从第19行恢复执行。

这时假如用户输入了退出字符CQUIT,由于在进入到trap函数后,中断被打开(参见第7章),则触发中断服务函数klrint调用ttyinputttyinput的第12行会向进程p1发送SIGQIT信号:signal(tp, c==CINTR? SIGINT:SIGQIT)signal函数会唤醒p1。这样在下一次p1获得CPU后,它从sleep的第19行开始执行,它调用issig判断当前进程是否收到信号,如果是,则跳转到第32行,调用aretu(u.u_qsav)返回到trap函数中,而不是canon。那么为什么这里需要直接返回到trap中呢?因为既然信号的优先级是很高的,它应该优先被处理,既然用户已经输入了退出符,则表明他想退出当前终端,所以再回到canon中处理是没有必要的,当然回到trap1也没必要了,因为对信号的处理部分在trap函数中。因此,在进程读取终端输入而控起时,用户输入CQUIT退出符的情形如图12-7所示。



12-7  在进程读取终端输入而挂起时,用户输入CQUIT退出符的情形

这里之所以trap1中调用savu保存栈指针而不是简单的保存返回地址,也是为了在sleep中调用aretu时,栈指针恢复成trap1中的值,这样就相当于在trap1中返回,否则就会引起栈指针混乱,如图12-8所示。

12-8  sleep调用aretu时的栈指针改变示意图

让我们回到trap函数的第72行,这时有两种情形:

1trap1函数在执行callp->call过程中,收到信号,正如图12-8所示。

2trap1函数正常执行完callp->call后返回。

如果是情形1,则u.u_intflg1,所以第73行设定错误码u.u_error;如果是情形2,则u.u_intflg0

74行判断u.u_error是否小于100(包括等于0的正确情况),这里主要是为了把EFAULT(106) 排除在外,因为EFAULT是一个比较致命的错误,它表示待访问地址无效或者内存读写出错(这通常由无效地址引起,也就是我们常见的非法地址访问,会引起第5章中所讲的内存管理违例),它可能在physioiomovepassccpassuchar中被设。在产生EFAULT错误时,第81行会设定一个SIGSYS信号,并在第115行把它传送给当前进程,如果程序员没有设定它对应的处理函数,则系统会强制当前进程退出,并且产生一个core文件,这就是我们熟悉的coredump

如果u.u_error小于100且不为0,则是非致命错误,所以在第76行设定PSW进位位(Carrier)的值为1,这样返回到用户实现中后,它就可以判断判断该位而知道当前产生了错误。同时把u.u_error赋给栈上的r0变量值,以便也带给用户实现,因为u.u_error位于内核空间,用户实现是无法直接访问它的。最后跳转到第117行。

117行判断当前进程是否收到信号,如果收到,则调用psig处理它,最后调用setpri设定当前进程优先级,关于这一点的原因在第5章中已经讲过,这里就不再重复。

至此系统调用的处理框架就已经讲完了,下面讲讲各个系统调用的具体实现。


上一章    UNIX可执行文件                       目录                                             下一章 进程间通信


阅读(13614) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~