sagalinux学习之/kernel目录

这篇分析有点长了,慢慢看~~。

请随手携带一本《32位微机原理》宝典,以备查询。

这是kernel目录下的Makefile,很简单

CFLAGS = -I../include/ -c -Wall -fno-stack-protector -o

all:	kernel.o page.o idt.o printk.o keyboard.o irq.o i8259.o console.o

clean:
	rm -f *.o *~

kernel.o:
	gcc ${CFLAGS} kernel.o kernel.c

page.o:
	gcc ${CFLAGS} page.o page.c

idt.o:
	gcc ${CFLAGS} idt.o idt.c

printk.o:
	gcc ${CFLAGS} printk.o printk.c

keyboard.o:
	gcc ${CFLAGS} keyboard.o keyboard.c

irq.o:
	gcc ${CFLAGS} irq.o irq.c

i8259.o:
	nasm -felf i8259.s

console.o:
	gcc ${CFLAGS} console.o console.c


我们先来看目录下的kernel.c文件,主函数就在这里,也就是sagalinux的“内核"核心。。。。我们从主函数发散出去逐个研究。

#include "kernel/idt.h"
#include "kernel/page.h"
#include "kernel/kernel.h"
#include "kernel/keyboard.h"
#include "kernel/console.h"

int main(void)
{
  //  int i;
  
  char* message = "\nWelcome!\n";
  char* kernel_release = "Kernel on an i386\n\n";
  char* fake_login = "[LinuxSaga]# ";

  page_init();//初始化页机制,开启分页,映射了0~16M的物理页
  trap_init();//初始化陷阱门
  keyboard_init();//初始化键盘
  con_clear();//清除终端的所有字符

  printk("%s",message);
  printk("%s",kernel_release);
  printk("%s",fake_login);

  //  i = 3 / 0;
  while(1);
  return 0;
}


简单吧,下面是每一个初始化函数的详细

/kernel/page.c	用于初始化页表,页表中描述一个4K的物理页的一个描述项叫做PTE,PTE大小为4B,
如果PTE在一个4K的物理页中连续存放,形成一个表,我们用PDE来描述这个PTE表,一个PDE大小为4B。
就是这一回事。
#include "kernel/page.h"//一些宏定义
#include "asm/system.h"

//初始化页表
void page_init()
{

  int pages = NR_PGT;//PTE表的描述项PDE的个数
                     // NR_PGT 为4,创建4个PTE表的描述项(PDE),每个PDE占4B,描述了一个PTE表,每个PTE描述了一个4K的页,这样总共可以访问4*1024*4K即16M大小的内存
		
  unsigned int page_offset = PAGE_OFFSET;//#define PAGE_OFFSET (unsigned int)0x2000 (8K)


                               //PDE项的开始物理地址,PGD_BASE=0x1000(4K),也就是说PDE位于物理内存的第二个页框内(每个page为4K)
  unsigned int* pgd = PGD_BASE;//#define PGD_BASE (unsigned int*)0x1000 PGD_BASE 是一个指针,指向unsigned int类型,指针的值是0x1000
			       //赋值之后pgd指向了0x1000这个内存地址,注意pgd这个指针变量占用4B,			

  
  unsigned int phy_add = 0x0000;// 从物理地址的最低端开始建立PTE项的映射,映射内存地址0~16M

  // PTE表从物理内存8K处开始,每个PTE表有1024项,占满一个物理页。共有4个PTE表,且连续存放
  unsigned int* pgt_entry = (unsigned int*)0x2000; //PTE表的开始地址(8K)
  
  // 填充页目录表PDE项
  // 这里依次创建4(pages=4)个页目录表PDE,一个PDE就是一个PTE表的描述项
  //(每个PTE表有1024项,1024*4B=4K,占一个物理页)
  while (pages--)
    {
      *pgd++ = page_offset |PTE_USR|PTE_RW|PTE_PRE;
      page_offset += 0x1000;//继续描述下一个PTE表
    }

  //PDE表,有四项PDE:0x1000~0x2000
  //PTE表:共四个表,位于0x2000~0x3000, 0x3000~0x4000, 0x4000~0x5000, 0x5000~0x6000

  pgd = PGD_BASE;//恢复pgd为PDE表基地址0x1000,4K

  // 在页表中填写页到物理地址的映射关系,映射了16M大小的物理内存 即0~16M的物理内存
//共映射了0x1000(4K)个PTE,每个PTE指向一个4K的物理页,共4K*4K=16M内存
  //这里因为PTE表是连续的,所以可以直接给所有的PTE赋值,(*pgt_entry++)
  while (phy_add < 0x1000000) {//0x1000000=16M
    *pgt_entry++ = phy_add |PTE_USR|PTE_RW|PTE_PRE;//下一个PTE项,每个PTE项描述了一个物理页
    phy_add += 0x1000;//下一个物理页,共有4K个PTE,每个PTE一次赋值,共循环了4K次
  }
	//嵌入式汇编语法
//__asm__(assembler template 
//	: output operands /* optional *// 
//	: input operands /* optional */ 
//	: list of clobbered registers /* optional */ 
//);
  // 启用页机制, cr3指向页目录根PDE表的开始
  __asm__ __volatile__ ("movl %0, %%cr3;"//pgd-->cr3,PDT基地址
			"movl %%cr0, %%eax;"
			"orl  $0x80000000, %%eax;"
			"movl %%eax, %%cr0;"	//eax-->cr0,cr0最高位置1,用于开启分页
			:
			:"r"(pgd)//%0代表pgd
			:"memory","%eax");
//Invalidate TLB before and after setting up page tables
//使TLB无效,在多核处理器上,避免其他cpu引用到无效的页表
  invalidate_tlb();
}

