initrd与initramfs的区别

boot loader装入kernel, 然后kernel需要执行/sbin/init, 读取
这个文件就必须先mount根文件系统, 早期是通过启动时的root=
参数告诉内核根文件系统在哪个设备上,  随着硬件和技术的发展,
现在根文件系统可能位于一个网络存储如NFS上, 可能由于RAID而
散布于多个设备上, 可能位于一个加密设备上需要提供用户名和密码,
这时root=参数就显得不够了. 为了应付这种局面, 先后出现两种
机制来作为boot loader装载kernel到真正的/sbin/init执行这个启动
过程的桥梁: initrd和initramfs, 两者有类似的地方, 比如都是
由内核执行其上的某个程序(initrd是/linuxrc, initramfs是/init),
由这个程序决定加载什么驱动以及如何装载根文件系统. 下面一点
笔记总结initrd的缺点和initramfs的优点.
initrd:
ram disk是一个基于ram的块设备,因此它占据了一块固定的内存,
而且事先要使用特定的工具比如mke2fs格式化,还需要一个文件系统
驱动来读写其上的文件。
如果这个disk上的空间没有用完,这些未用的内存就浪费掉了,并且
这个disk的空间固定导致容量有限,要想装入更多的文件就需要重新
格式化。
由于Linux的块设备缓冲特性, ram disk上的数据被拷贝到page cache
(对于文件数据)和dentry cache(对于目录项), 这个也导致内存浪费.
initramfs:
最初的想法是Linus提出的: 把cache当作文件系统装载. 他在一个叫
ramfs的cache实现上加了一层很薄的封装, 其它内核开发人员编写了
一个改进版tmpfs, 这个文件系统上的数据可以写出到交换分区, 而且
可以设定一个tmpfs装载点的最大尺寸以免耗尽内存. initramfs就是
tmpfs的一个应用.
优点:
    (1)tmpfs随着其中数据的增减自动增减容量.
    (2)在tmpfs和page cache/dentry cache之间没有重复数据.
    (3)tmpfs重复利用了Linux caching的代码, 因此几乎没有增加内核
     尺寸, 而caching的代码已经经过良好测试, 所以tmpfs的代码质量
     也有保证.
    (4)不需要额外的文件系统驱动.
另外, initrd机制被设计为旧的"root="机制的前端, 而非其替代物,
它假设真正的根设备是一个块设备, 而且也假设了自己不是真正的根设备,
这样不便将NFS等作为根文件系统, 最后/linuxrc不是以PID=1执行的, 因为
1这个进程ID是给/sbin/init保留的. initrd机制找到真正的根设备后将
其设备号写入/proc/sys/kernel/real-root-dev, 然后控制转移到内核由
其装载根文件系统并启动/sbin/init.
initramfs则去掉了上述假设, 而且/init以PID=1执行, 由init装载根文件
系统并用exec转到真正的/sbin/init, 这样也导致一个更为干净漂亮的设计.

ramdisk与 initrd、initramfs的关系

1、ramdisk、initrd是什么?

ramdisk是一种基于内存的虚拟文件系统,通常用于放置内核的中间数据。
而initrd全称为”boot loader initialized RAM disk”,也就是由启动加载器所初始化的RamDisk设备,它的作用是完善内核的模块机制,让内核的初始化流程更具弹性;内核以及initrd,都由bootloader在机子启动后被加载至内存的指定位置,主要功能为按需加载模块以及按需改变根文件系统。更详细的内容,请参阅initrd的man手册,里面阐述了内核开发者对initrd制订的功能标准。命令:man initrd

2、/dev/ram0 这个设备是哪来的?

