三、运行中的系统任务和用户进程
首先,进程之间最重要的是进程通信,先来分析一下通信是如何实现的。
进程要与其它进程通信均是调用send_recv函数,那我们就来看一下这个函数,节自kernel/proc.c:
/***send_recv调用了sendrec(syscall.asm),sendrec用int INT_VECTOR_SYS_CALL进行系统调用陷入内核,这时到了sys_call(kernel.asm)中执行,在sys_call中又通过sys_call_table(global.c)初始化的表项进入sys_sendrec(位于本文件)执行,它分别调用msg_send(位于本文件)和msg_receive(位于本文件)来进行进程间消息的传递***/
PUBLIC int send_recv(int function, int src_dest, MESSAGE* msg)
{
int ret = 0;
if (function == RECEIVE)
memset(msg, 0, sizeof(MESSAGE));
switch (function) {
case BOTH:
ret = sendrec(SEND, src_dest, msg);
if (ret == 0)
ret = sendrec(RECEIVE, src_dest, msg);
break;
case SEND:
case RECEIVE:
ret = sendrec(function, src_dest, msg);
break;
default:
assert((function == BOTH) ||
(function == SEND) || (function == RECEIVE));
break;
}
return ret;
}
中间涉及到的处理流程在注释中都已说明,就不再详细地贴出每个函数分析了,有兴趣自己看书吧。
接下来,依次分析各个系统任务和用户进程,用户进程很简单,而且不属于操作系统实现,因此重点放在系统任务上。
先来看看系统任务和用户进程都有哪些,节自kernel/global.c:
PUBLIC struct task task_table[NR_TASKS] = {
/* entry stack size task name */
/* ----- ---------- --------- */
{task_tty, STACK_SIZE_TTY, "TTY" },
{task_sys, STACK_SIZE_SYS, "SYS" },
{task_hd, STACK_SIZE_HD, "HD" },
{task_fs, STACK_SIZE_FS, "FS" },
{task_mm, STACK_SIZE_MM, "MM" }};
PUBLIC struct task user_proc_table[NR_NATIVE_PROCS] = {
/* entry stack size proc name */
/* ----- ---------- --------- */
{Init, STACK_SIZE_INIT, "INIT" },
{TestA, STACK_SIZE_TESTA, "TestA"},
{TestB, STACK_SIZE_TESTB, "TestB"},
{TestC, STACK_SIZE_TESTC, "TestC"}};
1、task_tty
操作系统将tty作为特殊的文件对待,从tty读入和输出到tty由该系统任务进行处理。它负责从键盘缓冲区中拿数据送到请求进程(通过文件系统task_fs中介),或从请求进程拿数据送到显存显示(通过文件系统task_fs)。
节自kernel/tty.c:
/***处理对tty的输入输出***/
PUBLIC void task_tty()
{
TTY * tty;
MESSAGE msg;
init_keyboard();
for (tty = TTY_FIRST; tty < TTY_END; tty++)
init_tty(tty);
select_console(0);
while (1) {
for (tty = TTY_FIRST; tty < TTY_END; tty++) {
do {
tty_dev_read(tty);
tty_dev_write(tty);
} while (tty->ibuf_cnt);
}
send_recv(RECEIVE, ANY, &msg);
int src = msg.source;
assert(src != TASK_TTY);
TTY* ptty = &tty_table[msg.DEVICE];
switch (msg.type) {
case DEV_OPEN:
reset_msg(&msg);
msg.type = SYSCALL_RET;
send_recv(SEND, src, &msg);
break;
/***直接给tty发送read和write消息的只有文件系统,因此msg->source只能是文件系统,其它的进程想要读写tty是通过文件系统这个中介来发送消息的,实际需要读和写的进程是msg->PROC_NR指定的进程。注意这点,跟一般的进程间通信是有区别的。***/
case DEV_READ:
/***tty_do_read会根据msg设置tty的参数,tty_req_buf、tty_trans_cnt等,然后进入下次循环的开始将会在for循环中处理该msg对应的进程。在该函数完成时会将msg中PROC_NR对应的进程阻塞,进行下一次循环中执行tty_dev_write中,会将缓冲区中的指令逐字节复制到PROC_NR对应的进程的缓冲区中,完成一条指令的输出,即遇到\n时,会给PROC_NR对应的被阻塞的进程发送继续执行的msg。***/
tty_do_read(ptty, &msg);
break;
case DEV_WRITE:
/***将进程缓冲区中的数据写出到显存,然后返回***/
tty_do_write(ptty, &msg);
break;
case HARD_INT:
/***该信号是为了周期性地唤醒tty任务,避免它阻塞在前面的send_recv中。如果一个进程要求从tty输入,而人的输入间隔很大,这就造成一条命令没有一次性输入完