回到主函数中下一个trap_init()函数,位于./kernel/idt.c中

#include "kernel/idt.h"
#include "kernel/kernel.h"
#include "asm/system.h"

//x86的描述符类型:存储段描述符;系统段描述符;门描述符

extern void __irq1(void);//声明了外部的中断函数

//idt包含三种类型的门描述符:任务门描述符,中断门描述符,陷阱门描述符。共同点都是CPU暂停当下过程,转而去执行其他过程。

//中断具体过程如下:
//			0. cpu得到中断向量
//			1. cpu由idtr寄存器中的基址和界限得到idt在物理内存中的信息
//			2. 基址+8*中断向量=中断描述符表中的中断门或陷阱门
//			3. cpu将某一门描述符中的选择子送cs,根据ti位确定从gdt或ldt中选择一个段描述符,即确定了一个段
//			4. 根据门描述符中的偏移确定段中中断服务程序的具体入口地址。

//intel x86支持256种中断,编号为中断向量0~255,中断向量0~32为intel设定的中断,又称为异常,由CPU内部同步产生,比如中断向量0为除法错误。
//中断向量y和外部中断号x的关系 默认为y=x+32
void trap_init()
{
  int i;
  idtr_t idtr; //idtr 用于指示中断描述符表idt的基址和界限 6B
  ////设置陷阱门
  set_trap_gate(0, (unsigned int)÷_error);
  set_trap_gate(1, (unsigned int)&debug);
  set_trap_gate(2, (unsigned int)&nmi);
  set_trap_gate(3, (unsigned int)&int3);
  set_trap_gate(4, (unsigned int)&overflow);
  set_trap_gate(5, (unsigned int)&bounds);
  set_trap_gate(6, (unsigned int)&invalid_op);
  set_trap_gate(7, (unsigned int)&device_not_available);
  set_trap_gate(8, (unsigned int)&double_fault);
  set_trap_gate(9, (unsigned int)&coprocessor_segment_overrun);
  set_trap_gate(10,(unsigned int) &invalid_TSS);
  set_trap_gate(11, (unsigned int)&segment_not_present);
  set_trap_gate(12, (unsigned int)&stack_segment);
  set_trap_gate(13, (unsigned int)&general_protection);
  set_trap_gate(14, (unsigned int)&page_fault);
  set_trap_gate(15, (unsigned int)&coprocessor_error);
  set_trap_gate(16, (unsigned int)&alignment_check);
			//(unsigned int)把指针强制转换成unsigned int类型

  for (i = 17;i<32;i++)//保留中断向量
    set_trap_gate(i, (unsigned int)&reserved);
  
  //设置外部中断
  set_trap_gate(33, (unsigned int)&__irq1);//irq1 为键盘中断,中断向量为1+32=33(中断号和中断向量的关系由主板物理连接决定)

  //设置中断描述符表寄存器
  idtr.limit = 34*8; //0~33共34个中断
  idtr.lowerbase = 0x0000;//从内存地址0x0开始
  idtr.higherbase = 0x0000;
  cli();//关中断cf <--0
  __asm__ __volatile__ ("lidt (%0)"//装载IDT的信息至idtr寄存器
			::"p" (&idtr)); 
  sti();//开中断cf <--1
}

