系统呼叫(通常称为syscalls)是Linux內核与上层应用程式进行交互通信的唯一介面,参见图5-4所示。从对中断机制的說明可知,用戶程式透过直接或间接(透过程式库函数)呼叫中断int 0x80,並在eax寄存器中指定系统呼叫功能号,即可使用內核资源,包括系统硬件资源。 不过通常应用程式都是使用具有标批介面定义的 C 函数库中的函数间接地使用內核的系统呼叫,见图5-19所示。
內核程式透过行程表对行程进行程管理,每个行程在行程表中佔有一项。在Linux系统中,行程表项是一个task_struct任务结构指标。任务资料结构定义在标头档include/linux/sched.h中。有写书上称其为行程控制块PCB(Process Control Block)或行程描述符PD (Processor Descriptor) 。其中保存著用于控制和管理行程的所有信息。主要包括当前执行的状态信息、信号、行程号、父行程号、执行时间累计值、正在使用的档案和本任务的区域描述符以及任务状态段信息。该结构每个栏位的具体含义如下所示。
};
■ long state栏位含有行程的当前状态代号。如果行程正在等待使用CPU或者行程正被执行,那麼state的值是TASK_RUNNING。如果行程正在等待某一事件的发生因而处於空閒状态,那麼state的值就是TASK_INTERRUPTIBLE或者TASK_UNINTERRUPTIBLE。这两个值含义区別在于处于TASK_INTERRUPTIBLE状态的行程能夠被信号喚醒並啟动,而处於TASK_UNINTERRUPTIBLE状态的行程则通常是在直接或间接地等待硬件条件的满足因而不会接受任何信号。TASK_STOPPED状态用於說明一个行程正处於停止状态。例如行程在收到一个相关信号时(例如SIGSTOP、SIGTTIN或SIGTTOU等)或者当行程被另一个行程使用ptrace系统呼叫监控並且控制权在监控行程中时。TASK_ZOMBIE状态用於描述一个行程已经被终止,但其任务资料结构项仍然存在於任务结构表中。一个行程在这些状态之间的转換过程见下面說明。
■ long counter栏位保存著行程在被暂时停止本次执行之前还能执行的时间滴答数,即在正常情況下还需要经过几个系统时钟周期才切換到另一个行程。调度程式会使用行程的counter值来选择下一个要执行的行程,因此counter可以看作是一个行程的动态特性。在一个行程刚被建立时counter的初值等於priority。
■ long priority用於给counter代入初始值。在Linux0.12中这个初值为15个系统时钟週期时间(15个滴答)。当需要时调度程式会使用priority的值为counter代入一个初值,参见sched.c程式和fork.c程式。当然,priority的单位也是时间滴答数。
■ long signal栏位是行程当前所收到信号的点阵图,共32个Bit位,每个Bit位元代表一种信号,信号值二位元偏移值 +l。因此Linux內核最多有32个信号。在每个系统呼叫处理过程的最后,系统会使用该信号点阵图对信号进行预处理。
此后系统设置新任务的代码和资料段基址、限长,並复制当前行程记忆体分页管理的页表。注意,此时系统並不为新的行程分配实际的实体记忆体页面,而是让它共用其父行程的记忆体页面。只有当父行程或新行程中任意一个有写记忆体操作时,系统才会为执行写操作的行程分配相关的独自使用的记忆体页面。这种处理方式称为写时复制(Copy On Write)技术。
Linux 0.12系统中共使用了四种堆栈。一种是系统开机初始化时临时使用的堆栈;一种己进入保护模式之后提供內核程式初始化使用的堆栈,位於內核代码位址空间固定位置处。该堆栈也是后来任务0使用的用戶态堆栈;另一种是每个任务透过系统呼叫,执行內核程式时使用的堆栈;我们称之为任务的內核态堆栈。每个任务都有自己独立的內核态堆栈;最后一种是任务在用戶态执行的堆栈,位於任务(行程)逻辑位址空间近末端处。
每个任务(除了任务0任务1)有自己的64MB位址空间。当一个任务(行程)刚被建立时,它的用户态堆栈指标被设置在其位址空间的靠近末端(64MB顶端)部分。实际上末端部分还要包括执行程式的参数和环境变数,然后才是用戶堆栈空间,见图5-25所示 。应用程式在用戶态下执行时就一直使用这个堆栈。堆栈实际使用的实体记忆则由CPU分页机制确定。由於Linux实现了写时复制功能(Copy on Write),因此在行程被建立后,若该行程及其父行程都沒有使用堆栈,则两者共用同一堆栈对应的实体记忆体页面。只有当其中一个行程执行堆栈写操作(例如push操作)时內核记忆体管理程式才会为写操作行程分配新的记忆体页面。而行程 0和行程1的用戶堆栈比较特殊,见后面說明。
Linux 0.12內核的档案系统採用了1.0版的MINIX档案系统,这是由於Linux是在MINIX系统上开发的,採用MINIX档案系统便於进行交叉编译,並且可以从MINIX中载入Linux分区。虽然使用的是MINIX档案系统,但Linux对其处理方式与MINIX系统不同。主要的区別在於MINIX对档案系统採用单执行绪处理方式,而Linux则採用了多执行绪方式。由於採用了多执行绪处理方式,Linux程式就必须处理多执行绪带来的竞爭条件、锁死等问题,因此Linux档案系统代码要比MINIX系统的复杂得多。为了避免竞爭条件的发生,Linux系统对资源分配进行了严格地检查,並且在內核模式下执行时,如果任务沒有主动睡眠(呼叫sleep( )),就不让內核切換任务。
系统呼叫是內核与外界介面的最高层。在內核中,每个系统呼叫都有一个序列号(在include/unistd 标头档中定义),並且常以巨集的形式实现,应用程式不应该直接使用系统呼叫,因为这樣的话,程式的移植性就不好了。因此目前Linux标準库LSB(Lir Standard Base)和许多其他标準都不允许应用程式直接存取系统呼叫巨集。系统呼叫的有关文档可参见Linux作业系统的線上手冊的第2部分。