从零编写linux0.11 - 第三章 printk函数

本文详细介绍如何在 Linux0.11 中实现 printk 函数,包括获取机器系统参数、初始化屏幕相关变量、实现基本打印功能及光标移动、处理特殊字符等功能。

编程环境:Ubuntu Kylin 16.04、gcc-5.4.0

代码仓库:https://gitee.com/AprilSloan/linux0.11-project

linux0.11源码下载(不能直接编译,需进行修改)

本章目标

编写printk函数,实现它的功能。

printk函数的功能与printf相同,区别在于printk在内核态被使用,printf在用户态被使用。首先printk要把格式化字符串转换成普通字符串,如"%s"转换成它指向的字符串,把"%d"转换成数字字符串等等。接着要把字符串显示在屏幕上,这里采用写显存的方式完成。显存是内存中的一块地址空间,在这里写入可打印字符会在屏幕上显示相应的字符。

1.获取机器系统参数

在写显存之前,我们要获取一些相关参数,如显存起始地址,显存大小,显存状态(彩色/单色),特性参数等等,在setup.s中完成这个操作。一些其他的参数以后可能会用到,为了避免反复修改setup.s,也就一起获取吧。

下面是setup.s的部分代码:

INITSEG     equ 0x9000
SYSSEG      equ 0x1000
SETUPSEG    equ 0x9020

start:
    mov ax, INITSEG
    mov ds, ax
    mov ah, 0x03
    xor bh, bh
    int 0x10
    mov [0], dx         ; 光标位置

    mov ah, 0x88
    int 0x15
    mov [2], ax         ; 扩展内存数

    mov ah, 0x0f
    int 0x10
    mov [4], bx         ; 显示页面
    mov [6], ax         ; 显示模式、字符列数

    mov ah, 0x12
    mov bl, 0x10
    int 0x10
    mov [8], ax         ; ??
    mov [10], bx        ; 显示内存、显示状态
    mov [12], cx        ; 特性参数

    mov ax, 0x0000
    mov ds, ax
    lds si, [4 * 0x41]  ; 取中断向量0x41的值,即hd0参数表的地址->ds:si
    mov ax, INITSEG
    mov es, ax
    mov di, 0x0080      ; 传输的目的地址es:di(0x9000:0x0080)
    mov cx, 0x10        ; 共传输0x10字节
    rep
    movsb

    mov ax, 0x0000
    mov ds, ax
    lds si, [4 * 0x46]  ; 取中断向量0x46的值,即hd1参数表的地址->ds:si
    mov ax, INITSEG
    mov es, ax
    mov di, 0x0090      ; 传输的目的地址es:di(0x9000:0x0090)
    mov cx, 0x10
    rep
    movsb

; 检查是否有第二个硬盘,不存在就把第二个表清零
    mov ax, 0x1500
    mov dl, 0x81
    int 0x13
    jc  no_disk1
    cmp ah, 3
    je  is_disk1
no_disk1:
    mov ax, INITSEG
    mov es, ax
    mov di, 0x0090
    mov cx, 0x10
    mov ax, 0x00
    rep
    stosb
is_disk1:
    cli                 ; 保护模式下中断机制尚未建立,应禁止中断
    mov ax, 0x00
    cld
do_move:                ; 将内核从0x10000移动到0x00

获得光标位置等BIOS中断可以看BIOS接口技术参考手册,这段程序首先改变了数据段寄存器的值,获取的参数保存在0x90000开始的地址。这里着重讲一下检查硬盘的操作。

int_0x13_ah_0x15

BIOS中断后,如果CF=1(读取出错)或AH!=3(不是硬盘),则清零第二张硬盘表。BIOS接口技术参考手册中有两个INT 0x13的章节,一个是软盘的,一个是硬盘的,上图是硬盘的内容,请不要搞错了。

读取的参数和保留的内存位置如下图。

参数及其内存位置

之后我们会用C语言指针访问这些地址,使用这些参数。

2.初始化屏幕相关变量

获得了显存起始地址其实就可以开始写字符了,但这样处理还是太粗糙了,无法完成字符打印时光标的移动处理,屏幕滚动,字符显示模式变更等操作。为了实现上述功能,这节就来使用上节的参数完成屏幕的初始化。

以下是console.c的内容(这里面定义的全局变量确实有点多啊)。

#include <linux/tty.h>

#define ORIG_X			    (*(unsigned char *)0x90000)     // 光标列号 
#define ORIG_Y			    (*(unsigned char *)0x90001)     // 光标行号
#define ORIG_VIDEO_PAGE		(*(unsigned short *)0x90004)    // 当前页号
#define ORIG_VIDEO_MODE		(*(unsigned char *)0x90006)     // 显示模式
#define ORIG_VIDEO_COLS 	(*(unsigned char *)0x90007)     // 显示列数
#define ORIG_VIDEO_LINES	(25)                            // 显示行数
#define ORIG_VIDEO_EGA_AX	(*(unsigned short *)0x90008)    // [??]
#define ORIG_VIDEO_EGA_BX	(*(unsigned short *)0x9000a)    // 显示内存和色彩模式
#define ORIG_VIDEO_EGA_CX	(*(unsigned short *)0x9000c)    // 显示卡特性参数

// 定义显示器单色/彩色显示模式类型符号常数
#define VIDEO_TYPE_MDA		0x10	// 单色文本显示
#define VIDEO_TYPE_CGA		0x11	// CGA显示
#define VIDEO_TYPE_EGAM		0x20	// EGA/VGA单色模式
#define VIDEO_TYPE_EGAC		0x21	// EGA/VGA彩色模式

static unsigned char	video_type;		    // 显示模式
static unsigned long	video_num_columns;	// 显示列数
static unsigned long	video_size_row;		// 每行字节数
static unsigned long	video_num_lines;	// 显示行数
static unsigned char	video_page;		    // 初始页面
static unsigned long	video_mem_start;	// 显存起始地址
static unsigned long	video_mem_end;		// 显存结束地址
static unsigned short	video_port_reg;		// 显示控制器索引寄存器端口
static unsigned short	video_port_val;		// 显示控制器数据寄存器端口
static unsigned short	video_erase_char;	// 擦除字符属性与字符(0x0720)

// 以下这些变量用于屏幕卷屏操作
static unsigned long	origin;		// 用于EGA/VGA快速滚屏
static unsigned long	scr_end;	// 用于EGA/VGA快速滚屏
static unsigned long	pos;        // 当前光标对应的显存地址
static unsigned long	x, y;       // 当前光标位置
static unsigned long	top, bottom;// 滚动时顶行行号,底行行号

static unsigned char    attr = 0x07;// 字符属性(黑底白字)

static inline void gotoxy(unsigned int new_x, unsigned int new_y)
{
   
   
	if (new_x > video_num_columns || new_y >= video_num_lines)
		return;
	x = new_x;
	y = new_y;
	pos = origin + y * video_size_row + (x << 1);
}

void con_init(void)
{
   
   
	register unsigned char a;
	char *display_desc = "????";
	char *display_ptr;

	video_num_columns = ORIG_VIDEO_COLS;
	video_size_row = video_num_columns * 2;
	video_num_lines = ORIG_VIDEO_LINES;
	video_page = ORIG_VIDEO_PAGE;
	video_erase_char = 0x0720;              // 擦除字符(0x20显示字符,0x07是属性)
	
	if (ORIG_VIDEO_MODE == 7)			    // 如果为单色显示
	{
   
   
		video_mem_start = 0xb0000;
		video_port_reg = 0x3b4;
		video_port_val = 0x3b5;
		
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值