void set_trap_gate(int vector, unsigned int handler_offset)//在IDT中填入8B的门描述符
{				//#define IDT_BASE      0x0000
  trapgd_t* trapgd = (trapgd_t*) IDT_BASE + vector;	//IDT_BASE+8*vector,在IDT中相应位置存放8B的陷阱描述符
  trapgd->loffset = handler_offset & 0x0000FFFF;	//存储函数地址低16位
  trapgd->segment_s = CODESEGMENT;			//这里选择子是0x08,指示的是内核代码段
  trapgd->reserved = 0x00;
  trapgd->options =  0x0F | PRESENT | KERNEL_LEVEL;//存在的,特权级为0的段
  trapgd->hoffset = ((handler_offset & 0xFFFF0000) >> 16);//存储函数地址高16位
  
}

void set_int_gate(int vector,  unsigned int handler_offset)//中断门
{				
  intgd_t* intgd = (intgd_t*) IDT_BASE + vector;
  intgd->loffset =  handler_offset & 0x0000FFFF;
  intgd->segment_s = CODESEGMENT;
  intgd->reserved = 0x0;
  intgd->options =  0x0E | PRESENT | KERNEL_LEVEL;//和set_trap_gate()的不同之处仅在于此,这里是0x0E
  intgd->hoffset = ((handler_offset & 0xFFFF0000) >> 16);  
}

void set_task_gate(int vector, ushort_t tss)		//任务门
{
  taskgd_t* taskgd = (taskgd_t*) IDT_BASE + vector;
  taskgd->reserved = 0x0;
  taskgd->tss = tss;//指向tss段的选择子

  taskgd->reservedd = 0x0;
  taskgd->options = 0x05 | PRESENT | KERNEL_LEVEL;
  taskgd->reserveddd = 0x0;
  
}

// Nooooo... just sleep :)
void sleep(char* message)
{
  printk("%s",message);
  while(1);
}

void divide_error(void)
{
  sleep("divide error");
}
void debug(void)
{
  sleep("debug");
}

void nmi(void)
{
  sleep("nmi");
}

void int3(void)
{
  sleep("int3");
}

void overflow(void)
{
  sleep("overflow");
}

void bounds(void)
{
  sleep("bounds");
}
void invalid_op(void)
{
  sleep("invalid op");
}

void device_not_available(void)
{
  sleep("device not available");
}

void double_fault(void)
{
  sleep("double fault");
}

void coprocessor_segment_overrun(void)
{
  sleep("coprocessor segment overrun");
}

void invalid_TSS(void)
{
  sleep("invalid TSS");
}

void segment_not_present(void)
{
  sleep("segment not present");
}

void stack_segment(void)
{
  sleep("stack segment");
}

void general_protection(void)
{
  sleep("general protection");
}

void page_fault(void)
{
  sleep("page fault");
}

void coprocessor_error(void)
{
  sleep("coprocessor error");
}

void alignment_check(void)
{
  sleep("alignment check");
}

void reserved(void)
{
  sleep("reserved");
}

idtr_t、trapgd_t、intgd_t、taskgd_t类型就不细说了,就拿本《32位微机原理》,和书上的格式对照一下,就知道了。


注意上面有一个extern void __irq1(void);
是对外部中断函数的声明,函数的实现在/kernel/i8259.s中
下面来看一下这个文件

/kernel/i8259.s

;进入保护模式就是32位了
[BITS 32]
			;这里的中断是外部8259上的中断,两片8259可以提供15个外部中断
extern  irq_handler	;在./include/kernel/irq.h中,是一个函数指针数组:void (*irq_handler[16])();

global	__irq0
global	__irq1
global	__irq2
global	__irq3
global	__irq4
global	__irq5
global	__irq6
global	__irq7
global	__irq8
global	__irq9
global	__irq10
global	__irq11
global	__irq12
global	__irq13
global	__irq14
global	__irq15
	;中断函数
do_irq:
	pop	eax	;外部中断号出栈
	call [irq_handler + 4*eax]	;irq_handler+4*eax,跳到真正的中断处理函数地址,每个地址4B

	mov	al, 0x20		;向8259发送EOI,通知中断结束。
	mov	dx, 0x20
	out	dx, al
	
	popa
	iret

