终于杀到了上篇的末尾了,现在要再添加一个系统调用:Printf()。我们可以在用户进程中调用这个系统调用,首先遇到第一个问题,用户进程要在哪个console上打印呢?我们可以在Printf函数添一个形参,来指定要在哪个console上打印,可是这样与传统的C语言中的printf函数不太一样。那么就选择在PCB中添加一个字段来选择在哪个console上打印。那么修改一下PCB的定义:
Code:
typedefstructs_proc_item
{
Stack_Frame stack_frame;
u16 LDT_Selector;
Descriptor ldts[2];
intticks;
intpriority;
inttty;
}PCB;
然后是这个字段的初始化,第1个用户进程在console0打印,其它2个在console1打印。在Init_PCB函数中修改:
Code:
PCB_Table[0].tty = 0;
User_PCB_Table[0].tty = 0;
User_PCB_Table[1].tty = 1;
User_PCB_Table[2].tty = 1;
最关键的是Printf函数的实现,最关键的技术是可变参数,C语言的调用约定可以实现这个技术,细节书上说得很清楚,只列出代码就好。这里只处理了“%d”的情况。新建一个printf.c文件:
Code:
/*====================================================================
printf.c
====================================================================*/
#include "type.h"
#include "tty.h"
#include "protect.h"
#include "proc.h"
#include "console.h"
#include "proto.h"
intPrintf(constchar*fmt,...)
{
charbuf[256];
chartmp[256];
char*p_next_arg = (char*)((char*)(&fmt) + 4);
char*p_buf = buf;
for(; *fmt ; fmt++)
{
if(*fmt !='%')
{
*p_buf++ = *fmt;
continue;
}
fmt++;
switch(*fmt)
{
case'd':
itoa(tmp,*((int*)(p_next_arg));
Str_Cpy(p_buf,tmp);
p_next_arg += 4;
p_buf += Str_Len(tmp);
break;
default:
break;
}
}
inti = p_buf - buf;
Write(buf,i);
returni;
}
里面用到了itoa函数,Write函数,Str_Cpy函数,Str_Len函数,其中后3个函数是新添加的,在klib.c文件中添加:
Code:
/*======================================================================*
Str_Len
*======================================================================*/
intStr_Len(constchar*p_str)
{
intlen = 0;
while(*p_str++)
{
len++;
}
returnlen;
}
/*======================================================================*
Str_Cpy
*======================================================================*/
voidStr_Cpy(char*p_des,constchar*p_src)
{
while(*p_src && (*p_des++ = *p_src++));
*p_des ='/0';
}
而Write函数则是我们的系统调用,在syscall.asm文件中添加:
global Write
...
Write:
mov eax,1
mov ebx,[esp + 4]
mov ecx,[esp + 8]
int 90h
ret
跟Get_Ticks系统调用一样,使用的是0x90号中断号,则在0x90号中断向量处理程序表中添加要调用的入口函数,在global.c文件中修改:
Code:
Sys_Call_Handler Sys_Call_0x90_Table[64] = {Sys_Call_Get_Ticks,Sys_Call_Write};
Sys_Call_Write函数就是要调用的入口函数,此时处于内核态。此函数定义在tty.c文件中:
Code:
voidSys_Call_Write(constchar*buf,intlen,PCB *p_PCB)
{
while(len--)
{
Out_Char(TTY_Table[p_PCB].console,*buf++);
}
}
我们看到,此函数不断的把buf中的字符拿到相应的console中打印,此处调用了Out_Char函数。
还有一个地方要修改,Sys_Call_Write函数有3个形参,而Sys_Call_Get_Ticks却没有形参,则要Sys_Call_0x90函数:
Sys_Call_0x90:
call Save
push dword [p_Resume_PCB]
sti
push ecx
push ebx
call [Sys_Call_0x90_Table + eax * 4]
add esp,12
mov [esi + 44],eax
cli
ret
我们看到,不管是0x90号的哪个系统调用,都向栈中压了3个参数,这里显然不够优雅,不过先就这样吧。
新写了那么多函数,在proto.h中添加相应的函数原型:
Code:
intPrintf(constchar*fmt,...);
voidWrite(constchar*buf,intlen);
voidSys_Call_Write(constchar*buf,intlen,PCB *p_PCB);
char* itoa(char* str,intnum);
intStr_Len(constchar*p_str);
voidStr_Cpy(char*p_des,constchar*p_src);
最后修改一下3个用户进程的执行体,让它们分别调用Printf():
Code:
voidProc_A()
{
inti = 0x0;
while(1)
{
Printf("%d",i++);
Milli_Delay(100);
}
}
voidProc_B()
{
while(1)
{
Printf("2");
Milli_Delay(100);
}
}
voidProc_C()
{
while(1)
{
Printf("3");
Milli_Delay(100);
}
}
由于添加了printf.c文件,所以要修改MAKEFILE。
make,运行,结果如下图所示:
用户进程1在console0中打印:
用户进程2,3在console1中打印:
最后发现显示起始位置会随着用户进程的打印会自动去切换,修改Out_Char函数:
Code:
if(p_con == &Console_Table[Current_Console])
{
Set_Cursor(p_con->cursor / 2);
Set_Start_Addr(p_con->current_addr / 2);
OK,结果正常。
至此上篇结束!