答: 拷贝initrd内容到/dev/ram0 上时文件系统已经被加载,只是没有设置根文件系统,内核的初始化流程还没有进入用户层而已,我们当然不能知道是否有这个设备,但的确是存在的。
      所谓文件系统,就是VFS,而根文件系统是其中的一个属性项而已;而每个设备本身是内核数据区里的一个数据结构,经过VFS的映射,才被用户层视为一个文件。所以,内核要创建继而访问一个设备,是可以直接传递”/dev/ram”这样的参数给create_dev函数来实现的。
       至于设置根文件系统,只是C语言里对一个数据结构的成员赋值而已。而所谓目录项,也是内核数据区里的一个数据结构。要创建/dev/ram0,只需赋值根文件系统的值,创建dev目录项,并且创建这个ram设备即可。
      要区分内核层以及用户层的区别,可以用一个很简单的逻辑来解释,试想一个,你在用户层删除了/dev里的ram0,那是不是内核就没有了这个设备了呢?再想,如果你删除了你正在使用的硬盘的设备节点/dev/hda,那么是不是内核就不能操作硬盘了呢?答案是很明显的,没有了设备节点,只意味着用户不能操作该设备,并不代表内核不能访问该设备。在最新的设备管理体系中,设备管理的操作逻辑是放在用户层的,那就是udev,默认的规则中没有/dev/eth0设备节点,但你可以照样上网。

3、在 grub 中使用到的 initrd ,是用来做什么的?如何做到?

答:GRUB中只是简单地把initrd拷贝在内存中指定的位置而已,没有对它做进一步的操作,只是将initrd的大小放在内核setup.S的头部数据区域,由内核来初始化这段内存区域。

请看内核初始化代码init中的initramfs.c代码片断:

代码:

#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start) {
int fd;
printk(KERN_INFO “checking if image is initramfs…”);
                /*这里的initrd_start和initrd_end就是GRUB放置initrd的内存区域的范围*/
err = unpack_to_rootfs((char *)initrd_start,
initrd_end – initrd_start, 1);
if (!err) {
printk(” it is “);
unpack_to_rootfs((char *)initrd_start,
initrd_end – initrd_start, 0);
free_initrd_mem(initrd_start, initrd_end);
return;
}

printk(“it isn’t (%s); looks like an initrd “, err);
fd = sys_open(“/initrd.image”, O_WRONLY|O_CREAT, 700);
if (fd >= 0) {
sys_write(fd, (char *)initrd_start,
initrd_end – initrd_start);
sys_close(fd);
free_initrd_mem(initrd_start, initrd_end);
}

}

#endif

GRUB不参与Linux内核的初始化!它是与内核独立的。

记住,GRUB与Linux内核交互的方式遵循一种协议,目前的协议版本为2.02;GRUB通过”setup head”区域与内核交换数据。
      假设内核映像为kernel.img,那么这个img文件里就包含着三截代码段(在编译内核的最后阶段生成),第一段是bootsect.S,就是旧的内核内置启动器,它的地位已经被GRUB替代,所以被丢弃不用;第二段是setup.S,由GRUB负责将其放置到物理地址0x90200上,它的头部就是前面提及的所谓head区,GRUB会把需要告诉内核的信息例如initrd的物理起始地址以及大小,放在这个head区;第三段就是真正的压缩内核映像,视乎其体积类型,如果是大内核,就将这段内核映像放至0x100000。
      那么,在初始化页表的时候,内核会搬动head区的数据到合适的位置,里面的数据就会被内核初始化所利用,所以内核就得知了initrd的所有信息。
      也就是说,setup.S 前面一段是用来在grub和内核之间传递参数的,如果在grub 中设置了 initrd的话,grub会读出该文件的地址以及文件的大小,然后将其写入setup.S的head区的特定地方,待内核启动时使用.grub引导结束后,应该跳转至setup.S执行.

这样的initrd.img文件是由grub载入内存,然后再将内存中的initrd信息告诉内核,让内核进行初始化。

内核文件头部是这样一个数据结构:

/* For the Linux/i386 boot protocol version 2.03. */
struct linux_kernel_header
{
char code1[0x0020];
unsigned short cl_magic; /* Magic number 0xA33F */
unsigned short cl_offset; /* The offset of command line */
char code2[0x01F1 – 0x0020 – 2 – 2];
unsigned char setup_sects; /* The size of the setup in sectors */
unsigned short root_flags; /* If the root is mounted readonly */
unsigned short syssize; /* obsolete */
unsigned short swap_dev; /* obsolete */
unsigned short ram_size; /* obsolete */
unsigned short vid_mode; /* Video mode control */
unsigned short root_dev; /* Default root device number */
unsigned short boot_flag; /* 0xAA55 magic number */
unsigned short jump; /* Jump instruction */
unsigned long header; /* Magic signature “HdrS” */
unsigned short version; /* Boot protocol version supported */
unsigned long realmode_swtch; /* Boot loader hook */
unsigned long start_sys; /* Points to kernel version string */
unsigned char type_of_loader; /* Boot loader identifier */
unsigned char loadflags; /* Boot protocol option flags */
unsigned short setup_move_size; /* Move to high memory size */
unsigned long code32_start; /* Boot loader hook */
unsigned long ramdisk_image; /* initrd load address */
unsigned long ramdisk_size; /* initrd size */
unsigned long bootsect_kludge; /* obsolete */
unsigned short heap_end_ptr; /* Free memory after setup end */
unsigned short pad1; /* Unused */
char *cmd_line_ptr; /* Points to the kernel command line */
unsigned long initrd_addr_max; /* The highest address of initrd */
} __attribute__ ((packed));

grub 首先把vmlinuxz装载入内存,然后把initrd装载入内存,并initrd的装载地址
填入vmlinuxz的头部相应位置

4、grub能知道该把intrd.img这个文件放在内存的什么地方才安全吗?

答:Linux为它的启动制订了一个标准协议,GRUB或者LILO要启动Linux,就必须遵循这个协议。该协议放在内核源码目录的”Documentation/i386/boot.txt”。
      根据这个协议,initrd的位置尽可能地放在内存的高地址,这个高地址的值取决于机器的架构,但为了达到最大的兼容性,一般放在16MB靠后的地方,因为16MB是i286能识别的最大内存。另外,不同的bootloader,对initrd的加载地址可能有点差异,但只要不覆盖内核以及内核初始化可能要用的数据区即可。

5、struct linux_kernel_header在哪个文件中定义的阿?

答:grub-0.97源码 /stage2/share.h文件中
      linux 内核 linux-2.6.18/Documentation/i386/boot.txt 也说明了头部结构
The header looks like:

Offset Proto Name Meaning
/Size

