首先对于任何一个操作系统, 系统内核必须应该明确一种获取进程信息的办法, pkzd所使用的是一个全局数组struct proc proc[NPROC]。
其中struct proc为进程结构的结构体, NPROC是一个用来确定当前能同时存在的进程的最大数目的宏。struct proc结构的具体代码如下:
struct proc{ int p_exit;
uint p_stat;
uint p_flag;
uint p_sig;
uint p_esp;
uint p_lesp;
pid_t p_pid;
pid_t p_ppid;
pid_t p_pgrp;
uid_t p_uid;
uid_t p_ruid;
gid_t p_gid;
gid_t p_rgid;
uint p_cpu;
int p_pri;
int p_nice;
time_t p_time;
uint p_error;
uint *p_stack;
uint *p_pgdir;
void *p_wchan;
void *p_ttyp;
int p_signal[NSIG];
void *p_cdir;
void *p_rdir;
uint p_start;
uint p_end;
uint p_brk;
uint p_send;
void *p_ofile[NOFILE];
};
操作系统的进程有各种各样的信息, 比如进程id, 打开的文件信息等。有的信息对内核而言不是必须的,而有的则是必须的。
我以进程id和文件描述符表为例做个对比:
p_pid作为每个进程的唯一标识必须一直常驻在系统内核空间,如果设计时将其放在用户空间,那么就会存在进程的pid可能被换出到交换空间的情况,一旦如此那么内核将无法辨别进程。
p_ofile为进程打开的文件描述符表, 每个进程的文件描述符表理论上不需要常驻内核空间。理由是只有当前进程才会去读写文件, 一个没有运行的进程是不会存在读取文件的情况的。
进程id必须存在内核空间的理由有很多,我举一个例子:比如当前进程执行exit系统调用后内核要向当前进程的父进程发送一个信号或者唤醒正在等待子进程终止的父进程, 而如果此时父进程的p_pid已经被换出, 那么内核将无所适从。
pkzd将进程的所有信息都存放在struct proc中,而不去区分信息是否必要常驻内核(这是一种偷懒的方式)。
unix系统的一般设计都会将只对当前进程有用的信息(比如文件描述符表)放在用户空间中, 这个空间一般称为u区。设计u区的最重要的理由是为了节省内存, 而现如今的机器已经都有非常充裕的内存了,所以已经不需要特别明确的把所有可以放在u区的信息都放在u区,我的做法
更是直接省去了u区的实现,当然这是偷懒的。
最后pkzd所使用的语言为c,之所以采用c而非其他语言,首先c和汇编的契合度很高(c中可以调用汇编函数,汇编中又可调用c)。最重要的理由是c对 资源的有力控制,而一般的面向对象的语言并不能做到。另外使用c的理由并非是因为很多人理解的c能操控硬件(一般控制硬件的代码还是用汇编完成的,虽然也 有使用c的例子),根本的理由是c不会像java这样的面向对象的语言肆意的自动分配内存而导致操作系统内核对资源的失控(个人编写系统后的见解)。
我编写的操作系统的源代码的链接:https://sourceforge.net/projects/pkzd/