??xml version="1.0" encoding="utf-8" standalone="yes"?>
(1)cygwin 2009Q集成gcc-4.3.2QrxvtQ非帔R合一般的~程开发,集成iso?8M
(2)i686-elf的cygwin toolchainQ工具链版本Qgcc-4.3.2, binutils-2.19, glibc-2.7Q测试正常工?br>以此工具铄译最新的linux内核2.6.28工作正常Qkernel打一些补丁:
cd linux-2.6.28
sed -i 's/R_386_32/1/' ./scripts/mod/modpost.c
sed -i 's/R_386_PC32/2/' ./scripts/mod/modpost.c
sed -i 's/R_ARM_ABS32/2/' ./scripts/mod/modpost.c
sed -i 's/R_ARM_PC24/1/' ./scripts/mod/modpost.c
sed -i 's/R_MIPS_HI16/5/' ./scripts/mod/modpost.c
sed -i 's/R_MIPS_LO16/6/' ./scripts/mod/modpost.c
sed -i 's/R_MIPS_26/4/' ./scripts/mod/modpost.c
sed -i 's/R_MIPS_32/2/' ./scripts/mod/modpost.c
sed -i 's/STT_COMMON/5/g' ./arch/x86/boot/compressed/relocs.c
sed -i 's/STV_DEFAULT/0/g' ./arch/x86/boot/compressed/relocs.c
sed -i 's/STV_INTERNAL/1/g' ./arch/x86/boot/compressed/relocs.c
sed -i 's/STV_HIDDEN/2/g' ./arch/x86/boot/compressed/relocs.c
sed -i 's/STV_PROTECTED/3/g' ./arch/x86/boot/compressed/relocs.c
sed -i 's/R_386_NONE/0/g' ./arch/x86/boot/compressed/relocs.c
sed -i 's/R_386_32/1/g' ./arch/x86/boot/compressed/relocs.c
sed -i 's/R_386_PC32/2/g' ./arch/x86/boot/compressed/relocs.c
sed -i 's/R_386_GOT32/3/g' ./arch/x86/boot/compressed/relocs.c
sed -i 's/R_386_PLT32/4/g' ./arch/x86/boot/compressed/relocs.c
sed -i 's/R_386_COPY/5/g' ./arch/x86/boot/compressed/relocs.c
sed -i 's/R_386_GLOB_DAT/6/g' ./arch/x86/boot/compressed/relocs.c
sed -i 's/R_386_JMP_SLOT/7/g' ./arch/x86/boot/compressed/relocs.c
sed -i 's/R_386_RELATIVE/8/g' ./arch/x86/boot/compressed/relocs.c
sed -i 's/R_386_GOTOFF/9/g' ./arch/x86/boot/compressed/relocs.c
sed -i 's/R_386_GOTPC/10/g' ./arch/x86/boot/compressed/relocs.c
sed -i 's/R_386_NUM/11/g' ./arch/x86/boot/compressed/relocs.c
sed -i 's/ELF32_ST_VISIBILITY(sym->st_other)/sym->st_other \& 0x03/g' ./arch/x86/boot/compressed/relocs.c
sed -i 's/else rm -f $(@D)\/.tmp_$(@F); exit 1;/else mv -f $(@D)\/.tmp_$(@F) $@;/' ./arch/x86/vdso/Makefile
menuconfig中把netfilterLQ里面有一些文件名一P大小写不一PQipv6也去掉,~译的elf文g正常?br>
上v漕河?br>2009.01.06
]]>
]]>
2 #include <stdlib.h>
3 #include <stdio.h>
4 #include <string.h>
5
6 typedef void (*func_type)(void * obj, int num);
7 class test_t
8 {
9 public:
10 void test_func(int num)
11 {
12 printf("num is %d\n", num);
13 }
14
15 int a;
16 int b;
17
18 };
19
20
21 int main(int argc, char* argv[])
22 {
23 test_t obj;
24 func_type p = &test_t::test_func;
25 for(int i = 100; i < 104; i++)
26 p(&obj, i);
27
28 return 0;
29 }
30
E序比较单,注意它的~译参数Q?br>g++ -Wno-pmf-conversions -O hello.cpp && ./a.out
否则~译不能通过?br>
]]>
链接器把构造函数放在start_ctors和end_ctors之间Q所以我们可以这样做Q?br> for (i = &start_ctors; i < &end_ctors; i++) {
foo = (CONSTRUCTOR_FUNC)*i;
foo(); /* 构造函C能用 cout对象Q这个时候控制台q没有初始化 */
}
引出一个特D需求,全局对象按顺序构造,我们昄无法预知start_ctors表顺序?br>一个可行的Ҏ使用重蝲newQƈ用模板函数封装其执行Q?br>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
struct test_t
{
public:
test_t()
{
printf("construct of test_t()\n");
}
int a;
int b;
};
void * operator new (size_t size, void * place)
{
return place;
}
/* call the default constructor */
template <class object_t> void construct(object_t * ptr)
{
new (ptr) object_t();
}
test_t t;
int main(int argc, char* argv[])
{
construct(&t);
return 0;
}
]]>
下蝲下来一看,只是从pro版切换到lite版,反正我是没)
发个囄看吧Q呵呵:
要是有更好用的pim/gtd软gQ欢q大家提?br>
]]>
]]>
]]>
大致介绍一下:linux环境Qgcc~译器,c++语言Q合式内核Q现在还只是单内核)Q目前目标^台i386/arm。刚刚完成了MM部分Q都是移的linux法QbuddyQslab{)Q整个MM׃个类Q彻底荤菜,呵呵。如果找些h讨论讨论c++倒是好事Q当然不是U无里头的,得先看书Q,但是我c++菜鸟,又不善组l,希望冥冥中得遇大老指点一二?br>
不知道sf大家讉K有没有问题,贴一些主要内容:
版本历史Q?br>
3.1.1版:代号Q?/font>
M目标/功能Qxx
3.0.0版:代号Q?/font>
M目标/功能Q(发布E_pȝQ?/font>
2.1.1版:代号Q?/font>
M目标/功能Q单元测试,bug修复
2.0.0版:代号Q?/font>
M目标/功能Q(完成pȝ核心Q封锁版本)
1.7.1版:代号Q?/font>
M目标/功能Q完成计划的W?章节
1.6.1版:代号QxxQ?/font>2008-02-05 ~ 2008-05-05Q?/font>
M目标/功能Q完成计划的W?章节Qtcp/ipQ?/font>
1.5.1版:代号QxxQ?/font>2008-01-01 ~ 2008-02-04Q?/font>
M目标/功能Q完成计划的W?章节Q文件系l)
1.4.1版:代号QxxQ?/font>2007-10-25 ~ 2007-12-24Q?/font>
M目标/功能Q完成计划的W?章节Q设备驱动)
1.3.1版:代号QxxQ?/font>2007-10-10 ~ 2007-10-24Q?/font>
M目标/功能Q单元测试,bug修复
1.2.1版:代号Q一帘忧梦(2007-07-01 ~ 2007-09-24正在q行中)
M目标/功能Q完成计划的W?Q?Q?Q?章节
(4).jinix-1.2.1.2007-07-19_09.18.22.tar.bz2
完成slab法调研及编?br>
内核初始化流E完善,及相关部分接?框架
(3).jinix-1.2.1.2007-07-15_16.36.41.tar
textioc重?lt;<q算W以支持cout << __func__;
内存理初始化及buddy法完成, 内核对象完善Q启动流E)
(2).jinix-1.2.1.2007-07-12_18.49.44.tar.bz2
内核对象~写Qboot.S->head.SQ初始化汇编文g中全局变量Udbootdata.cpp?br>
内存理cL口编写,以及使用到的相关数据l构从linux中移?br>
(1).jinix-1.2.1.2007-07-03_21.07.57.tar.bz2
某些初始化过的全局变量被编译到bss节去了,被boot.S文g清空Q?br>
调整到新Makefile, 和unixlite怼
使用__attribute__((section(...强制链接?data
(0).jinix-1.2.1.2007-06-30_17.50.21.tar.bz2
控制台显C部分调试完? textio和ostream调试完成
1.pȝ初始?/td>
内核启动
完成
i86g初始化(分页Q?/td>
完成
旉初始?/td>
完成
中断及异常初始化
完成
reboot/halt/poweroff
完成
acpi/apm
未完?/em>
fpu/mmx/sse/sse2
未完?/em>
2.支撑型数据结构和法
hash
完成
队列
完成
链表
完成
fifo/lifo
完成
3.内存理子系l?/td>
buddy法
完成
slab分配?/td>
完成
异常处?/td>
完成
cowQcopy on writeQ?/td>
未完?/em>
demand paging
未完?/em>
守护U程QswapQ页面冲z)
未完?/em>
4.q程及Q务调?/td>
d切换
未完?/em>
fork()
未完?/em>
调度法
未完?/em>
内核U程
未完?/em>
idleU程
未完?/em>
用户U程
未完?/em>
同步机制Q内栔R和原子操?/td>
未完?/em>
5.讑֤驱动
中断机制
未完?/em>
驱动框架及对外接口考虑
未完?/em>
字符讑֤驱动框架
未完?/em>
块设备驱动框?/td>
未完?/em>
|络讑֤驱动框架
未完?/em>
控制台驱动(昑֍Q键盘,鼠标Q?/td>
未完?/em>
dma驱动
未完?/em>
pci驱动
未完?/em>
8029/8139, ne2000|卡驱动
未完?/em>
usb驱动
未完?/em>
6.文gpȝ
proc
未完?/em>
ramdisk
未完?/em>
ext2
未完?/em>
ext3
未完?/em>
vfat
未完?/em>
可执行文件elf加蝲
未完?/em>
动态链接库和内核模?/td>
未完?/em>
7.TCP/IP|络模块
数据链\层(Layer 2Q?/td>
未完?/em>
IP层(Layer 3Q?/td>
未完?/em>
icmp, arp, ip, 路由协议
未完?/em>
tcp, udp
未完?/em>
socket接口
未完?/em>
8.q程间通信IPC
׃n内存
未完?/em>
信号?/td>
未完?/em>
消息队列
未完?/em>
9.posix支持及其?/td>
susv3调研
未完?/em>
lsb调研
未完?/em>
posix兼容及测?/td>
未完?/em>
linux-abi兼容
未完?/em>
shell及常用application
未完?/em>
arm体系架构调研
未完?/em>
10.保留
]]>
可以?/span>http://www.gnome.org/projects/dia上找到它的下载和screenshot?span lang="EN-US">
q个软g基本上不需要学习,使用界面非常单?span lang="EN-US">
下面是示例图Q?/span>
Dia对各cd形的支持是插件Ş式提供,很容易扩展。如果支持图形间链接更好了。不q程序发生异常的情况较多Q最严重的是有时候异怺原来的图形文件打不开。等?/span>1.x版本出来的时候可能会好些吧?/span>
W?span lang=EN-US>9课:pȝ调用和可执行E序 下蝲源代?/font>
声明Q{载请保留Q?span lang=EN-US>
译者:http://www.shnenglu.com/jinglexy
原作者:xiaoming.mo at skelix dot org
MSN & Email: jinglexy at yahoo dot com dot cn
目标
q一节中Q我们来实现从磁盘加载应用程序ƈ执行它,以及l这个应用程序系l调用的能力?span lang=EN-US>
pȝ调用
l过前面一pd的课E,我们基本上徏立了一?span lang=EN-US>OS的各个简单组Ӟ实非常单,没有什么复杂的法和架构。现在再来实C个简单的shell控制Q一般的shell处理程是:getty()打印d提示W,{待用户loginQ然后执?span lang=EN-US>bash。这里,我当然不惛_C?span lang=EN-US>shellQ也不想写什?span lang=EN-US>getty。这一节中Q?span lang=EN-US>skelix从盘加蝲一个可执行E序q执行它Q就像常用的*nix OS一栗?span lang=EN-US>
׃slelix的虚拟内存管理部分还没有做好Q所有的d׃n同一个地址I间。我们采取一个简单的ҎQ程序从盘中加载到地址0x100000Q放?span lang=EN-US>"/"根目录下面?span lang=EN-US>
要注意的是,用户d不能讉K内核地址I间Q所以一些函数如kprintf, print_c{也不能再用了Q实际上是可以的Q因为我们没有实现虚拟内存管理,q里先假定不能用它们)。大多数的操作系l都提供了一?span lang=EN-US>API或系l调用来执行q些函数。现在先d一个系l调用:
09/isr.s
sys_print:
pushl %esi # 背景颜色
pushl %edi # 前景颜色
pushl %ebx # 要打印的字符
cli # 中断门会字节xEFLAGS ?span lang=EN-US>IF位,pȝ调用却不?span lang=EN-US>
call print_c
sti
addl $12, %esp
ret
恨简单,不是吗?上面pȝ调用有三个参敎ͼesi是背景颜Ԍedi是前景颜Ԍebx是要打印的字W。这个系l调用只打印一个字W,起始是用户E序对内?span lang=EN-US>print_c函数的一?span lang=EN-US>wrapper?span lang=EN-US>
理论上说Q用户Q务不能直接?span lang=EN-US>print_c函数Q也不应当知道有q个函数存在。唯一的方法就是用系l调用,以此提供l用户一个可用的接口?span lang=EN-US>DOS 操作pȝ提供?span lang=EN-US>int 0x21作ؓpȝ服务Q?span lang=EN-US>linux 提供int 0x80pȝ调用Q本节中模仿linux?span lang=EN-US>IDT 中添加一个号?span lang=EN-US>0x80 的系l调用?span lang=EN-US>
现在我们只用CIDT 中断?span lang=EN-US>34,所以其余的w可以拿来使用Q但是我们只需?span lang=EN-US>0x80已够。在IDT中徏立一?span lang=EN-US>386 的陷阱门p了,陷阱门和中断门非常类|只是E 位的cd?span lang=EN-US>8替代Qƈ?span lang=EN-US>DPL ҎU设|ؓ3?span lang=EN-US>
09/init.c
static void
sys_call_install(void) {
unsigned long long sys_call_entry = 0x0000ef0000080000ULL
| ((unsigned long long)CODE_SEL<<16);
sys_call_entry |= ((unsigned long long)sys_call<<32)
& 0xffff000000000000ULL;
sys_call_entry |= ((unsigned long long)sys_call) & 0xffff;
idt[SYS_CALL] = sys_call_entry;
}
?span lang=EN-US>SYS_CALL gؓ0x80?span lang=EN-US>
现在我们来看?span lang=EN-US>0x80的陷阱门处理例程Q?span lang=EN-US>
09/isr.s
sys_call:
cmpl $1, %eax
jb
iret
1:
pushal
call *sys_call_table(, %eax, 4)
popal
iret
我尽量做到简单,虽然很多东西没有考虑周全Q这样做可以降低学习成本。该例程判断pȝ调用P卛_储在eax中的|然后查找函数表:sys_call_table?span lang=EN-US>
函数表定义如下:
09/syscall.c
void (*sys_call_table[VALID_SYSCALL])(void) = {sys_print};
VALID_SYSCALL 宏定义ؓ1, of course
pȝ调用试函数
现在我们来做一个用户Q务的pȝ调用试函数Q?span lang=EN-US>
09/color.c
void
color(void) {
int i, j;
for (i=0; i<16; ++i)
for (j=0; j<16; ++j)
__asm__ ("int $0x80"::"S"(i),"D"(j),"b"('X'),"a"(0));
for (;;)
;
}
按例Q最单实现。这个函数打C?span lang=EN-US>“X”字符Q以不同的前景和背景颜色。还有一个问题,怎样把它存储在磁盘上呢,在没?span lang=EN-US>shell的情况下Q否则可以用cp来拷贝了Q。解决的Ҏ是另外写一个程序把E序直接写到盘上:实现一?span lang=EN-US>ghex.cE序来把color可执行程序的内容打印出来Q在内核里面再把可执行文件写到磁盘上?span lang=EN-US>
Makefile也得修改一下:
09/Makefile
all: final.img color
color: color.o
${LD} --oformat binary -N -e color -Ttext 0x100000 -o color $<
上面的命令把color.o文g生成一个二q制可执行文Ӟ入口点在color函数Q代码段逻辑地址?span lang=EN-US>0x100000开始?span lang=EN-US>
Q?span lang=EN-US>color.c׃Z?span lang=EN-US>gcc~译器编译,ld链接Q?span lang=EN-US>
现在把这些二q制数据Q显C命令是Q?span lang=EN-US>./ghex ../colorQ写到磁盘上Q?span lang=EN-US>
09/fs.c
void
install_color(void) {
struct SUPER_BLOCK sb;
char sect[512] = {0};
struct DIR_ENTRY *de = NULL;
int inode = -1;
struct INODE clnode;
unsigned int blk = 0;
unsigned char color[] =
{0x57,0x56,0x53,0x83,0xec,0x08,0xc7,0x44,0x24,0x04,0x00,0x00,
0x00,0x00,0x83,0x
0x00,0x00,0x00,0x00,0x83,0x
0x24,0x04,0x8b,0x
0x00,0x00,0x00,0xcd,0x80,0x89,0xe0,0xff,0x00,0xeb,0xe1,0x8d,
0x44,0x24,0x04,0xff,0x00,0xeb,0xcb,0xeb,0xfe};
sb.sb_start = *(unsigned int *)(HD0_ADDR);
hd_rw(ABS_SUPER_BLK(sb), HD_READ, 1, sect);
memcpy(&sb, sect, sizeof(struct SUPER_BLOCK));
inode = alloc_inode(&sb);
assert(inode > 0);
blk = alloc_blk(&sb);
assert(blk != 0);
clnode.i_block[0] = blk;
hd_rw(blk, HD_WRITE, 1, color);
clnode.i_mode = FT_NML;
clnode.i_size = sizeof color;
iput(&sb, &clnode, inode);
// ?span lang=EN-US>colorE序分配新的inode节点和块
hd_rw(iroot.i_block[0], HD_READ, 1, sect);
de = &((struct DIR_ENTRY *)sect)[2];
strcpy(de->de_name, "color");
de->de_inode = inode;
hd_rw(iroot.i_block[0], HD_WRITE, 1, sect);
// ?span lang=EN-US>colorE序攑ֈ根目?span lang=EN-US>"/"下面
iget(&sb, &iroot, 0);
iroot.i_size = 3*sizeof(struct DIR_ENTRY);
iput(&sb, &iroot, 0);
// 搞定
}
// colorE序已经写到盘上了Q再把它加蝲到内存地址0x100000
09/fs.c
void
load_color(void) {
struct INODE inode;
struct SUPER_BLOCK sb;
char sect[512] = {0};
sb.sb_start = *(unsigned int *)(HD0_ADDR);
hd_rw(ABS_SUPER_BLK(sb), HD_READ, 1, sect);
memcpy(&sb, sect, sizeof(struct SUPER_BLOCK));
iget(&sb, &inode, 1);
/* 只是单的?span lang=EN-US>color加蝲到虚拟地址0x100000Q也是物理地址0x100000Q?span lang=EN-US> */
hd_rw(inode.i_block[0], HD_READ, 1, (void *)0x100000);
}
q记得以前运行的task1_run ?span lang=EN-US>task2_runq两个Q务吗Q它们只是做一些轮在屏幕上打印字W的无聊工作。我们把task1换成执行盘中的colorE序Q?span lang=EN-US>
09/init.c
void
do_task1(void) {
__asm__ ("incb 0xb8000+160*24+2");
load_color();
__asm__ ("jmp 0x100000");
}
最后在check_root()函数中添加一些安装函敎ͼ
09/fs.c
if (! testb(sect, 0)) {
kprintf(KPL_DUMP, "/ has not been created, creating....\t\t\t\t\t ");
if (alloc_inode(&sb) != 0) {
kprintf(KPL_PANIC, "\n/ must be inode 0!!!\n");
halt();
}
iroot.i_block[0] = alloc_blk(&sb);
iput(&sb, &iroot, 0);
hd_rw(iroot.i_block[0], HD_READ, 1, sect);
de = (struct DIR_ENTRY *)sect;
strcpy(de->de_name, ".");
de->de_inode = 0;
++de;
strcpy(de->de_name, "..");
de->de_inode = -1;
hd_rw(iroot.i_block[0], HD_WRITE, 1, sect);
kprintf(KPL_DUMP, "[DONE]");
if (iroot.i_size == 2*sizeof(struct DIR_ENTRY))
install_color();
}
~译Q运行一下。如果一h常的话,应该?span lang=EN-US>colord打印一pd的彩色字W。很有成感吧:Q?span lang=EN-US>
到此为止Q这一pd的课E就l束了,大家可以l箋深入下去Q?span lang=EN-US>linux内核是一个值得学习的好kernelQ?span lang=EN-US>
而且也有一些较好的书籍Q如情景分析Q?span lang=EN-US>ULK2Q?span lang=EN-US>In A Nut Shell{?span lang=EN-US>
学习是一件愉快的事情Q当然也不了挫折和煎熬。态度才是W一位,大家进步:Q?span lang=EN-US>
丁亥q四月廿?span lang=EN-US> 于上体育馆
http://www.shnenglu.com/jinglexy
天有缝
W?span lang="EN-US">8课:内存理 下蝲源代?/span>
声明Q{载请保留Q?span lang="EN-US">
译者:http://www.shnenglu.com/jinglexy
原作者:xiaoming.mo at skelix dot org
MSN & Email: jinglexy at yahoo dot com dot cn
目标
抱歉Q其实还没有实现。在d分配独立?span lang="EN-US">4G地址I间上调试失败了Q现在只使能了分|Ӟ异常。大量的工作未实玎ͼ有兴的同学可以搜烦buddy?span lang="EN-US">slab的相兌料,l典的内存管理算法?span lang="EN-US">
分页
386处理器的内存理单元可以实现d独立地址I间QQ务间内存保护。每个Q务可以拥有独立的4G虚拟地址I间。内存映是内存理很重要的一步,可以分ؓ两部分:分段和分c前面的评中已l讨分段机制了,通过分段可以隔开不同的代码,数据Q堆栈等Q分单元把虚拟地址映射成物理地址Q还可以用来实现虚拟内存Q和盘分区q行交换Q,现在我们来了解一下它?span lang="EN-US">
对于每个dQ我们无法分?span lang="EN-US">4G的物理内存,所以用了一些机制来理内存Q及虚拟内存机制。该机制有处理器的分部分来实现Q首先我们将内存分成一些块Q每个块大小?span lang="EN-US">4kQ通常我们UCZ个页帧。操作系l通过늛录和表来管理这些页帧。页目录是相当于W一U页表,其中的每一再理一个下U页表。(更详l过E请参?span lang="EN-US">intel?span lang="EN-US">IA 32/64手册Q?span lang="EN-US">
当分|制开启时Q处理器把Q务中的虚拟地址转换成物理地址Q步骤如下:
1.查找D选择子在GDT ?span lang="EN-US"> LDT 中的描述W,做一些权限检查,看看能否讉K
2.以描q符中的基址相加늛录基址得到一个线性地址
3.在页表中索引虚拟地址所对应的页表项Q得到页地址
4.查找偏移得到实际物理地址?span lang="EN-US">
如果实际物理不存在Q可能交换到盘中去了)Q则引发异常Q可以在q个异常里面做想要做的事情(加蝲盘中的交换,或?span lang="EN-US">killq个E序Q?span lang="EN-US">Segment FaultQ等{)
处理器用的늛录或者页表,都是?span lang="EN-US">32 位的组成:
늛录项Q?/span>
31 12
11 9 876 5
43 2 1 0
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
?nbsp; 指向表的物理地址 ?用户定义 ?nbsp; X ?nbsp;A? X ?U/S?R/W?P ?o:p>
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
表:
31 12
11 9 87 6 5
43 2 1 0
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
?nbsp; 指向的物理地址 ?用户定义 ?nbsp;X┃D?nbsp;A?X ? U/S?R/W?P ?o:p>
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
从上面可以知道,늛录项和页表项的结构很cMQ下面逐个说明一下其中的域:
Bit 0 |
P |
存在位(presentQ,?span lang="EN-US">0 表示该页帧或表不在内存中。如果访问该将发生异常?span lang="EN-US"> |
Bit 1 |
R/W |
表示表或页帧指向的内存只读Q=0Q,或可写(Q?span lang="EN-US">1Q?span lang="EN-US"> |
Bit 2 |
U/S |
表示表或页帧的权限Q当ҎUؓ0Ӟ只有ring0?span lang="EN-US">2的特权可以讉K它,否则所有的ring3d都可以访问。这个域非常重要?span lang="EN-US"> |
Bits 3, 4, (6), 7, 8 |
X |
Intel 保留位,讄?span lang="EN-US">0p?span lang="EN-US"> |
Bit 5 |
A |
该页是否已访?span lang="EN-US"> |
Bits 9-11 |
用户定义 |
我们使用W?span lang="EN-US">11位,表示该页帧是否被交互到硬盘上?span lang="EN-US"> |
늛录的每一:即页表的物理地址Q它的高20 位地址表示有个的v始地址Q正好和4k寚w?span lang="EN-US">2^20可以表示1M范围Q每个页帧大是4kQ所以可以烦?span lang="EN-US">1M * 4K地址I间。页目录中q有一?span lang="EN-US">D 位,它用来表CZ个页帧是否已修改Q?span lang="EN-US">linux用它来表CZ个页面释放是脏页面,q个位非常有用,当一个页帧交换到盘上后Q如果该q没有被修改Q而且是已l从盘交换出来的,则简单取消以后的交换?span lang="EN-US">
Z逻辑地址转换成物理地址Q逻辑地址被分?span lang="EN-US">3 部分Q?span lang="EN-US">
Bits 31-22 |
늛录项的烦引下标,由它可以得到表的物理地址 |
Bits 21-12 |
表的索引下标Q由它可以得到页帧的物理地址 |
Bits 11-0 |
相对起始地址的偏U?span lang="EN-US"> |
举例来说Q我们有一个逻辑地址Q?span lang="EN-US">0x3E837B0A。前提条ӞCR3寄存器指向的늛录地址?span lang="EN-US"> 0x0005C000Q这个寄存器存储了当前页目录所使用的页帧的物理地址Q通常也叫?span lang="EN-US">
PDBR?span lang="EN-US">
先取它的?span lang="EN-US">10位, 是0x0FAQ由它可以烦引到늛录的W?span lang="EN-US">0x0FA,我们取得q一的|假设得到的地址值是0x0003F000。然后我们取虚拟地址的中?span lang="EN-US">10位,是0x037Q再取出0x0003F000指向的第0x037的|假设?span lang="EN-US">0x0001B000。这个地址是我们要找的虚拟地址对应的物理地址的页帧的起始地址Q最后加上偏Ud|?span lang="EN-US">12位)Q即0xB0AQ得到实际的物理地址是:0x0001BB0A?span lang="EN-US">
相关的知识可以参?span lang="EN-US"> Intel ?span lang="EN-US">IA 32/64手册?span lang="EN-US">
CR3寄存器必d分页机制开启前p载好Q可以?span lang="EN-US">MOV 指o或者在d切换时?span lang="EN-US">TSS中的CR3域的倹{当处理器访问不存在的页帧时Q发生一个异常,CR2 寄存器存引发异常的逻辑地址Q同旉误码也会压入到堆栈中Q错误码格式如下Q?span lang="EN-US">
31
3 2 1 0
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
?nbsp; 未?span lang="EN-US"> ?U/S?R/W?P ?o:p>
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
异常处理例程通常采取如下的步骤:
查找一个空闲的或从盘中将交换出来Q重新设|正的늛录项或页表项的|hTLB。处理器通常保存最q最多访问的늛录或表到一?span lang="EN-US">cache中,以避免每ơ都q行虚拟地址到物理地址的{换,q个cache叫?span lang="EN-US">TLB。只有我们改动了늛录或表,应当刷?span lang="EN-US">TLB。方法很单,是重新加蝲CR3 寄存器?span lang="EN-US">
现在我们来看看代码段Q内存管理通常不了大量的宏定义:
08/include/kernel.h
#define PAGE_DIR
((HD0_ADDR+HD0_SIZE+(4*1024)-1) & 0xfffff000)
物理内存安排Q?span lang="EN-US">IDTQ在0x40000Q,接下来是GDTQ接下来?span lang="EN-US">HD0使用Q然后才是页目录Q?span lang="EN-US">
所以这个宏看v来有炚w?span lang="EN-US">
08/include/mm.h
#define PAGE_SIZE
(4*1024) /*
_度 */
#define PAGE_TABLE (PAGE_DIR+PAGE_SIZE) /*
表物理地址 */
#define MEMORY_RANGE (4*1024)
/* skelix只管?span lang="EN-US">4M 内存暂时 */
08/mm.c
/* 物理内存使用情况的位图表 */
static char mmap[MEMORY_RANGE/PAGE_SIZE] = {PG_REVERSED,
};
void
mm_install(void) {
unsigned int *page_dir = ((unsigned int *)PAGE_DIR);
unsigned int *page_table = ((unsigned int *)PAGE_TABLE);
unsigned int address = 0;
int i;
for(i=0; i<MEMORY_RANGE/PAGE_SIZE; ++i) {
/* 表属性设|ؓ: kernel, r/w, present */
page_table[i] = address|7;
address += PAGE_SIZE;
};
// 上面循环初始化了0~4M对应的所有页表项
page_dir[0] = (PAGE_TABLE|7);
// 늛录项只需要第一个就可以了,因ؓ只有4M内存
for (i=1; i<1024; ++i)
page_dir[i] = 6;
// 其他?span lang="EN-US">1023个页目录设|ؓI,如果q?span lang="EN-US">1024w讄Q可讉K4G内存I间
// 讄0?span lang="EN-US">1M内存为已使用?span lang="EN-US">
for (i=(1*1024*1024)/PAGE_SIZE-1; i>=0; --i)
mmap[i] = PG_REVERSED;
// 因ؓ内核只用C低于1M的内存,所以保留它们,q样׃会被交换出去?span lang="EN-US">
__asm__ (
"movl
%%eax,
%%cr3\n\t" // 加蝲늛录基址到寄存器
"movl
%%cr0, %%eax\n\t"
"orl $0x80000000,
%%eax\n\t"
"movl
%%eax,
%%cr0"::"a"(PAGE_DIR)); // 开启分|ӞCR0的最高位
}
通过mmap位图Q我们可以清楚的知道内存的用情况,q样可以分配空闲页帧了Q如下:
08/mm.c
unsigned int
alloc_page(int type) {
int i;
for (i=(sizeof mmap)-1; i>=0 && mmap[i]; --i)
;
if (i < 0) {
kprintf(KPL_PANIC, "NO MEMORY
LEFT");
halt();
}
mmap[i] = type;
return
i; // q回?span lang="EN-US">
}
void *
page2mem(unsigned int nr)
{ // 转换拟地址
return (void *)(nr * PAGE_SIZE);
}
void
do_page_fault(enum KP_LEVEL kl,
unsigned int
ret_ip, unsigned int ss, unsigned int gs,
unsigned int
fs, unsigned int es, unsigned int ds,
unsigned int
edi, unsigned int esi, unsigned int ebp,
unsigned int
esp, unsigned int ebx, unsigned int edx,
unsigned int
ecx, unsigned int eax, unsigned int isr_nr,
unsigned int
err, unsigned int eip, unsigned int cs,
unsigned int
eflags,unsigned int old_esp, unsigned int old_ss) {
unsigned int cr2, cr3;
(void)ret_ip; (void)ss; (void)gs; (void)fs; (void)es;
(void)ds; (void)edi; (void)esi; (void)ebp; (void)esp;
(void) ebx; (void)edx; (void)ecx; (void)eax;
(void)isr_nr; (void)eip; (void)cs; (void)eflags;
(void)old_esp; (void)old_ss; (void)kl;
__asm__ ("movl %%cr2, %%eax":"=a"(cr2));
__asm__ ("movl %%cr3, %%eax":"=a"(cr3));
kprintf(KPL_PANIC, "\n The fault at %x cr3:%x was
caused by a %s. "
"The accessing
cause of the fault was a %s, when the "
"processor was
executing in %s mode, page %x is free\n",
cr2, cr3,
(err&0x1)?"page-level protection voilation":"not-present
page",
(err&0x2)?"write":"read",
(err&0x4)?"user":"supervisor",
alloc_page(PG_NORMAL));
}
异常函敎ͼ它什么也没有做,知识昄一些错误信息?span lang="EN-US">
现在我们来动态的分配一些内存,我们修改一下Q务函敎ͼ
08/init.c
static void
new_task(unsigned int eip) {
struct TASK_STRUCT *task = page2mem(alloc_page(PG_TASK));
memcpy(&(task->tss), &(TASK0.tss), sizeof(struct
TSS_STRUCT));
task->tss.esp0 = (unsigned int)task + PAGE_SIZE;
task->tss.eip = eip;
task->tss.eflags = 0x3202;
task->tss.esp = (unsigned int)page2mem(alloc_page(PG_TASK))+PAGE_SIZE;
task->tss.cr3 = PAGE_DIR;
task->priority = INITIAL_PRIO;
task->ldt[0] = DEFAULT_LDT_CODE;
task->ldt[1] = DEFAULT_LDT_DATA;
task->next = current->next;
current->next = task;
task->state = TS_RUNABLE;
}
自己分配的Q务数据结构和d堆栈Q是不是很有成就感:Q?span lang="EN-US">
最后在init.c中添加初始化代码Q?span lang="EN-US">
08/init.c
void
init(void) {
char wheel[] = {'\\', '|', '/', '-'};
int i = 0;
idt_install();
pic_install();
mm_install(); /* 初始化函数调?span lang="EN-US"> */
kb_install();
timer_install(100);
set_tss((unsigned long long)&TASK0.tss);
set_ldt((unsigned long long)&TASK0.ldt);
__asm__ ("ltrw
%%ax\n\t"::"a"(TSS_SEL));
__asm__ ("lldt
%%ax\n\t"::"a"(LDT_SEL));
kprintf(KPL_DUMP, "Verifing disk partition
table....\n");
verify_DPT();
kprintf(KPL_DUMP, "Verifing file systes....\n");
verify_fs();
kprintf(KPL_DUMP, "Checking / directory....\n");
verify_dir();
sti();
new_task((unsigned int)task1_run);
new_task((unsigned int)task2_run);
__asm__ ("movl %%esp,%%eax\n\t" \
"pushl %%ecx\n\t"
\
"pushl
%%eax\n\t" \
"pushfl\n\t" \
"pushl
%%ebx\n\t" \
"pushl
$1f\n\t" \
"iret\n" \
"1:\tmovw
%%cx,%%ds\n\t" \
"movw
%%cx,%%es\n\t" \
"movw %%cx,%%fs\n\t"
\
"movw
%%cx,%%gs" \
::"b"(USER_CODE_SEL),"c"(USER_DATA_SEL));
__asm__ ("incb 0xeeffeeff");
/* 试Q?/span>触发一个异?span lang="EN-US"> */
for (;;) {
__asm__ ("movb
%%al, 0xb8000+160*24"::"a"(wheel[i]));
if (i == sizeof wheel)
i = 0;
else
++i;
}
}
异常处理例程中什么也没做Q访问内存出错则LQ?span lang="EN-US">
08/exceptions.c
void
page_fault(void) {
__asm__ ("pushl
%%eax;call do_page_fault"::"a"(KPL_PANIC));
halt();
}
最后把mm.o d?span lang="EN-US"> Makefile ?span lang="EN-US">KERNEL_OBJS 中去Q?span lang="EN-US">
08/Makefile
KERNEL_OBJS= load.o init.o isr.o timer.o libcc.o scr.o
kb.o task.o kprintf.o hd.o exceptions.o fs.o mm.o
一?/span>solaris 内核及应用程序源E序Q?/span>
http://src.opensolaris.org/source 主页
http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/cmd/ls/ls.c 范例Q?/span>ls源程?/span>
二、比较多?/span>gcc资料
三?/span>linux内核交叉引用Q从0.1?.0Q?.0Q?.2Q?.4Q?/span>
http://www.oldlinux.org/lxr/http/source/
四?/span>C 语言常见问题?/span>(中英?/span>)
http://c-faq-chn.sourceforge.net/
五、操作系l相关的一些资料,包括linux源程序分?/span>
六?/span>linus自传
http://www.bookcool.com/online/zhuanji/happyking-gb/0/content.htm
七、嵌入式及OS开发资料(英文Q?/span>
八?/span>Testing and debugging KOSQ英文)
http://kos.enix.org/~d2/snapshots/kos_current/doc/testingen-html
九?/span>the Single UNIX Specification Version 3Q推荐:可作?/span>posix的替代参考资料)
http://www.unix.org/single_unix_specification
十?/span>OS设计参考(英文Q?/span>
http://www.nondot.org/~sabre/os/articles
声明Q{载请保留Q?/span>
译?/span>Q?/span>http://www.shnenglu.com/jinglexy
原作者:xiaoming.mo at skelix dot org
MSN & Email: jinglexy at yahoo dot com dot cn目标 下蝲源程?/span>
q节课我们讲q的内容与操作系l暂无太大关p,但是q些基础函数非常重要Qƈ且在后面的课E中l常用到。这是我们l常听到的内核库。如果你对这些不是很感兴,知道kprintf?span lang="EN-US">c语言里面?span lang="EN-US">print一样工作就行了。简单掠q即可?span lang="EN-US">
C用户库里面的printfh高度可~性,也很Ҏ理解Q相比之?span lang="EN-US">CQ+中的IOq算W就比较难了。ؓ了在屏幕上显C字W串或数据,我们现在需要实现类?span lang="EN-US">C
q里有一U方法来实现Q我们直到象func(int arg1, int arg2, int arg3)q样一个函数被调用Ӟ它汇~后的指令应该如下(所有从左向叛_栈的~译器应该从地球上彻底消失)Q?span lang="EN-US">
pushl arg3
pushl arg2
pushl arg1
call func
我们看到Q参C叛_左一个个入栈Q参数越多,入栈深。如果是可变参数那我们怎么知道有多个参数呢?{案?span lang="EN-US">printf格式化字W串中参数判断:有多个%XQ就有多个参数要解析。在32位模式下Q所有小?span lang="EN-US">4字节的参数都被当?span lang="EN-US">4字节处理。例如一?span lang="EN-US">char型参敎ͼ入栈时就?span lang="EN-US">int型了Q所以在解析参数时务必保证正?span lang="EN-US">
我们q样设计kprintf参数Q?span lang="EN-US">kprintf(color,
format string, arguments...)
W一个参数定义输出的前景/背景颜色。我们定义了很多宏来解析栈,如果你熟?span lang="EN-US">C语言应该很容易理解它们?span lang="EN-US">
03/kprintf.c
#define args_list char
* // q个宏用例{换栈I间为字W串指针
#define _arg_stack_size(type)
(((sizeof(type)-1)/sizeof(int)+1)*sizeof(int))
// q个宏四舍五入参数大ؓ4字节的倍数
#define args_start(ap, fmt) do { \
ap = (char *)((unsigned int)&fmt + _arg_stack_size(&fmt));
\
} while (0)
//
参数从格式化字W串后面开始解析,?span lang="EN-US">fmt是栈顶Q上面这个宏是取参数的首地址
#define args_end(ap) //
到现在ؓ止,什么也不做
#define args_next(ap, type) (((type *)(ap+=_arg_stack_size(type)))[-1])
//
?span lang="EN-US">‘当前’参数地址Q然后设|指针ؓ下一个参数地址Q暧昧的函数名!
03/kprintf.c
static char buf[1024] =
{-1}; // 注意没有锁保护,引用该变量的函数不可重入Q?span lang="EN-US">
static int ptr = -1;
下面两个函数解析gؓ指定的进制数Q?span lang="EN-US">
static void
parse_num(unsigned int value, unsigned int base)
{ // 可以打印于{于10q制的数
unsigned int n = value / base;
int r = value % base;
if (r < 0) {
r += base;
--n;
}
if (value >= base)
parse_num(n, base);
buf[ptr++] = (r+'0');
}
static
void //
打印16q制?span lang="EN-US">
parse_hex(unsigned int value) {
int i = 8;
while (i-- > 0) {
buf[ptr++] =
"0123456789abcdef"[(value>>(i*4))&0xf];
}
}
现在我们来看一?span lang="EN-US"> kprintfq个函数Q它支持的格式:%s,
%c, %x, %d, %%
void
kprintf(enum KP_LEVEL kl, const char *fmt, ...) {
int i = 0;
char *s;
/* must be the same size as enum KP_LEVEL */
struct KPC_STRUCT {
COLOUR fg;
COLOUR bg;
} KPL[] = {
{BRIGHT_WHITE, BLACK},
{YELLOW, RED},
};
enum KP_LEVEL {KPL_DUMP, KPL_PANIC} 定义?span lang="EN-US"> include/kprintf.h, 它表CZU输出方?span lang="EN-US">, KPL_DUMP 使用黑色背景白色前景昄字符Q?span lang="EN-US">KPL_PANIC 使用黄色前景和红色背景。颜色常量定义在 include/scr.h,
后面会介l到.
args_list args;
args_start(args, fmt);
ptr = 0;
for (; fmt[i]; ++i) {
if ((fmt[i]!='%') &&
(fmt[i]!='\\')) {
buf[ptr++] = fmt[i];
continue;
} else if (fmt[i] == '\\') {
/* \a \b \t \n \v \f
\r \\ */
switch (fmt[++i]) {
case 'a': buf[ptr++] =
'\a'; break;
case 'b': buf[ptr++] =
'\b'; break;
case 't': buf[ptr++] =
'\t'; break;
case 'n': buf[ptr++] =
'\n'; break;
case 'r': buf[ptr++] =
'\r'; break;
case '\\':buf[ptr++] =
'\\'; break;
}
continue;
}
/* 下面是支持的打印格式 */
switch (fmt[++i]) {
case 's':
s = (char
*)args_next(args, char *);
while (*s)
buf[ptr++] = *s++;
break;
case 'c':
buf[ptr++] =
(char)args_next(args, int);
break;
case 'x':
parse_hex((unsigned
long)args_next(args, unsigned long));
break;
case 'd':
parse_num((unsigned
long)args_next(args, unsigned long), 10);
break;
case '%':
buf[ptr++] = '%';
break;
default:
buf[ptr++] = fmt[i];
break;
}
}
buf[ptr] = '\0';
args_end(args);
for (i=0; i<ptr; ++i)
print_c(buf[i],
KPL[kl].fg,
KPL[kl].bg); /*
print_c() 是下层的昄函数Q本文后面会有讲?span lang="EN-US"> */
}
׃是内核程序,我们无法使用C用户库。所以一?span lang="EN-US">memcpyQ?span lang="EN-US">memsetQ?span lang="EN-US">memcpy函数需要自己实玎ͼ但是需要注意的是在BSDpȝ中,即便使用?span lang="EN-US">-nostdlibQ编译器仍然会?span lang="EN-US">System V中相关的memcpy{代码,具体情况我也不是很清除。这些函数的效率当然无法?span lang="EN-US">linux内核中的内嵌汇编相比Q我们暂时这样实现它们吧?span lang="EN-US">
03/libcc.c
/* 下面函数寚w叠区域也q行了处?span lang="EN-US">
*/
void
bcopy(const void *src, void *dest, unsigned int n) {
const char *s = (const char *)src;
char *d = (char *)dest;
if (s <= d)
for (; n>0; --n)
d[n-1] = s[n-1];
else
for (; n>0; --n)
*d++ = *s++;
}
void
bzero(void *dest, unsigned int n) {
memset(dest, 0, n);
}
void *
memcpy(void *dest, const void *src, unsigned int n) {
bcopy(src, dest, n);
return dest;
}
void *
memset(void *dest, int c, unsigned int n) {
char *d = (char *)dest;
for (; n>0; --n)
*d++ = (char)c;
return dest;
}
int
memcmp(const void *s1, const void *s2, unsigned int n) {
const char *s3 = (const char *)s1;
const char *s4 = (const char *)s2;
for (; n>0; --n) {
if (*s3 > *s4)
return 1;
else if (*s3 < *s4)
return -1;
++s3;
++s4;
}
return 0;
}
int
strcmp(const char *s1, const char *s2) {
while (*s1 && *s2) {
int r = *s1++ - *s2++;
if (r)
return r;
}
if (*s1 == *s2)
return 0
else
return (*s1)?1:-1;
}
char *
strcpy(char *dest, const char *src) {
char *p = dest;
while ( (*dest++ = *src++))
;
*dest = 0;
return p;
}
unsigned int
strlen(const char *s) {
unsigned int n = 0;
while (*s++)
++n;
return n;
}
print_c函数
直接操作昑֭区域一点也不方便,所以我们需要一个显C模块。这个就是我们的‘昑֍驱动’了,是不是不敢相信驱动是q么单的事情Q我们先来看一下一些常量定义:
03/include/scr.h
#define MAX_LINES
25 //
bios默认讄屏幕?span lang="EN-US"> 80x25大小Q彩色字W模?span lang="EN-US">
#define MAX_COLUMNS 80
#define TAB_WIDTH
8
// 必须是:2^n
#define VIDEO_RAM
0xb8000 // 昑֭地址
我们曄要提到过q个地址Q在字符模式下,适配器?span lang="EN-US">0xB8000-0xBF000作ؓ视频内存。通常我们处于80x25大小屏幕Q有16U颜艌Ӏ由于一个屏q只需?span lang="EN-US">80x25x2个字节,?span lang="EN-US">4kQ所以该视频内存可以分ؓ多个c我们用所有的,但是当前只能有一个页面可见。ؓ了显CZ个字W,用?span lang="EN-US">2个字节,一个字节是字符|另一个字节是字符属性(即颜Ԍ。属性字节定义如下:
To display a single character, two bytes are being used
which called the character byte and the attribute byte. The character byte
contains the value of the character. The attribute byte is defined like this:
Bit 7 |
闪烁 |
Bits 6-4 |
背景?span lang="EN-US"> |
Bit 3 |
明亮模式 |
Bit3 2-0 |
前景?span lang="EN-US"> |
#define LINE_RAM (MAX_COLUMNS*2)
#define PAGE_RAM (MAX_LINE*MAX_COLUMNS)
#define BLANK_CHAR (' ')
#define BLANK_ATTR (0x70)
/* 白色前景Q黑色背?span lang="EN-US"> */
#define CHAR_OFF(x,y)
(LINE_RAM*(y)+2*(x)) /* 计算l定坐标xQ?span lang="EN-US">y的偏Ud址Q相?span lang="EN-US">0xB8000Q?span lang="EN-US"> */
Calculates the offset of a given ordinary x, y from 0xB8000
typedef enum COLOUR_TAG
{ /*
颜色?span lang="EN-US"> */
BLACK, BLUE, GREEN, CYAN, RED, MAGENTA, BROWN, WHITE,
GRAY, LIGHT_BLUE, LIGHT_GREEN, LIGHT_CYAN,
LIGHT_RED, LIGHT_MAGENTA, YELLOW, BRIGHT_WHITE
} COLOUR;
坐标pd下:
___________________\
| Q?span lang="EN-US">0Q?span lang="EN-US">0Q?span lang="EN-US"> /
|
\|/
03/scr.c
static int csr_x = 0;
static int csr_y = 0;
׃我们只用C一个视频页Q所以上面两个变量就可以存储坐标了。关于多|C可以在|络上查扄兌料?span lang="EN-US">
static void
scroll(int lines) { 向上滚动屏幕多少行,是一些内存复写?span lang="EN-US">
short *p = (short *)(VIDEO_RAM+CHAR_OFF(MAX_COLUMNS-1,
MAX_LINES-1));
int i = MAX_COLUMNS-1;
memcpy((void *)VIDEO_RAM, (void
*)(VIDEO_RAM+LINE_RAM*lines),
LINE_RAM*(MAX_LINES-lines));
for (; i>=0;
--i) // 说明q个for循环有问题,觉得应该Ҏ下面q样Q?span lang="EN-US">
// for (i = i * lines;
i>=0; --i)
*p-- =
(short)((BLANK_ATTR<<4)|BLANK_CHAR);
}
下面函数讄光标可能会引发竞态条Ӟ但是print_c只准备在内核中用,所以没有关中断。它可能会引起一?span lang="EN-US">bugQ但是我没有扑ֈ。译注:全局变量没有锁保护在设计上就是一U错误。这里的代码保护实是没有做Q读者应用到自己的内核时要小心了?span lang="EN-US">
void
set_cursor(int x, int y) {
csr_x = x;
csr_y = y;
outb(0x0e,
0x3d4);
讄光标?span lang="EN-US">8位的准备工作
outb(((csr_x+csr_y*MAX_COLUMNS)>>8)&0xff,
0x3d5); 讄光标?span lang="EN-US">8?span lang="EN-US">
outb(0x0f,
0x3d4);
讄光标?span lang="EN-US">8位的准备工作
outb(((csr_x+csr_y*MAX_COLUMNS))&0xff,
0x3d5); 讄光标?span lang="EN-US">8?span lang="EN-US">
}
void
get_cursor(int *x, int *y) {
*x = csr_x;
*y = csr_y;
}
void
print_c(char c, COLOUR fg, COLOUR bg) {
// 用这个函数来昄一个具体的字符到屏q,我们可以把它看作‘昑֍驱动’
char *p;
char attr;
p = (char *)VIDEO_RAM+CHAR_OFF(csr_x,
csr_y); // 取光标位|?span lang="EN-US">
attr =
(char)(bg<<4|fg);
// 属?span lang="EN-US">
switch (c) {
case '\r':
csr_x = 0;
break;
case '\n':
for (; csr_x<MAX_COLUMNS; ++csr_x) {
*p++ = BLANK_CHAR;
*p++ = attr;
}
break;
case '\t':
c =
csr_x+TAB_WIDTH-(csr_x&(TAB_WIDTH-1));
c = c<MAX_COLUMNS?c:MAX_COLUMNS;
for (; csr_x<c; ++csr_x) {
*p++ = BLANK_CHAR;
*p++ = attr;
}
break;
case '\b':
if ((! csr_x) && (! csr_y))
return;
if (! csr_x) {
csr_x = MAX_COLUMNS -
1;
--csr_y;
} else
--csr_x;
((short *)p)[-1] = (short)((BLANK_ATTR<<4)|BLANK_CHAR);
break;
default:
*p++ = c;
*p++ = attr;
++csr_x;
break;
}
if (csr_x >= MAX_COLUMNS) {
csr_x = 0;
if (csr_y < MAX_LINES-1)
++csr_y;
else
scroll(1);
}
set_cursor(csr_x,
csr_y); // 讄光标位置
}
函数比较单,没有分析的必要了Q大家自q吧?span lang="EN-US">
声明Q{载请保留Q?/span>
译?/span>Q?/span>http://www.shnenglu.com/jinglexy
原作者:xiaoming.mo at skelix dot org
MSN & Email: jinglexy at yahoo dot com dot cn目标Q?/span>?span lang="EN-US">"system"从Y盘启动,q打?span lang="EN-US">"Hello World!" 下蝲源程?/span>
内存d
处理器以‘字节’理和访问内存,每个字节都有独立的地址Q即物理地址。有两种地址映射方式Q分D和分页Q?span lang="EN-US">skelix内核中都用到了?a name="Memory_Addressing">
D对于我们来说再熟悉不过了,先回一?span lang="EN-US">dos时期的段吧。它是一?span lang="EN-US">16位的寄存器,所以最多可以直接访?span lang="EN-US">2^16字节的内存,?span lang="EN-US">64K。这对应用程序来说太了Q于?span lang="EN-US">Intel使用Segment:Offsetl合方式来表CZ个虚拟地址。段寄存器左U?span lang="EN-US">4位加上偏Ud得到实际的物理地址了。例如,0x
+ 0189
-------
现在我们来计最大可以访问的地址Q?span lang="EN-US">FFFF:FFFF
FFFF0
+ FFFF
-------
10FFEF
q个范围?st1:chmetcnv unitname="m" sourcevalue="1" hasspace="False" negative="False" numbertype="1" tcsc="0" w:st="on">1M + 65519 bytes, 因ؓ?span lang="EN-US">80386中用了20位地址U,所以可以额外多讉K65519个字节虚拟地址Q例如地址0x100010被映到地址0x10Q访问这两个地址是等L?span lang="EN-US">
表示同一个物理地址有多U方式,例如
另一个概忉|U性地址Q这个是32位地址Q只有当分页机制开启时才有效,文章后面会提到它?span lang="EN-US">
引导q程
当系l上甉|RESETӞ处理器将执行一些列的初始化Q寄存器被设|成非预知状态,q且cpu处于实模式。也怽想知?span lang="EN-US">cpu是怎样讄segment:offset为物理地址FFFF0的(0xf000:0xfff0是bios入口地址Q,q是因ؓcs寄存器有一个非可见部分Q它保存?span lang="EN-US">ffff:0000地址Qƈ?span lang="EN-US">cs在初始化时会被装?span lang="EN-US">f000倹{此后以正常方式使用它。当bois取得控制权后Q根据用户配|(从Y驱,盘Q或cdromQ中dW一?span lang="EN-US">sector?st1:chmetcnv unitname="C" sourcevalue="7" hasspace="False" negative="False" numbertype="1" tcsc="0" w:st="on">00007C00Qƈ跌{到该地址执行Q就是引导程?span lang="EN-US">bootstrapQ。在bootstrap中我们可以?span lang="EN-US">bios中断Q但是进?span lang="EN-US">kernel后就不能再用了?span lang="EN-US">
E序一Q?span lang="EN-US">as?span lang="EN-US">ld的范?/span>
你可以在下蝲源程序的01/first.cry/bootsect.s
.text .text表示代码D?span lang="EN-US">
.globl start表示start可以用作外部W号
.code16 GCC默认使用32位地址和操作数Q这里告诉它使用16?span lang="EN-US">
start:
jmp
start d@?span lang="EN-US">
.org 0x1fe,
0x90 .org NEW-LC,
FILLQ?/span>说明Q这里填?span lang="EN-US">0x90Q是nop指o的机器码
.word 0xaa55
讲解Q?span lang="EN-US">.org指o指示下一个数据地址Qؓ了编译这个程序,我们写了一?span lang="EN-US">MakefileQM能老是敲命令吧Q呵c?span lang="EN-US">
|络上可以找到很多写Makefile的资料,~译选项才是我们x的焦炏V?span lang="EN-US">
01/first.cry/Makefile
AS=as
gcc汇编工具
LD=ld
gccq接?span lang="EN-US">
.s.o:
${AS} -a $< -o $*.o >$*.map
all: final.img
final.img: bootsect
mv bootsect final.img
bootsect: bootsect.o
${LD} --oformat binary -N -e start -Ttext 0x
讲解Q?span lang="EN-US">ld可以被配|ؓ支持多于一U的目标文g. binary表示没有E序头和其他信息Q仅仅是一些裸数据。如果没有这个选项Q将被默认链接ؓelf格式?span lang="EN-US">-N?span lang="EN-US">text?span lang="EN-US">data节设|ؓ可读写?span lang="EN-US">-Ttext?span lang="EN-US">text节v始地址讄?span lang="EN-US">0x
现在我们q行make指o~译一下:
[root@root~/source/os/skelix/01/first.cry]$ ls
bootsect.s COPYING Makefile
[root@root~/source/os/skelix/01/first.cry]$ make
as -a bootsect.s -o bootsect.o >bootsect.map
ld --oformat binary -N -e start -Ttext 0x
mv bootsect final.img
[root@root~/source/os/skelix/01/first.cry]$ ls
bootsect.map bootsect.o
bootsect.s COPYING final.img
Makefile
[root@root~/source/os/skelix/01/first.cry]$
现在Q我们启?span lang="EN-US">vmwareQ运行,载入软驱映象文g"final.img"Q我们得C个黑屏,q是正确的,因ؓ我们什么也没有做?span lang="EN-US">
E序一Q显C?Hello World!
好了Q上面的黑屏E序q不是太好玩Q现在我们尝试在上面打印"Hello World!"
01/hello.world/bootsect.s
.text
.globl start
.code16
start:
jmp code
msg:
使用jmp指o跌该变量,q是我们Z么在Makefile使用-N链接选项?span lang="EN-US">
.string "Hello World!\x0"
code:
movw $0xb800,%ax
movw
%ax,
%es esD设|成B800Q如前所qͼsegment:offset地址映射方式Q它指向B8000Q?span lang="EN-US">
q意味着W一个字节地址?span lang="EN-US">0Q映到B8000Q,属性字节是1Q映到B8001Q?span lang="EN-US">
B8001D|ؓ0x07可以这?span lang="EN-US">byte颜色讄为黑底白字?span lang="EN-US">
xorw
%ax, %ax
movw
%ax, %ds
movw $msg,
%si ?span lang="EN-US">movsb指o讄正确?span lang="EN-US">si?span lang="EN-US">di
xorw
%di, %di
cld
movb $0x07,
%al 字的颜色
1:
cmp
$0, (%si)
je
movsb
stosb
jmp 1b
1: jmp 1b
.org 0x1fe, 0x90
.word 0xaa55
声明Q{载请保留Q?/span>
译?/span>Q?/span>http://www.shnenglu.com/jinglexy
原作者:xiaoming.mo at skelix dot org
MSN & Email: jinglexy at yahoo dot com dot cn
GCC
Skelix 使用c语言~写Q当然也用了汇编语言Q?span lang="EN-US">at&t风格Q,?span lang="EN-US">linux下?span lang="EN-US">gcc~译?span lang="EN-US">
[root@root ~]$ gcc -v
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man
--infodir=/usr/share/info --enable-shared --enable-threads=posix
--disable-checking --with-system-zlib --enable-__cxa_atexit
--disable-libunwind-exceptions --enable-java-awt=gtk --host=i386-redhat-linux
Thread model: posix
gcc version 3.4.2 20041017 (Red Hat 3.4.2-6.fc3)
在每教E中都给Z源程序和软盘映象文gQ你可以直接使用它们。如果你需要编译这些源E序Q编译环境必L。我们推荐的环境?span lang="EN-US">linux2.6.x内核Q?span lang="EN-US">gcc3.x~译器?span lang="EN-US">
׃在源E序中用了__asm__, __attribute__, __extention__Q以?span lang="EN-US">gcc内嵌汇编Q还?span lang="EN-US">unsigned long long(直到C99才开始支?span lang="EN-US">)Q如果你使用了其他编译器Q需要修改对应的源程序。且~译器必L32位,q样做的目的是保持源E序z清晰?span lang="EN-US">
对于windows用户可以使用 cygwinQ它提供?span lang="EN-US">windows下的linux环境。不q我没有试使用它,因ؓ我的电脑上没有安?span lang="EN-US">windows操作pȝ。也可以在你?span lang="EN-US">windowspȝ上安装一个虚拟机上的linuxQ如果你的电脑够快的话?span lang="EN-US">
VMWARE
Zq行教程中的范例Q一个虚拟机必不可少Q?span lang="EN-US">virtual pc2007已经可以免费使用了,?span lang="EN-US">M$的官方网站上可以扑ֈ下蝲。当然也可以使用qemu?span lang="EN-US">bochs之类的虚拟机。推荐的虚拟机是VMWARE?span lang="EN-US">
Things Are Good To Know
如果能看?/span>Makefile最好了Q这?/span>*nixE序员必L握的一基本知识。另外,如果你熟悉内存地址映射Q中断,异常Q?/span>GDTQ?/span>LDTQ?/span>IDTQ分|Ӟ范围端口更好了。当然不懂也没关p,Intel的三h册是案头必备Q?/span>http://www.intel.com
IA-32 Intel Architecture Software Developer's Manual
Volume1: Basic Architecture
IA-32 Intel Architecture Software Developer's Manual Volume
IA-32 Intel Architecture Software Developer's Manual Volume3B: System
Programming Guide Part2
读者对q些东西不必紧张Q我在教E中会解释相关的知识?span lang="EN-US">c语言和汇~是最基本的要求,能够很清楚的了解什么是堆和栈。关?span lang="EN-US">c语言的数据成千上万,但是保护模式斚w的书c比哈雷慧星q少Q据说每76q可以买C本,如果你够幸q的话:Q?a name="Color_Pattern">
风格U定
原文中的格式被擅自去掉了Q翻译后的风格应该可以一看就懂?/span>
which: 只在PATH环境变量中寻找文?span lang="EN-US">
whereis: 在系l定义的目录中寻?span lang="EN-US">
2.Ҏ关键字查?span lang="EN-US">man?span lang="EN-US">
举例Q?span lang="EN-US">apropos split 或?span lang="EN-US"> man -k split
3.sedCZQ?span lang="EN-US">
sed -e s/root/toor/g /etc/passwd >
~/test.out 替换 /etc/passwd中的root?span lang="EN-US">toor输出?span lang="EN-US">~/test.out
sed -e 's/root/toor/g; s/ftp/ptf/g'
/etc/passwd 使用 -e 指定多个命o
sed -e 's/root/toor/g' -e ' s/ftp/ptf/g'
/etc/passwd 同上
使用命o文gQ?span lang="EN-US">
/* test.sed 开始,不包含本?span lang="EN-US"> */
s/root/toor/g
s/ftp/ptf/g
/* test.sed l束Q不包含本行 */
指oQ?span lang="EN-US">sed -f
test.sed /etc/passwd
4.awkCZQ?span lang="EN-US">
awk '{print $0}'
/etc/passwd $0表示完整的输入记?span lang="EN-US">
awk -F":" '{print $1}'
/etc/passwd 打印W一列,以:为分隔符
awk -F":" '{print "username:
"$1 "\t\t\t user id: "$3}'
/etc/passwd 格式化ƈ打印
使用命o文gQ?span lang="EN-US">
/* test.awk 开始,不包含本?span lang="EN-US"> */
BEGIN{
FS=":"
}
{printf "username: "$1 "\t\t\t user id:
"$3"\n"}
END{
printf "all done processing
/etc/passwd\n"
}
/* test.awk l束Q不包含本行 */
指oQ?span lang="EN-US">awk -f
test.awk /etc/passwd
5.shell脚本
1)Ҏ变量Q?span lang="EN-US">
?前一个命令输出状?span lang="EN-US">
0当前脚本?span lang="EN-US">
1~9参数
2)范例1Q?span lang="EN-US">if语句
#!/bin/bash
echo "guest
the select color"
read COLOR
if [ $COLOR =
"yellow" ]
then
echo
"you are correct"
elif [ $COLOR = "blue" ]
then
echo
"you are correct also"
fi
3)范例2Q?span lang="EN-US">case语句
#!/bin/bash
case "$1"
in
start)
echo "start......"
;;
stop)
echo "stop......"
;;
status)
echo "status......"
;;
*)
echo "usage: $0 {start | stop | status}"
;;
esac
4)范例3Q用P代流E?span lang="EN-US">
#!/bin/bash
echo "guest
color: red, blue or orange\n"
read COLOR
while [ $COLOR !=
"orange" ]
do
echo
"incorrect, try again"
read
COLOR
done
echo
"correct"
5)使用双引可行命令替?span lang="EN-US">
lines="$(wc -l 3.sh)"
echo $lines
6)试文g
-d
file 目录
-e
file 存在
-r
file 可读
-w
file 可写
-x
file 可执?