__irq0:				;硬件中断的处理程序,这里的函数地址将会存储在IDT中的门描述符中
	pusha
	mov eax, $0
	push eax
	jmp do_irq

__irq1:				;sagalinux只实现了这个键盘中断,
	pusha
	mov eax, $1
	push eax
	jmp do_irq

__irq2:
	pusha
	mov eax, $2
	push eax	
	jmp do_irq

__irq3:
	pusha
	mov eax, $3
	push eax
	jmp do_irq

__irq4:
	pusha
	mov eax, $4
	push eax
	jmp do_irq

__irq5:
	pusha
	mov eax, $5
	push eax
	jmp do_irq

__irq6:
	pusha
	mov eax, $6
	push eax
	jmp do_irq

__irq7:
	pusha
	mov eax, $7
	push eax
	jmp do_irq

__irq8:
	pusha
	mov eax, $8
	push eax
	jmp do_irq

__irq9:
	pusha
	mov eax, $9
	push eax
	jmp do_irq

__irq10:
	pusha
	mov eax, $10
	push eax
	jmp do_irq

__irq11:
	pusha
	mov eax, $11
	push eax
	jmp do_irq

__irq12:
	pusha
	mov eax, $12
	push eax
	jmp do_irq

__irq13:
	pusha
	mov eax, $13
	push eax
	jmp do_irq

__irq14:
	pusha
	mov eax, $14
	push eax
	jmp do_irq

__irq15:
	pusha
	mov eax, $15
	push eax
	jmp do_irq

再细看究竟键盘中断是怎么实现的,就是按一下键,屏幕上产生一个字符。
我们回到主函数中看下一个函数keyboard_init(),它位于./kernel/keyboard.c中

#include "kernel/irq.h"
#include "kernel/kernel.h"
#include "asm/io.h"
#include "sys/types.h"

static byte shift = 0;//here the variable shift is global; typedef unsigned char byte;

#define LEFTSHIFT 0x2A//make code of leftshift
#define RIGHTSHIFT 0x36//make code of rightshift

//you should know : make code and break code ,which mean press and relax, break code = make code | 0x80
static byte keymap[] = { /* SHIFT OFF */ \
		     ' ',' ', '1', '2', '3', '4', '5', '6', '7', '8',
		     '9', '0', '-', '=', '\b', '\t', 'q', 'w', 'e', 'r',
                      't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',  ' ',
                      'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';',
                      '\'', '~', ' ', '\"', 'z', 'x', 'c', 'v', 'b', 'n',
                      'm', '<', '>', '/', ' ', ' ', ' ', ' ', ' ', //the last character this line is keymap[0x3B]
			 /* SHIFT ON */
		      ' ', ' ', '!', '@', '#', '$', '%', '^', '&', '*',
                      '(', ')', '_', '+', ' ', ' ', 'Q', 'W', 'E', 'R',
                      'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', ' ',  ' ',
                      'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':',
                      '\"', '<', ' ', '"', 'Z', 'X', 'C', 'V', 'B', 'N',
                      'M', '<', '>', '?', ' ', ' ', ' ', ' ', ' '};

//keyboard interrupt process function,when key press or relax ,interrupt occur
void keyboard_handler()//这就是真正的键盘中断处理函数
{			//过程:按下键盘->芯片收到中断信号->将中断号发给cpu->cpu加32得到中断向量->cpu去向量表中找到函数指针__irq1->__irq1跳到do_irq->do_irq通过中断号索引函数指针数组irq_handler[],最终执行中断函数keyboard_handler()。
  byte scancode = inb(0x60); //read code from buffer ,only one byte when the interrupt occur,每次中断只处理缓冲区中的一个字节
  byte ascii = 0;
  
  switch (scancode) {//键盘按下和松开都会产生扫描码
  case LEFTSHIFT:
  case RIGHTSHIFT:
    shift = 0x3B;         // Nothing special.只是按下shift键的话不做任何处理,注意shift是一个全局变量
    break;
  case LEFTSHIFT | 0x80:	//break code,relax the key
  case RIGHTSHIFT | 0x80:	//松开键盘,shift标志清0
    shift = 0;
    break;
  default://the other character
      if (scancode < 0x3B)
	{
	  ascii = keymap[scancode + shift];//由扫描码索引得到字符
	  printk("%c",ascii);
	}
  }
}

