c语言的printf是系统调用,【第7章】Printf()的实现

终于杀到了上篇的末尾了,现在要再添加一个系统调用: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中打印:

0818b9ca8b590ca3270a3433284dd417.png

用户进程2,3在console1中打印:

0818b9ca8b590ca3270a3433284dd417.png

最后发现显示起始位置会随着用户进程的打印会自动去切换,修改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,结果正常。

至此上篇结束!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值