是时候有一个像样的打印工具了,在kernel\chr_drv目录下添加tty_io.c,将tty_table的定义,copy_to_cooked和do_tty_interrupt都移到这个文件中。
添加get_fs_byte函数是读取fs段内addr地址处(fs:[addr])一字节的数据,%0是返回的 寄存器变量_v;%1是内存地址addr,定义一个寄存器变量_v,该变量将被保存在一个寄存器中,以便提高访问和操作效率。
函数的主要作用是读取buf中的数据并put进tty->write_q队列,然后调用tty->write(tty)输出。
#include <linux/head.h>
#include <linux/sched.h>
#include <linux/tty.h>
#include <asm/system.h>
struct tty_struct tty_table[] = {
{
con_write,
{0,0,0,0,""}, /* console read-queue */
{0,0,0,0,""}, /* console write-queue */
{0,0,0,0,""} /* console secondary queue */
}
};
void copy_to_cooked(struct tty_struct * tty)
{
signed char c;
if(!EMPTY(tty->read_q) ){
GETCH(tty->read_q,c);
PUTCH(c,tty->write_q);
tty->write(tty);
}
}
void do_tty_interrupt(int tty)
{
copy_to_cooked(tty_table+tty);
}
inline unsigned char get_fs_byte(const char * addr)
{
unsigned register char _v;
__asm__ ("movb %%fs:%1,%0":"=r" (_v):"m" (*addr));
return _v;
}
int tty_write(unsigned channel, char * buf, int nr)
{
static cr_flag=0;
struct tty_struct * tty;
char c, *b=buf;
if (channel>2 || nr<0) return -1;
tty = channel + tty_table;
while(nr>0 && !FULL(tty->write_q)){
c=get_fs_byte(b);
if(c=='\n')
c=13;
b++; nr--;
PUTCH(c,tty->write_q);
}
tty->write(tty);
return (b-buf);
}
头文件tty.h中添加
#define LEFT(a) (((a).tail-(a).head-1)&(TTY_BUF_SIZE-1))
#define FULL(a) (!LEFT(a))
void con_write(struct tty_struct * tty);
include中添加stdarg.h头文件
#ifndef STDARG_H
#define STDARG_H
typedef char *va_list;
#endif /* STDARG_H */
%4是base,eax中存放的是n,所以divl %4相当于%eax = n/base; %edx = n%base;
输出寄存器列表:"=a" (n),"=d" (__res),说明代码运行结束后将eax的值放到n中,将%ebx中的值放到__res中。
最后一行的__res;说明该表达式的输出值为__res
所以n中保存商,返回值为余数
C语言里规定:16bit程序中,返回值保存在ax寄存器中,32bit程序中,返回值保持在eax寄存器中,如果是64bit返回值,edx寄存器保存高32bit,eax寄存器保存低32bit
number是将整数num转换为相应的16进制数的字符串
#include <stdarg.h>
#define do_div(n,base) ({ \
int __res; \
__asm__("divl %4":"=a" (n),"=d" (__res):"0" (n),"1" (0),"r" (base)); \
__res; })
static char * number(char *str, int num, int base)
{
char c,sign,tmp[36];
int i=0;
const char *digits="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
if (num==0)
tmp[i++]='0';
else while (num!=0)
tmp[i++]=digits[do_div(num,base)];
while(i-->0)
*str++ = tmp[i];
return str;
}
typedef char *va_list;
int vsprintf(char *buf, const char *fmt, va_list args)
{
int len;
int i;
char * str;
for (str=buf ; *fmt ; ++fmt) {
if (*fmt != '%') {
*str++ = *fmt;
continue;
}
*fmt++;
switch (*fmt) {
case 'x':
//flags |= SMALL;
case 'X':
str=number(str,*((int *)args), 16);
break;
case 'd':
case 'i':
//flags |= SIGN;
case 'u':
str = number(str,*((int *)args), 10);
break;
}
}
*str = '\0';
return str-buf;
}
在kernel文件夹下添加printk.c
C调用约定:函数后面的参数先入栈,并且由调用者清理堆栈。所以fmt最后入栈,&fmt+4为fmt后面一个参数的地址 。。。。。
#include <linux/kernel.h>
#include <stdarg.h>
static char buf[1024];
extern int vsprintf(char * buf, const char * fmt, va_list args);
int printk(const char *fmt, ...)
{
int i;
va_list args = (va_list)((char*)(&fmt)+4);
i=vsprintf(buf,fmt,args);
__asm__("push %%fs\n\t"
"push %%ds\n\t"
"pop %%fs\n\t"
"pushl %0\n\t"
"pushl $buf\n\t"
"pushl $0\n\t"
"call tty_write\n\t"
"addl $8,%%esp\n\t"
"popl %0\n\t"
"pop %%fs"
::"r" (i):"ax","cx","dx");
return i;
}
kernel下的Makefile
# Makefile for the simple example kernel.
AS =as
LD =ld
LDFLAGS = --oformat binary -N -e start -Ttext 0x0
CC =gcc
CFLAGS = -I../include -fno-stack-protector
.c.s:
$(CC) $(CFLAGS) \
-S -o $*.s $<
.s.o:
$(AS) -c -o $*.o $<
.c.o:
$(CC) $(CFLAGS) \
-c -o $*.o $<
OBJS = asm.o kliba.o System_call.o chr_drv/chr_drv.o Traps.o sched.o printk.o vsprintf.o
kernel.o:$(OBJS)
$(LD) -r -o kernel.o $(OBJS)
sync
chr_drv/chr_drv.o:
(cd chr_drv; make)
clean:
rm -f *.o *.s
(cd chr_drv;make clean)