void keyboard_init()
{	//    #define KEYBOARD 0x01
  request_irq(KEYBOARD, keyboard_handler);//在中断处理函数数组中写入keyboard_handler函数指针 #define KEYBOARD 0x01
}


request_irq()的实现在./kernel/irq.c中
#include "kernel/irq.h"
#include "asm/io.h"

void enable_irq(int irq)
{
  if (irq > 8)				//从芯片irq>8
    outb(inb(SLAVE)&(~(1<<irq)),SLAVE);//set bit 0 means open,使用inb(SLAVE)读取从芯片的配置,并将irq号位清0,使能此中断。
  else
    outb(inb(MASTER)&(~(1<<irq)),MASTER);  
}

void disable_irq(int irq)
{
  if (irq > 8)
    outb((inb(SLAVE)|irq),SLAVE);//set bit 1 means close,这里应该是1<<irq吧
  else
    outb((inb(MASTER)|irq),MASTER);
}
//设置某中断号的中断处理程序
void request_irq(int irq, void (*handler)())
{
  irq_handler[irq] = handler;// void (*irq_handler[16])(); irq_handler[irq]为函数指针 is a address of function
  enable_irq(irq);	//使能中断
}

看到这里,基本上这个系统最核心的东西就完了,还有一些处理终端界面,输入输出的函数,下面我们再分析。
内存的初始化和中断的过程是sagalinux所描述最多的,这也是计算机中基本而又最重要的知识。




分析到这里,./kernel目录下的文件就只剩下console.c和printk.c了

再回到主函数中看下一个函数con_clear(),它在./kernel/concole.c中
concole.c主要是设置界面终端字符的显示和清空的
代码也很简单,采用了直接写视频缓冲区0xB8000的方式,看一下就好了

#include "kernel/console.h"
#include "sys/types.h"
#include "asm/system.h"
#include "asm/io.h"

#define SCREEN 0xB8000
#define MAXLINES 25
#define MAXCOLUMNS 80
#define NEXTLINE 160

static unsigned int cur_pos = 0;
static unsigned int cur_x,cur_y;
static char* scr_origin = (char*)SCREEN;
static ushort_t video_cont_reg = 0x3d4;   // Video Controller Chip 6845
static ushort_t video_cont_val = 0x3d5;

void set_cursor()
{
  cli();
  outb(14, video_cont_reg);
  outb((cur_pos>>9) & 0xFF, video_cont_val);
  outb(15, video_cont_reg);
  outb(cur_pos>>1 & 0xFF, video_cont_val);
  sti();
}

void con_write(char* message, char attr)
{
  unsigned int pos = cur_x * 2 + cur_y * NEXTLINE;//屏幕上的一个字符需要2B来表示,一行有80个字符需要160B。
  char c;

  while ((c = *message++) != '\0') {
    switch (c) {
      case '\n':
	  pos += (NEXTLINE - pos % NEXTLINE);
	  cur_y++;
	  cur_x = 0;
	  break;
      case '\b':
	pos -= 2;
	cur_x--;
	 *(scr_origin + pos) = ' ';
	break;
    default:
      *(scr_origin + pos) = c;
      *(scr_origin + pos + 1) = attr;
      cur_x++;
      pos += 2;
    }
  }
  cur_pos = pos;
  set_cursor();
}

void con_clear()
{
  int i;
  int scr_buffer = MAXLINES * MAXCOLUMNS * 2; 

  for (i = 0; i < scr_buffer; i+=2)
    *(scr_origin + i) = ' ';

  cur_x = 0;
  cur_y = 0; 
}

void scroll_up()
{
  
}

void scroll_down()
{
  
}

void goto_xy(unsigned int x, unsigned int y)
{
  if (x > MAXCOLUMNS || y > MAXLINES)
    return;
  cur_x = x;
  cur_y = y;
}

再看主函数中的最后一个函数printk();

从这个函数里我们可以学到可变参数函数的原理微笑

#include "stdarg.h"
#include "kernel/kernel.h"
#include "kernel/console.h"

static char buf[1024];		//定义了一个静态全局变量buf缓冲区

void printk(const char* fmt,...)//格式化输出,函数参数是逆序入栈的(从括号末尾处的参数开始进栈),内存最低处存的是字符串指针。
{
  va_list args;
  va_start(args,fmt);
  vsnprintf(buf,sizeof(buf), fmt, args);//将
  va_end(args);
  con_write(buf,0xf);//最后输出的字符串存在buf中,在终端上显示buf。
}