01F1/1 ALL(1 setup_sects The size of the setup in sectors
01F2/2 ALL root_flags If set, the root is mounted readonly
01F4/4 2.04+(2 syssize The size of the 32-bit code in 16-byte paras
01F8/2 ALL ram_size DO NOT USE – for bootsect.S use only
01FA/2 ALL vid_mode Video mode control
01FC/2 ALL root_dev Default root device number
01FE/2 ALL boot_flag 0xAA55 magic number
0200/2 2.00+ jump Jump instruction
0202/4 2.00+ header Magic signature “HdrS”
0206/2 2.00+ version Boot protocol version supported
0208/4 2.00+ realmode_swtch Boot loader hook (see below)
020C/2 2.00+ start_sys The load-low segment (0x1000) (obsolete)
020E/2 2.00+ kernel_version Pointer to kernel version string
0210/1 2.00+ type_of_loader Boot loader identifier
0211/1 2.00+ loadflags Boot protocol option flags
0212/2 2.00+ setup_move_size Move to high memory size (used with hooks)
0214/4 2.00+ code32_start Boot loader hook (see below)
0218/4 2.00+ ramdisk_image initrd load address (set by boot loader)
021C/4 2.00+ ramdisk_size initrd size (set by boot loader)
0220/4 2.00+ bootsect_kludge DO NOT USE – for bootsect.S use only
0224/2 2.01+ heap_end_ptr Free memory after setup end
0226/2 N/A pad1 Unused
0228/4 2.02+ cmd_line_ptr 32-bit pointer to the kernel command line
022C/4 2.03+ initrd_addr_max Highest legal initrd address

在文件arch/i386/boot/bootsect.S可以看到这个结构的影子

.org 497
setup_sects: .byte SETUPSECTS
root_flags: .word ROOT_RDONLY
syssize: .word SYSSIZE
swap_dev: .word SWAP_DEV
ram_size: .word RAMDISK
vid_mode: .word SVGA_MODE
root_dev: .word ROOT_DEV
boot_flag: .word 0xAA55

vim写程序常用技巧


"Set ma pleader
let g:mapleader = ","

set nocompatible

set backspace=indent,eol,start

"显示行号
set nu

"标签
let g:miniBufExplMapWindowNavVim = 1
let g:miniBufExplMapWindowNavArrows = 1
let g:miniBufExplMapCTabSwitchBufs = 1
let g:miniBufExplModSelTarget = 1

"windows manger
let g:winManagerWindowLayout='FileExplorer'
nmap wm :WMToggle

"打开语法高亮
syntax on
"let asmsyntax="gas"
let asmsyntax="nasm"
"设置字体
set guifont=DejaVu Sans Mono 12

"设置缩进
set softtabstop=8
set shiftwidth=8
set expandtab

"关闭toolbar
set guioptions-=T

"关闭自动备份
set nobackup

set completeopt=longest,menu

"自动格式化
set formatoptions=tcrqn

"在行和段开始处使用制表符
set smarttab

"在normal模式下使用系统剪贴板
"set clipboard+=unnamed

"自动缩进设置
set cindent
set smartindent
set incsearch
set autoindent
set cinoptions=:0
"Show matching bracets
set showmatch

"Get out of VI's compatible mode
set nocompatible

"Have the mouse enabled all the time
set mouse=a

"Set to auto read when a file is changed from the outside
set autoread

"Enable filetype plugin
filetype plugin indent on

"设置配色方案为torte
"colo torte
colo desert
"colo tango
"设置支持的文件编码类项,目前设置为utf-8和gbk两种类型
set fenc=utf-8
set fileencodings=utf-8,chinese,gb18030,gbk,gb2312,cp936
set enc=utf-8
let &termencoding=&encoding

"设置断词
set linebreak

"设置搜索结果高亮显示
set hlsearch

"设置记录的历史操作列表
set history=200

"设置折叠
set foldenable
set foldcolumn=2
set foldlevel=3

"打开目录时不显示隐藏目录和文件
let g:netrw_hide= 1
let g:netrw_list_hide= '^..*'

"AutoCommand
" 鼠标跳到上次关闭时,编辑的位置
" When editing a file, always jump to the last known cursor position.
" Don't do it when the position is invalid or when inside an event handler
" (happens when dropping a file on gvim).
autocmd BufReadPost *
if line("'"") > 0 && line("'"") <= line("$") | exe "normal g`"" | endif "新建.c,.h.cpp,.sh,.java,.php,.py文件自动打开Taglist autocmd BufNewFile *.[ch],*.cpp,*.sh,*.java,*.php,*.py exec ":call SetTitle()" "读入.c,.h.cpp,.sh,.java,.php,.py文件自动打开Taglist "autocmd BufRead *.[ch],*.cpp,*.sh,*.java,*.php,*.py exec ":Tlist" "新建文件后,自动定位到文件末尾 autocmd BufNewFile * normal G "如果是新建的php文件,则自动定位到最后第二行 autocmd BufNewFile *.php normal k "写入.c,.h.cpp,.sh,.java,.php,.py文件自动更新ctags autocmd BufWrite *.[ch],*.cpp,*.sh,*.java,*.php,*.py exec ":!ctags -R *" " "读入python文件,设置缩进格式 autocmd BufNewFile,BufRead *.py set cinwords=if,elif,else,for,while,try,expect,finally,def,class "读入C文件,设置折叠方式为syntax autocmd BufNewFile,BufRead *.[ch],*.cpp set foldmethod=syntax "读入其它文件,设置折叠方式为indent autocmd BufNewFile,BufRead *.py,*.sh,*.java,*.php set foldmethod=indent "设置Java代码的自动补全 autocmd FileType java setlocal omnifunc=javacomplete#Complete "autocmd FileType java set tags=./tags,./../tags,./http://www.cnblogs.com/tags "设置输入代码的自动补全 "autocmd BufEnter * call DoWordComplete() set complete=.,w,b,u,t,i,k set completeopt=longest,menu "设置当回复邮件时自动定位到最后一行 autocmd BufRead /tmp/mutt-* normal G "autocmd BufRead /tmp/mutt-* normal $ "绑定自动补全的快捷键;
imap ;

"绑定复制到系统剪贴板快捷键
vmap c "+y
nmap c "+y

"绑定粘贴系统剪贴板内容快捷键
"imap v "+p "不设置insert模式下的快捷键,因为会造成无法输入,v
vmap v "+p
nmap v "+p

"设定开关Taglist插件的快捷键为F4,可以在VIM的左侧栏列出函数列表等
map :Tlist

"设置程序的运行和调试的快捷键F5和Ctrl-F5
map :call CompileRun()
map :call Debug()
"设置手动更新tags文件
map :!ctags -R *
map :!splint %
"设置tab操作的快捷键,绑定:tabnew到t,绑定:tabn, :tabp到n,
"p
map t :tabnew
map n :tabn
map p :tabp

"设置空格键开关折叠
nmap @=((foldclosed(line('.')) < 0) ? 'zc' : 'zo')

"使用r打开上次运行的命令
nmap r :

"用cscope支持
set csprg=/usr/bin/cscope
let Tlist_Ctags_Cmd='/usr/local/bin/ctags'
let Tlist_Show_One_File=1
let Tlist_Exit_OnlyWindow=1
let Tlist_Use_Right_Window=1
"默认打开Taglist
let Tlist_Auto_Open=1

"设置搜索的tags文件范围
set tags=./tags,./../tags,./http://www.cnblogs.com/tags,/usr/include/tags,/usr/src/linux-3.2.6/include/tags

"使用e打开当前文件同目录中的文件
if has("unix")
map e :e =expand("%:p:h") . "/"
else
map e :e =expand("%:p:h") . ""
endif

"定义CompileRun函数,用来调用进行编译和运行
func CompileRun()
exec "w"
"C程序
if &filetype == 'c'
exec "!gcc % -g -o %<" exec "!./%<" "Java程序 elseif &filetype == 'java' exec "!javac %" exec "!java %<" "php程序 elseif &filetype == 'php' exec "!php %" "bash程序 elseif &filetype == 'sh' exec "!bash %" "python程序 elseif &filetype == "python" exec "!python %" endif endfunc "结束定义CompileRun "定义Debug函数,用来调试程序 func Debug() exec "w" "C程序 if &filetype == 'c' exec "!gcc % -g -o %<" exec "!gdb %<" "Java程序 elseif &filetype == 'java' exec "!javac %" exec "!jdb %<" "Php程序 elseif &filetype == 'php' exec "!php %" "bash程序 elseif &filetype == 'sh' exec "!bash -x %" "python程序 elseif &filetype == 'python' exec "!pdb %" endif endfunc "结束定义Debug "定义函数SetTitle,自动插入文件头 func SetTitle() "如果文件类型为.sh文件 if &filetype == 'sh' || &filetype == 'python' call setline(1, "#========================================================================") call append(line("."), "# Author: findstr") call append(line(".")+1, "# Email: findstr@sina.com") call append(line(".")+2, "# File Name: ".expand("%")) call append(line(".")+3, "# Description: ") call append(line(".")+4, "# ") call append(line(".")+5, "# Edit History: ") call append(line(".")+6, "# ".strftime("%Y-%m-%d")." File created.") call append(line(".")+7, "#========================================================================") call append(line(".")+8, "") "其它程序文件 else call setline(1, "/**") call append(line("."), "=========================================================================") call append(line(".")+1, " Author: findstr") call append(line(".")+2, " Email: findstr@sina.com") call append(line(".")+3, " File Name: ".expand("%")) call append(line(".")+4, " Description: (C) ".strftime("%Y-%m"). " findstr") call append(line(".")+5, " ") call append(line(".")+6, " Edit History: ") call append(line(".")+7, " ".strftime("%Y-%m-%d")." File created.") call append(line(".")+8, "=========================================================================") call append(line(".")+9, "**/") call append(line(".")+10, "") endif "如果为php文件,添加相应头和尾 if &filetype == 'php' call append(0, "")
endif
"如果为sh文件,添加相应的头
if &filetype == 'sh'
call append(0, "#!/bin/bash")
"如果为python文件,添加相应的头和编码设定
elseif &filetype == 'python'
call append(0, "#!/usr/bin/python")
call append(1, "# -*- coding: utf-8 -*-")
endif
endfunc

"ececute project relate configuration in current directory
if filereadable("workspace.vim")
source workspace.vim
endif

corte-m3中的pc值的问题

        在Cortex-M3中指令是3级流水线,出于对Thumb代码的兼容的考虑,读取pc时,会返回当前指令地址+4的值。但是在进入中断服务程序后,压入栈的地址是pc,而返回时也是用作pc,那么pc的值如果是当前指令加4的话就有可能返回后就漏掉一条指令。
        但是事实上中断服务程序可以完美的返回。那么这中间就一定存在着某些诡异的地方。
        下面来看一段测试代码:

    我们可看出,pc总是指向下一条指令,那么书中的“出于对Thumb代码的兼容的考虑,读取pc时,会返回当前指令地址+4的值”的关键在于“读取”2字,如果我们使用指令(如mov)读了PC,那么返回给我们地址就是当前地址+4,如果是单片机自动压栈那么就不属于使用指令读取的范畴,也就是说压入栈中的PC值就真正的值。

关于keil编译cortex-m3纯汇编时为什么问题使用align地址问题

  在编译下面一段代码时:

STACK_TOP	EQU	0x20002000
	AREA	Reset,CODE,READONLY
	DCD	0x20002000
	DCD	Start
	ENTRY
;	CODE16 

Start
	ldr	r2,=Test
  	LDRD r0,r1,[r2,#4]
	LDRD r0,r1,[r2]
	LDRD r0,r1,[r2]
;	movs	r0,r0
;	NOP
;	align 4
Test
	DCD	0x12345678
	END

我发现,如果加上NOP或align4,程序就不会跑飞,否则程序就跑飞了。

  经调试发现:    如果不加NOP或align 4的话产生的Test的标号地址就会产生错误,而LDRD 指令操作的地址必须是4字节对节的,如果使用的地址不是四字节对齐,那么程序就会产生异常,所以程序就跑飞了。  那么为什么不加NOP或align 4的话Test标号地址就会产生错误呢?  来看一段手册上的话:
也就是说DCD是需要标号地址按字对齐的,如果你没有对齐就可以看到如下的编译警告:test.asm(18): warning: A1581W: Added 2 bytes of padding at address 0x1a
这说明编译器会自动添加两个字节来帮你对齐,数据分布情况和下面很相似:

这说明编译器会自动添加两个字节来帮你对齐,数据分布情况和下面很相似:

STACK_TOP	EQU	0x20002000
	AREA	Reset,CODE,READONLY
	DCD	0x20002000
	DCD	Start
	ENTRY
;	CODE16 

Start
	ldr	r2,=Test
  	LDRD r0,r1,[r2,#4]
	LDRD r0,r1,[r2]
	LDRD r0,r1,[r2]
;	movs	r0,r0
;	NOP
;	align 4
Test
	dcb	00	    ;编译器自动添加
	dcb	00      ;编译器自动添加,而movs	r0,r0的机器码就是0x0000,会被	
	            ;编译器翻译成movs r0,r0,不是当作数据0x0000
	DCDU	0x12345678
	END

也许看来这样就完美了,但是程序依然会跑飞。原因有两点:
 1.即使加了两个字节那么Test的标号地址依然不是四字节对齐。

 2.这两个字节的零会被编译器当作指令来处理的,这也就是说Test标号会被编译器来当作代码标号来处理,看到了吧,我们的数据编译器一插手就变成代码了,实在无奈的很。再来看一段手册上的讲解:

                      

也就是实际上LDR r2,=Test执行后,r2=Test+1这也解释了为什么不加NOP或align 4的话r2=0x8000017而加了NOP或Align 4就r2=0x8000018。那么来看一下,加nop或align 4后的效果:

STACK_TOP	EQU	0x20002000
	AREA	Reset,CODE,READONLY
	DCD	0x20002000
	DCD	Start
	ENTRY
;	CODE16 

Start
	ldr	r2,=Test
  	LDRD r0,r1,[r2,#4]
	LDRD r0,r1,[r2]
	LDRD r0,r1,[r2]
;	movs	r0,r0
	; 如果是align 4会被加两个节字的movs r0,r0(机器码为0x0000)
	; 如果是nop 则会被加上nop的机器码(0xBF00)

;	NOP

	align 4
Test
	DCDU	0x12345678
	END

需要说明的是,我总是把align
和nop 放在一块说,并不是说nop也具有对齐作用。是因为加上nop后刚好可以使Test标号地址放在4字节对齐的其他地方。在其他地方,nop也许并无此作用。