这里的va_list类型在./include/stdarg.h中

你需要理解函数运行时栈的变化。

比如printf("hello,world! %d %d", 1, 2);

栈由高向低生长,从高到低依次存储的是2,1,以及字符串"hello,world! %d %d"的地址。

#ifndef __STRARG_H__
#define __STRARG_H__

typedef char* va_list;//指针类型

// Implicit type conversion occurs in argument passing. In order to use the arguments 
// in an argument list where the parameters are pushed to stack, the sizes of the arguments has to be known.
// In printf you can use %d for char short int and int. Thats because all are converted to int. 
// !!!!!!!! Implicit type conversion occurs if the prototype does not specify a type... the prototype of 
// printf(const char* fmt,...) does not specify a type for arguments so all are promoted...

#define __va_size(type) \
   (((sizeof(type) + sizeof(long) - 1) / sizeof (long)) * sizeof (long))//这里是得到类型的大小,考虑到了内存4字节对齐。如果type是char型的话会返回4。

#define va_start(ap, last) \
   ((ap) = ((char*)&last) + __va_size(last))//得到堆栈中下个元素的指针赋给ap

#define va_arg(ap, type) \
   (*(type*)((ap)+= __va_size(type), (ap) - __va_size(type)))//先让ap指向堆栈中的下一个参数,然后返回上一个参数的指针

#define va_end(va_list) ((void)0)

#endif

再看printk()函数下的vsnprintf()函数,它位于./lib/vsnprintf.c中

#include "stdarg.h"
#include "sys/types.h"

void vsnprintf (char *str, size_t size, const char *fmt, va_list ap)
{
  char *temp;
  char c;
  size_t len = size;
  int i,j,sign = 0;
  int num;
  char numbers[] = {'0','1', '2', '3', '4', '5', '6', '7', '8', '9'};
  char strnumber[] = "           "; // :) max 11 digit with the sign.用来存储数的字符串,带符号
  
  while (*fmt && len)
    {
      if(*fmt != '%')//未遇见格式标识则照常规输出
	{
	  *str++ = *fmt++;
	  len--;
	  continue;
	}
      fmt++;		//接下来是控制字符
      switch (*fmt++){
      case 'c':	
	*str++ = (unsigned char)va_arg(ap,int);
	len--;
	break;
      case 'i':
      case 'd':
	num = va_arg(ap,int);//先将指针指向下一个参数,然后返回堆栈中下一个参数的值,
	if (num < 0)
	  {
	    num *= -1;
	    sign = 1;
	  }
	for(i = 0; i < 11; i++){
	  if(num < 10) 
	    {
	      strnumber[i] = numbers[num];
	      break;
	    }
	  strnumber[i] = numbers[num % 10];
	  num = num / 10;
	}
	if(sign--)//数字在临时缓冲区中逆序存放,比如-1234,存的是"4321-"
	    strnumber[++i] = '-';
      	for(j = i; j >= 0 && len>0; j--,len--)
	  *str++ = strnumber[j];
	break;
	case 's':
	  temp = va_arg(ap,char*);
	  while((c = *temp++) != '\0')
	    *str++ = c;
	  break;
	default:
	  break;
      }  
    }
  *str='\0';
}


void snprintf (char *str, size_t size, const  char  *format,...)
{
  va_list args;
  va_start(args,format);
  vsnprintf(str, size, format, args);
  va_end(args);  

}

void sprintf (char *str, const  char  *format,...)
{
  va_list args;
  va_start(args,format);
  vsnprintf(str, 0xFFFFFFFFUL, format, args);
  va_end(args);  
}

至此,sagaLinux中的所有运行代码分析完毕,我花了一天时间写的这篇东西。。。希望能够对大家有所帮助。
包括汇编,寻址方式,中断原理,内存初始化,描述符表,嵌入汇编,对指针的理解,函数调用堆栈,C语法。又向高手迈出了巨大的一步~~~~。
感谢Faik Yalcin Uygur,是他写的整个代码,而且现在又出了一个改进的版本,提供基本的内存管理和文件系统,有兴趣的同志可以去看看。


我下次准备再分析一个GeekOS来看看。

你可能感兴趣的:(vector,list,video,keyboard,alignment,Numbers)