文档库 最新最全的文档下载
当前位置:文档库 › Linux内核解析 0.6

Linux内核解析 0.6

Linux内核解析 0.6
Linux内核解析 0.6

Linux 内核解析

I Bootstrap

1 汇编代码分析

2 start_kernel函数

3 准备进入用户态

3.1 Initrd初始化

3.1.0 准备知识

在讲述如何释放initrd到rootfs之前,有比较讲述一下什么是rootfs,rootfs的初始化相关的函数;以及rootfs 的初始化函数是如何被调用的。

这里所说的rootfs指的是VFS的根节点/,以及在内存中创建的根目录/下的文件和目录节点,这个文件系统仅仅存在于内存之中,由内核初始化的时候负责创建,该文件系统不会存储到其它非易失性介质上。该rootfs文件系统mnt_init函数调用init_rootfs和init_mount_tree两个函数来负责创建和初始化:

void __init mnt_init(void)

{

......

//这个函数很简单,就是注册了rootfs 的文件系统。

init_rootfs();

//在这里,将rootfs 文件系统挂载,它的挂载点默认为”/”。

//最后切换进程的根目录和当前目录为”/”,这也就是根目录的由来。

//不过这里只是初始化,等挂载完具体的文件系统之后,

//一般都会将根目录切换到具体的文件系统,所以在系统启动之后,

//用mount 命令是看不到rootfs 的挂载信息的。

init_mount_tree();

}

有了rootfs后,就可以将initrd的image释放到rootfs中了,至于哪个函数完成这项工作?在讲述该函数之前,我们首先看看该函数是如何被调用的。首先看kernel_init函数中的do_basic_setup函数:

static int __init kernel_init(void * unused)

{

......

do_basic_setup();

}

do_basic_setup()是一个很关键的函数,所有直接编译在kernel 中的模块都是由它启动的。

/*

* Ok, the machine is now initialized. None of the devices

* have been touched yet, but the CPU subsystem is up and

* running, and memory and process management works.

*

* Now we can finally start doing some real work..

*/

//注意上面的关于该函数的注释:CPU和进程管理模块已经正常工作,但是外设还没初始化。

static void __init do_basic_setup(void)

{

cpuset_init_smp();

usermodehelper_init();

init_tmpfs();

driver_init();

init_irq_proc();

do_ctors();

//启动所有在__initcall_start 和__initcall_end 段的函数,

//而静态编译进内核的modules 也会将其入口放置在这段区间里。

do_initcalls();

}

将initrd的image释放到rootfs中的工作是由populate_rootfs函数完成,该函数由rootfs_initcall()所引用。注意到有以下初始化函数:

rootfs_initcall(populate_rootfs);

如此:也就是说会在系统初始化的时候,也就是do_initcalls被调用的时候,会调用populate_rootfs 进行初始化。

3.1.1 释放initrd

总的来说,rootfs 分为两种:虚拟rootfs 和真实rootfs。现在kernel 的发展趋势是将更多的功能放到用户空间完成,以保持内核的精简。虚拟rootfs 也是各linux 发行厂商普遍采用的一种方式,可以将一部份的初始化工作放在虚拟的rootfs 里完成,然后切换到真实的文件系统。在虚拟rootfs 的发展过程中,又有以下几个版本:

Initramfs:Initramfs 是在kernel 2.5中引入的技术,实际上它的含义就是:在内核镜像中附加一个cpio 包,这个cpio包中包含了一个小型的文件系统,当内核启动时,内核将这个cpio 包解开,并且将其中包含的文件系统释放到rootfs 中,内核中的一部分初始化代码会放到这个文件系统中,作为用户层进程来执行。这样带来的明显的好处是精简了内核的初始化代码,而且使得内核的初始化过程更容易定制。这种这种方式的rootfs 是包含在kernel image 之中的。

cpio-initrd: cpio 格式的rootfs

image-initrd:传统格式的rootfs

将在下文具体讲述这两种initrd。

populate_rootfs代码如下:

/*

* 处理Initramfs/cpio-initrd/image-initrd三个格式的image。

* 主要功能实现了将Initramfs/cpio-initrd两种格式的image释放到rootfs;

* 不同不出image-initrd这种老式的initrd格式,需要依赖其它手段,后续会讲。

*/

static int __init populate_rootfs(void)

{

/*

* 第一种情况是跟kernel融为一体的initramfs,从内核2.5引入这种技术。在编译kernel的时候,通过链接脚本

* 将其存放在__initramfs_start 至__initramfs_end 的区域。这种情况下,

* 直接调用unpack_to_rootfs 将其释放到根目录/。

*

* 如果不是属于initramfs这种形式的,也就是__initramfs_start 和__initramfs_end 的值相等,

* 长度为零,不会做任何处理,退出;err此时也为NULL。

*/

char *err = unpack_to_rootfs(__initramfs_start, __initramfs_size);

if (err)

panic(err); /* Failed to decompress INTERNAL initramfs */

/*

* 紧接着处理initrd格式,而initrd又有两种格式:Image-initrd和CPIO-initrd。

* 由initrd_start变量是否为0,来判断内核是否接受了参数:“initrd=”,如果有参数“initrd=”,* 由下面的代码对initrd进行处理,处理过程又根据initrd的两种格式而不尽相同:

* CPIO-initrd:会被直接释放到rootfs;

* Image-initrd:暂时不会被释放到rootfs,而是将initrd image拷贝到/initrd.image文件中。

*/

if (initrd_start) {

/*

* 必须要配制CONFIG_BLK_DEV_RAM 才会支持image-initrd。否则全当成cpio-initrd 的形式处理。

*/

#ifdef CONFIG_BLK_DEV_RAM

int fd;

printk(KERN_INFO "Trying to unpack rootfs image as initramfs...\n");

err = unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start);

//如果unpack_to_rootfs返回正常

if (!err) {

free_initrd();

return 0;

} else {

//如果释放initrd到根目录发生错误,重新释放initramfs。

clean_rootfs();

unpack_to_rootfs(__initramfs_start, __initramfs_size);

}

printk(KERN_INFO "rootfs image is not initramfs (%s)"

"; looks like an initrd\n", err);

fd = sys_open((const char __user __force *) "/initrd.image", O_WRONL Y|O_CREAT, 0700);

/*

* 将initrd写入/initrd.image文件

* 看上去image-initrd和cpio-initrd两种格式的image都会被写入/initrd.image

* 这看上去不太合理,因为unpack_to_rootfs能够正确处理cpio-initrd,就不需要

* 拷贝到/initrd.image了;其实内核是根据是否配置CONFIG_BLK_DEV_RAM来

* 判断是否支持image-initrd,如果不支持image-initrd,就不要配置CONFIG_BLK_DEV_RAM,* 内核就会编译下面的宏定义段内代码,就不会拷贝initrd到/initrd.image了。

*/

if (fd >= 0) {

sys_write(fd, (char *)initrd_start, initrd_end - initrd_start);

sys_close(fd);

/*

* 对于是image-initrd 的情况。将其释放到/initrd.image.

* 最后将initrd 内存区域归入伙伴系统。这段内存就可以由操作系统来做其它的用途了。

*/

free_initrd();

}

#else

printk(KERN_INFO "Unpacking initramfs...\n");

err = unpack_to_rootfs((char *)initrd_start,

initrd_end - initrd_start);

if (err)

printk(KERN_EMERG "Initramfs unpacking failed: %s\n", err);

free_initrd();

#endif

}

return 0;

}

rootfs_initcall(populate_rootfs);

/*

* 正确,返回NULL;否则返回一个C语言格式字符串。

*

* 顾名思义就是解压包,并将其释放至rootfs。

* 在这个函数里,对应如下的三种虚拟根文件系统的情况:

* Initramfs 是在kernel 2.5中引入的技术,实际上它的含义就是:在内核镜像中附加一个cpio 包,这个cpio * 包中包含了一个小型的文件系统,当内核启动时,内核将这个cpio 包解开,并且将其中包含的文件系* 统释放到rootfs 中,内核中的一部分初始化代码会放到这个文件系统中,作为用户层进程来执行。这样* 带来的明显的好处是精简了内核的初始化代码,而且使得内核的初始化过程更容易定制。这种这种方* 式的rootfs 是包含在kernel image 之中的.

* cpio-initrd: cpio 格式的rootfs

* image-initrd:传统格式的rootfs

* 关于这两种虚拟文件系统,请参考后续章节。

*

*===========================================================================

* 一种是跟kernel 融为一体的initramfs,在编译kernel的时候,通过链接脚本

* 将其存放在__initramfs_start 至__initramfs_end 的区域。这种情况下,

* 直接调用unpack_to_rootfs 将其释放到根目录。

* 如果不是属于这种形式的,也就是__initramfs_start 和__initramfs_end 的值相等,

* 长度为零。不会做任何处理,退出。

* 对应后两种情况,image-initrd不会被释放到rootfs,该格式的处理细节后文继续讲解;

* 也就是说unpack_to_rootfs不能正确处理image-initrd,需要依赖其它手段。

* 对于是cpio-initrd 的情况,直接将其释放到根目录。

*/

static char * __init unpack_to_rootfs(char *buf, unsigned len)

{

int written, res;

decompress_fn decompress;

const char *compress_name;

static __initdata char msg_buf[64];

header_buf = kmalloc(110, GFP_KERNEL);

symlink_buf = kmalloc(PATH_MAX + N_ALIGN(PATH_MAX) + 1, GFP_KERNEL);

name_buf = kmalloc(N_ALIGN(PA TH_MAX), GFP_KERNEL);

if (!header_buf || !symlink_buf || !name_buf)

panic("can't allocate buffers");

state = Start;

this_header = 0;

message = NULL;

while (!message && len) {

loff_t saved_offset = this_header;

if (*buf == '0' && !(this_header & 3)) {

state = Start;

written = write_buffer(buf, len);

buf += written;

len -= written;

continue;

}

if (!*buf) {

buf++;

len--;

this_header++;

continue;

}

this_header = 0;

decompress = decompress_method(buf, len, &compress_name);

if (decompress) {

res = decompress(buf, len, NULL, flush_buffer, NULL,

&my_inptr, error);

if (res)

error("decompressor failed");

} else if (compress_name) {

if (!message) {

snprintf(msg_buf, sizeof msg_buf,

"compression method %s not configured",

compress_name);

message = msg_buf;

}

} else

error("junk in compressed archive");

if (state != Reset)

error("junk in compressed archive");

this_header = saved_offset + my_inptr;

buf += my_inptr;

len -= my_inptr;

}

dir_utime();

kfree(name_buf);

kfree(symlink_buf);

kfree(header_buf);

return message;

}

3.2 initrd

到目前为止,initrd技术虽然是一种比较过时但还是一种比较常用的技术。Linux Kernel2.6的initrd的文件系统镜像文件变成了cpio格式;变化不仅仅体现在文件格式上,内核对这两个格式的initrd有着截然不同的处理方法。本节首先介绍什么是initrd技术,然后介绍了3.0内核的initrd的处理流程。

Initrd的含义是bootloader initialized RAM Disk,就是由bootloader初始化的内存盘。在Linux内核启动之前,bootloader会将存储介质中的initrd文件加载到内存,内核启动时会在加载rootfs之前首先访问内存中的initrd文件系统。在bootloader配置了initrd的情况下,内核启动被分为两个阶段:第一阶段首先执行initrd 中的某个文件(/linuxrc或者/init,下文会详细讲),文成一些特定的任务,如加载RAID驱动等;第二阶段才是真正执行rootfs中的/sbin/init,以启动init进程。

Initrd主要有以下四个优势:

1 动态适应底层硬件平台:Linux发行版必须支持各种各样的底层平台,将所有的驱动都编译进内核是不太现实的,而且内核会常驻内存,这也会造成内存资源的极大浪费。Linux发行版的内核中只会包含最基本的一些驱动;而通过在安装过程中检测系统硬件,动态生成底层平台依赖的initrd无非是一种既可行又灵活的解决方案。

2 用于LiveCD:LiveCD可能面对复杂的硬件环境,所以也必须使用initrd。

3 制作USB启动盘时必须使用initrd:USB设备是一个启动比较慢的设备,从驱动加载到设备真正可用大概需要几秒的时间。如果将USB驱动编译进内核,内核通常不能正确访问USB中的文件系统,因为内核访问USB时,USB设备还没有初始化完毕。所以通常的做法是:在initrd中加载USB驱动,然后休眠几秒钟,等待USB设备初始化完毕后再去挂载USB中的文件系统。

4 initrd(脚本)很灵活,而且是工作在用户态,可以在其中很方便的定制一些功能,如系统安全相关的设

置。

在深入介绍内核2.6及其以后版本使用的initrd机制之前,先简单介绍内核2.4中的initrd机制。内核2.4中的initrd的文件格式是一个文件系统镜像文件,姑且称之为image-initrd,以区分内核2.6及其以后的CPIO 格式的initrd。内核2.4对initrd的处理流程如下:

1 bootloader将内核和initrd两个image加载到内存,经过一些必要的初始化后,将控制权交给内核;

2 内核经过一些进一步的初始化后,将initrd所在的内存区域(由bootloader告知内核)设置为/dev/initrd 设备;然后kernel 透过内部的decompressor (gzip 解压缩) 解开该内容并复制到/dev/ram0 装置设备上;

3 内核以R/W (可读写) 模式将/dev/ram0 挂载为暂时性的rootfs;

4 kernel 准备执行/dev/ram0 上的/linuxrc ,该程序(脚本)是在用户态执行的;如果/dev/ram0 被指定为真正的根文件系统,那么内核跳至第7步正常启动;

5 /linuxrc与相关程序处理特定的操作;linuxrc 通常是一个脚本文件,负责加载内核访问根文件系统必须的驱动,以及加载根文件系统;

6 /linuxrc 执行即将完毕,执行权转交给kernel;如果rootfs中存在/initrd 目录,那么/dev/ram0 将从/ 移动到/initrd。否则如果/initrd 目录不存在,/dev/ram0 将被卸载。

7 Linux 挂载真正的rootfs 并执行/sbin/init 程式

8 依据Linux distribution 规范的流程,执行各式系统与应用程式;linux2.4 内核的initrd 的执行是作为内核启动的一个中间阶段,也就是说initrd 的/linuxrc 执行以后,内核会继续执行初始化代码,我们后面会看到这是linux2.4 内核同2.6 内核的initrd 处理流程的一个显著区别。

内核2.6支持两种格式的initrd,一种是前面提到的传统格式个image-initrd,其核心文件是/linuxrc;另外一种格式是CPIO,这种格式从内核2.5开始引入,使用CPIO工具生成,其核心文件是/init,姑且称这种initrd为CPIO-initrd。尽管内核2.6对image-initrd和CPIO-initrd都有支持,但处理流程却有着显著的区别。下面分别介绍内核2.6对这两种格式的initrd的处理流程:

CPIO-initrd的处理流程:

1 Bootloader将内核initrd加载到内存特定位置;

2 内核判断initrd是否是CPIO格式,如果是:

3 将initrd的内容释放到rootfs中;

4 执行initrd中的/init文件;

5 执行完/init后,控制权再交给内核

Image-initrd的处理流程:

1 Bootloader将内核initrd加载内存特定位置;

2 内核判断initrd格式,如果不是CPIO格式,则作为Image-initrd格式处理;

3 将initrd的内容保存在rootfs的/initrd.image文件中;

4 将/initrd.image中的内容读入到/dev/ram0中,也就是读入了一个内存盘中;

5 接着内核以读写的方式将/dev/ram0挂载原始的根文件系统;

6 如果/dev/ram0被指定为rootfs,跳转到最后一步进行正常启动;

7 执行/linuxrc文件,linuxrc通常是一个脚本,负责加载根文件需要的驱动,以及加载根文件系统;

8 /linuxrc执行完毕,控制权交给内核,常规跟文件系统被挂载;

9 如果常规根文件系统中存在/initrd目录,那么/dev/ram0将从/移动到/initrd;否则如果/initrd不存在,/dev/ram0将被卸载;

10 在rootfs上进行启动,执行/sbin/init。

通过上面的流程可知,内核2.6对image-initrd的处理流程相较内核2.4并无明显变化;CPIO-image相较image-initrd有很大差别,流程非常简单。

CPIO-image相较image-initrd,有一些区别和优势:

1 CPIO-initrd的制作比较简单,通过以下两个命令即可完成:

#假设当前目录位于准备好的initrd文件系统的根目录下

bash# find . | cpio -c -o > ../initrd.img

bash# gzip ../initrd.img

而image-initrd的制作相对繁琐:

#假设当前目录位于准备好的initrd文件系统的根目录下

bash# dd if=/dev/zero of=../initrd.img bs=512k count=5

bash# mkfs.ext2 -F -m0 ../initrd.img

bash# mount -t ext2 -o loop ../initrd.img /mnt

bash# cp -r * /mnt

bash# umount /mnt

bash# gzip -9 ../initrd.img

2 CPIO-image的内核处理流程简单

通过上面的对CPIO-initrd处理流程的介绍,CPIO-initrd的处理流程也显得格外简单,CPIO-initrd主要在以下两个方面做了简化:

a CPIO-image并没有使用额外的RamDisk,这样就省略了RamDisk的挂载和卸载工作;

b CPIO-initrd启动完/init进程,内核任务就结束了,剩下的工作都交给/init处理;而对于image-initrd,内核在执行完/linuxrc之后,还需要等待其结束,再进行一些收尾工作,并且需要负责执行rootfs中的/sbin/init。可以通过下面的简化的流程图更加清晰表示两者之间的区别:

CPIO-initrd的职能更加重要,不再像image-initrd那样是Linux启动过程的一个中间步骤,而是作为内核启

动的终极步骤,内核将控制权交给/init后就完了,所以CPIO-initrd可以做更多的工作,而不需要担心和内核后续处理的衔接问题。

3.2.2 内核2.6 Initrd处理代码分析

下面对内核2.6中与initrd相关的代码进行详细分析。首先阐述几个相关的概念;

rootfs:一个驻留在内存中的文件系统,也是Linux初始化时构建的第一个文件系统,其实VFS。initramfs:和本文的主题关系不是太大,但是代码涉及到了initramfs,为了更好地理解代码,对其做简单介绍。Initramfs是内核2.5引入的技术,它的实际含义是,内核镜像中附加一个CPIO包,该CPIO包中包含了一个小型的文件系统,当内核启动的时候,内核将包解开,并且将其中包含的文件系统释放到rootfs 中。内核中的一部分初始化代码会放到这个文件系统中,作为用户态程序来运行。这样明显的好处是简化了内核的初始化代码,而且使得内核的初始化过程更加容易定制。Linux内核2.6.12中的initramfs还没有什么实质性的内容,一个完整的initramfs的实现还需要一个漫长的过程。

realfs:用户最终使用文件系统。

内核初始化代码起始于start_kernel,和initrd相关的代码起始于kernel_init,下图列出了跟该initrd相关的函数调用关系:

首先从kernel_init函数开始分析,我们省略了该函数中跟initrd无关的代码。

static int __init kernel_init(void * unused)

{

//省略跟initrd无关的部分

if (!ramdisk_execute_command)

ramdisk_execute_command = "/init";

if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {

//返回值!= 0,表示不存在该文件;所以就会使用老式的方法去执行/linuxrc,也就是传统的initrd 格式

ramdisk_execute_command = NULL;

prepare_namespace();

}

/*

* Ok, we have completed the initial bootup, and

* we're essentially up and running. Get rid of the

* initmem segments and start the user-mode stuff..

*/

init_post();

return 0;

}

从上面的代码可知,根据rootfs中是否存在ramdisk_execute_command文件,即/init文件,处理流程分支为Image-initrd和GPIO-initrd。

3.2.2.1 GPIO-initrd处理

我们首先看看GPIO-initrd启动的完整代码,有上面介绍的kernel_init函数可知,该过程从init_post函数开始:

static noinline int init_post(void)

{

//省略跟initrd无关的代码

......

//假如变量ramdisk_execute_command被设置;

//ramdisk_execute_command一般在函数kernel_init被设置为/init

if (ramdisk_execute_command) {

run_init_process(ramdisk_execute_command);

printk(KERN_W ARNING "Failed to execute %s\n",

ramdisk_execute_command);

}

/*

* We try each of these until one succeeds.

*

* The Bourne shell can be used instead of init if we are

* trying to recover a really broken machine.

*/

//由于一些历史原因,有一些不同的init程序文件存在,我们会依次测试每一种合理的

//可能性;如果run_init_process执行某个文件成功,则不会返回了,也就从内核状态

//调用用户态程序,然后返回到了用户态。关于细节请看run_init_process函数的实现。

if (execute_command) {

run_init_process(execute_command);

printk(KERN_W ARNING "Failed to execute %s. Attempting "

"defaults...\n", execute_command);

}

run_init_process("/sbin/init");

run_init_process("/etc/init");

run_init_process("/bin/init");

run_init_process("/bin/sh");

panic("No init found. Try passing init= option to kernel. "

"See Linux Documentation/init.txt for guidance.");

}

run_init_process函数定义如下,该函数只是一个对kernel_execve函数的简单wrap。

static void run_init_process(const char *init_filename)

{

argv_init[0] = init_filename;

kernel_execve(init_filename, argv_init, envp_init);

}

kernel_execve函数的定义如下:

int kernel_execve(const char *filename,

const char *const argv[],

const char *const envp[])

{

struct pt_regs regs;

int ret;

memset(®s, 0, sizeof(struct pt_regs));

//加载并执行一个新的可执行程序

ret = do_execve(filename,

(const char __user *const __user *)argv,

(const char __user *const __user *)envp, ®s);

if (ret < 0)

goto out;

/*

* Save argc to the register structure for userspace.

*/

//将do_execve的返回值存入regs.ARM_r0;

//因为ARM规定,使用R0返回函数的执行结果。

regs.ARM_r0 = ret;

/*

* We were successful. We won't be returning to our caller, but

* instead to user space by manipulating the kernel stack.

*/

asm( "add r0, %0, %1\n\t"

"mov r1, %2\n\t"

"mov r2, %3\n\t"

"bl memmove\n\t" /* copy regs to top of stack */

"mov r8, #0\n\t" /* not a syscall */

"mov r9, %0\n\t" /* thread structure */

"mov sp, r0\n\t" /* reposition stack pointer */

"b ret_to_user"

:

: "r" (current_thread_info()),

"Ir" (THREAD_START_SP - sizeof(regs)),

"r" (®s),

"Ir" (sizeof(regs))

: "r0", "r1", "r2", "r3", "ip", "lr", "memory");

out:

return ret;

}

EXPORT_SYMBOL(kernel_execve);

3.2.2.2 Image-initrd处理

为处理Image-initrd,首先将ramdisk_execute_command设置为NULL,然后调用prepare_namespace函数,如下面的代码所示:

if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {

//返回值!= 0,表示不存在该文件;所以就会使用老式的方法去执行/linuxrc,也就是传统的initrd格式ramdisk_execute_command = NULL;

prepare_namespace();

}

/*

* Prepare the namespace - decide what/where to mount, load ramdisks, etc.

*

* 该函数完成很多工作,几乎是Image-initrd流程的全部工作:

* 0 这个步骤是有bootloader完成的:boot loader把内核以及initrd文件加载到内存的特定位置

* 1 内核将initrd的内容保存在rootfs下的/initrd.image文件中

* 2 内核将/initrd.image的内容读入/dev/ram0设备中

* 3 接着内核以可读写的方式把/dev/ram0设备挂载为原始的根文件系统

* 4 执行initrd上的/linuxrc文件

* 5 等待/linuxrc(linuxrc是通过另外一个进程去执行的,所以内核使用wait系统调用来等待linuxrc进程)执行完毕

* 6 将real root device mount到/root目录,且当前目录就是/root

* 7 由该函数倒数第二行将/root挂载到/。

*

* 但是该函数没有执行/sbin/init程序。该程序的执行实在init_post函数中,该函数实现比较巧妙:

* 1 首先看ramdisk_execute_command是否被设置,如果被设置,一般设置成/init;则/init作为第一个进程开始执行,就是CPIO-initrd

* 中执行的步骤;

* 2 在就是看"/sbin/init"这个可执行文件

* 3 还会查看其它类似文件,具体请看init_post函数的实现。

*

* 由此可见,在内核3.0中,为支持Image-initrd,就会多执行image-initrd相关的操作,最后也会到init_post 函数;

* 而CPIO-image会直接调用init_post函数;在init_post函数中根据ramdisk_execute_command参数是否被设置来决定执行:

* /init(包含在CPIO-initrd)还是/sbin/init(包含在real root device)。

*

*

* 由此可见,CPIO-initrd在/init就交出了控制权,而Image-initrd在/linuxrc暂时交出控制权,然后又回收,最后将控制权交给了/sbin/init。

*

*/

void __init prepare_namespace(void)

{

int is_floppy;

if (root_delay) {

printk(KERN_INFO "Waiting %dsec before mounting root device...\n",

root_delay);

ssleep(root_delay);

}

/*

* wait for the known devices to complete their probing

*

* Note: this is a potential source of long boot delays.

* For example, it is not atypical to wait 5 seconds here

* for the touchpad of a laptop to initialize.

*/

wait_for_device_probe();

//软件磁盘阵列相关的设备: /dev/md0

md_run_setup();

if (saved_root_name[0]) {

root_device_name = saved_root_name;

//如果root device是mtd或者ubi

if (!strncmp(root_device_name, "mtd", 3) ||

!strncmp(root_device_name, "ubi", 3)) {

mount_block_root(root_device_name, root_mountflags);

goto out;

}

ROOT_DEV = name_to_dev_t(root_device_name);

//去掉传给kernel的root参数的前5个字符:/dev/

if (strncmp(root_device_name, "/dev/", 5) == 0)

root_device_name += 5;

}

/*

* 加initrd加载到内存,具体的工作分为如下几步:

* 0 这个步骤是有bootloader完成的:boot loader把内核以及initrd文件加载到内存的特定位置

* 1 内核将initrd的内容保存在rootfs下的/initrd.image文件中

* 2 内核将/initrd.image的内容读入/dev/ram0设备中

* 3 接着内核以可读写的方式把/dev/ram0设备挂载为原始的根文件系统

* 4 执行initrd上的/linuxrc文件

* 5 等待/linuxrc(linuxrc是通过另外一个进程去执行的,所以内核使用wait系统调用来等待linuxrc 进程)执行完毕

* 6 将real root device mount到/root目录,且当前目录就是/root

*/

if (initrd_load())

goto out;

/* wait for any asynchronous scanning to complete */

if ((ROOT_DEV == 0) && root_wait) {

printk(KERN_INFO "Waiting for root device %s...\n",

saved_root_name);

while (driver_probe_done() != 0 ||

(ROOT_DEV = name_to_dev_t(saved_root_name)) == 0)

msleep(100);

async_synchronize_full();

}

is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;

if (is_floppy && rd_doload && rd_load_disk(0))

ROOT_DEV = Root_RAM0;

mount_root();

out:

devtmpfs_mount("dev");

//将real root device挂载到/;原先挂载在/root下。

//至此,我们已经完成image-initrd的挂载、执行其中的/linuxrc并等待结束、通过mount_root将//real root device挂载到/root目录、如果可能就将/dev/ram挂载/root/initrd等一系列工作。

//其实到此我们已经完成image-initrd相关的所有工作;

//下面一句就是将/root(real root device)挂载到/目录,也就完成了image-initrd的中间步骤。

sys_mount(".", "/", NULL, MS_MOVE, NULL);

sys_chroot((const char __user __force *)".");

}

接着,我们来详细看看initrd_load函数的细节:

int __init initrd_load(void)

{

if (mount_initrd) {

//创建/dev/ram这个设备节点

create_dev("/dev/ram", Root_RAM0);

/*

* Load the initrd data into /dev/ram0. Execute it as initrd

* unless /dev/ram0 is supposed to be our actual root device,

* in that case the ram disk is just set up here, and gets

* mounted in the normal path.

*/

//由上面的内核注释可以比较清楚地看出下面几句的含义,这样清晰的注释在内核并不多见//首先通过rd_load_image函数将initrd image从/initrd.image加载到/dev/ram0

//接着判断如果/dev/ram0不是真正的根文件系统,执行if中的两个函数,其实就是

//image-initrd挂载的其他步骤,在分析handle_initrd的时候给出细节。

if (rd_load_image("/initrd.image") && ROOT_DEV != Root_RAM0) {

sys_unlink("/initrd.image");

handle_initrd();

return 1;

}

}

sys_unlink("/initrd.image");

return 0;

}

省略一些无关紧要的代码。

/*

* 正确返回1;

*/

int __init rd_load_image(char *from)

{

int res = 0;

int in_fd, out_fd;

unsigned long rd_blocks, devblocks;

int nblocks, i, disk;

char *buf = NULL;

unsigned short rotate = 0;

decompress_fn decompressor = NULL;

#if !defined(CONFIG_S390) && !defined(CONFIG_PPC_ISERIES)

char rotator[4] = { '|' , '/' , '-' , '\\' };

#endif

//【1】分别打开存放initrd image的源文件和目的文件

out_fd = sys_open((const char __user __force *) "/dev/ram", O_RDWR, 0);

if (out_fd < 0)

goto out;

in_fd = sys_open(from, O_RDONL Y, 0);

if (in_fd < 0)

goto noclose_input;

/*【2】

* 该函数只是用来验证initrd image,该image目前存储在/initrd.image文件中:* 1 returns the number of blocks to read for a non-compressed image

* 2 returns 0 if the image is a compressed image,

* 3 returns -1 if an image with the right magic numbers could not be found.

*/

nblocks = identify_ramdisk_image(in_fd, rd_image_start, &decompressor);

if (nblocks < 0)

goto done;

/*【3】

* /initrd.image中存储的是压缩格式的initrd image。

* 将in_fd(/initrd.image)中的内容解压到out_fd(/dev/ram)。

*/

if (nblocks == 0) {

/*

* 如果initrd image是压缩的,通过crd_load函数将image解压到out_fd

*/

if (crd_load(in_fd, out_fd, decompressor) == 0)

goto successful_load;

goto done;

}

//【4】如果initrd是非压缩的,将其从/initrd.image拷贝到/dev/ram中

/*

* NOTE NOTE: nblocks is not actually blocks but

* the number of kibibytes of data to load into a ramdisk.

* So any ramdisk block size that is a multiple of 1KiB should

* work when the appropriate ramdisk_blocksize is specified

* on the command line.

*

* The default ramdisk_blocksize is 1KiB and it is generally

* silly to use anything else, so make sure to use 1KiB

* blocksize while generating ext2fs ramdisk-images.

*

* 该函数返回out_fd中的块的个数,即/dev/ram的块的个数。

*/

if (sys_ioctl(out_fd, BLKGETSIZE, (unsigned long)&rd_blocks) < 0)

rd_blocks = 0;

else

rd_blocks >>= 1;

/*

* 如果将in_fd(/initrd.image)中的块的数目大于out_fd(/dev/ram)中的一半,出错。* 如果块的个数大于块的大小的一半时,出错。

*/

if (nblocks > rd_blocks) {

printk("RAMDISK: image too big! (%dKiB/%ldKiB)\n",

nblocks, rd_blocks);

goto done;

}

/*

* OK, time to copy in the data

*/

if (sys_ioctl(in_fd, BLKGETSIZE, (unsigned long)&devblocks) < 0)

devblocks = 0;

else

devblocks >>= 1;

if (strcmp(from, "/initrd.image") == 0)

devblocks = nblocks;

if (devblocks == 0) {

printk(KERN_ERR "RAMDISK: could not determine device size\n");

goto done;

}

buf = kmalloc(BLOCK_SIZE, GFP_KERNEL);

if (!buf) {

printk(KERN_ERR "RAMDISK: could not allocate buffer\n");

goto done;

}

printk(KERN_NOTICE "RAMDISK: Loading %dKiB [%ld disk%s] into ram disk... ",

nblocks, ((nblocks-1)/devblocks)+1, nblocks>devblocks ? "s" : "");

for (i = 0, disk = 1; i < nblocks; i++) {

if (i && (i % devblocks == 0)) {

printk("done disk #%d.\n", disk++);

rotate = 0;

if (sys_close(in_fd)) {

printk("Error closing the disk.\n");

goto noclose_input;

}

change_floppy("disk #%d", disk);

in_fd = sys_open(from, O_RDONL Y, 0);

if (in_fd < 0) {

printk("Error opening disk.\n");

goto noclose_input;

}

printk("Loading disk #%d... ", disk);

}

/*【5】将initrd从/initrd.image拷贝到/dev/ram的两行核心函数,【4】和【5】之间全部是合法性判断

* 对拷两个文件,似乎没有什么好办法:都是从一个文件读进一个buffer,然后从buffer写入另一个文件。

*/

sys_read(in_fd, buf, BLOCK_SIZE);

sys_write(out_fd, buf, BLOCK_SIZE);

#if !defined(CONFIG_S390) && !defined(CONFIG_PPC_ISERIES)

if (!(i % 16)) {

printk("%c\b", rotator[rotate & 0x3]);

rotate++;

}

}

printk("done.\n");

successful_load:

res = 1;

done:

sys_close(in_fd);

noclose_input:

sys_close(out_fd);

out:

kfree(buf);

/*【6】

* 为什么需要删除"/dev/ram"

* 因为在后续函数handle_initrd中,重新调用了create_dev("/dev/root.old", Root_RAM0);

*/

sys_unlink((const char __user __force *) "/dev/ram");

return res;

}

/*

* 该函数主要完成将Root_RAM0挂载到/目录,并创建一个进程执行/linuxrc,并等待其结束。* 在linuxrc结束后,通过mount_root挂载real root device到/root;

* 如果存在/root/initrd,将Root_RAM0挂载到/root/initrd下面;否则卸载Root_RAM0。

*/

static void __init handle_initrd(void)

{

int error;

int pid;

real_root_dev = new_encode_dev(ROOT_DEV);

create_dev("/dev/root.old", Root_RAM0);

/*

* 注意注释:

* mount initrd on rootfs' /root

*/

mount_block_root("/dev/root.old", root_mountflags & ~MS_RDONL Y);

sys_mkdir("/old", 0700);

root_fd = sys_open("/", 0, 0);

old_fd = sys_open("/old", 0, 0);

/* move initrd over / and chdir/chroot in initrd root */

/*

* 下面两句合起来就是将Root_RAM0设备挂载到/目录

* 亦即完成了initrd的挂载到/的任务。

Linux内核修改与编译图文教程

Linux 内核修改与编译图文教程 1

1、实验目的 针对Ubuntu10.04中,通过下载新的内核版本,并且修改新版本内核中的系统调用看,然后,在其系统中编译,加载新内核。 2、任务概述 2.1 下载新内核 https://www.wendangku.net/doc/3818356579.html,/ 2.2 修改新内核系统调用 添加新的系统调用函数,用来判断输入数据的奇偶性。 2.3 进行新内核编译 通过修改新版内核后,进行加载编译。最后通过编写测试程序进行测试 3、实验步骤 3.1 准备工作 查看系统先前内核版本: (终端下)使用命令:uname -r 2

3.2 下载最新内核 我这里使用的内核版本是 3.3 解压新版内核 将新版内核复制到“/usr/src”目录下 在终端下用命令:cd /usr/src进入到该文件目录 解压内核:linux-2.6.36.tar.bz2,在终端进入cd /usr/src目录输入一下命令: bzip2 -d linux-2.6.36.tar.bz2 tar -xvf linux-2.6.36.tar 文件将解压到/usr/src/linux目录中 3

使用命令: ln -s linux-2.6.36 linux 在终端下输入一下命令: sudo apt-get install build-essential kernel-package libncurses5-dev fakeroot sudo aptitude install libqt3-headers libqt3-mt-dev libqt3-compat-headers libqt3-mt 4

基于32位ARM920T内核的微处理器的嵌入式Linux系统构建详解

基于32位ARM920T内核的微处理器的嵌入式Linux系统构建详解目前,在嵌入式系统中基于ARM微核的嵌入式处理器已经成为市场主流。随着ARM技术的广泛应用,建立面向ARM构架的嵌入式操作系统成为当前研究的热点问题。 已经涌现出许多嵌入式操作系统,如VxWork,windows-CE,PalmOS,Linux等。在众多的嵌入式操作系统中,Linux以其开源代码及免费使用倍受开发人员的喜爱。本文选用的微处理器S3C2410是基于32位ARM920T内核的微处理器,基于此处理器构造一Linux 嵌入式操作系统,将其移植到基于32位的ARM920T内核的系统中,在此基础上进行应用程序开发。 l、开发环境介绍 1.1、基于S3C2410ARM920T的硬件平台 该系统的硬件平台为深圳旋极公司提供,硬件的核心部件为三星$3C2410ARM920T芯片,外围还包括:64MNANDFLASH和RAM外围存储芯片;串口、网口和USB外围接口;CSTNLCD和触摸屏外围显示设备;UDAl34lTS的外围音频设备。S3C2410处理器和外围设备共同构成了基于ARM920T的开发板。 1.2、嵌入式Limlx软件系统 该嵌入式Linux的软件系统包括以下4个部分:引导加载程序vivi;Linux2.6.14内核;YAFFS2文件系统以及用户程序。他们的可执行映像依次存放在系统存储设备上. 与通常的嵌入式系统布局有所不同,本系统在引导加载程序和内核映像之间还增加了一个启动参数区,在这个区里存放着系统启动参数。引导加载程序通过调用这些参数来决定启动模式、启动等待时间等,这些启动参数的增加加强了系统的灵活性。本系统采用64MNANDFLASH的存储设备。 2、嵌入式Linux系统设计与实现 2.1、引导加载程序vivi

Linux内核崩溃原因分析及错误跟踪技术

Linux内核崩溃原因分析及错误跟踪技术 随着嵌入式Linux系统的广泛应用,对系统的可靠性提出了更高的要求,尤其是涉及到生命财产等重要领域,要求系统达到安全完整性等级3级以上[1],故障率(每小时出现危险故障的可能性)为10-7以下,相当于系统的平均故障间隔时间(MTBF)至少要达到1141年以上,因此提高系统可靠性已成为一项艰巨的任务。对某公司在工业领域14 878个控制器系统的应用调查表明,从2004年初到2007年9月底,随着硬软件的不断改进,根据错误报告统计的故障率已降低到2004年的五分之一以下,但查找错误的时间却增加到原来的3倍以上。 这种解决问题所需时间呈上升的趋势固然有软件问题,但缺乏必要的手段以辅助解决问题才是主要的原因。通过对故障的统计跟踪发现,难以解决的软件错误和从发现到解决耗时较长的软件错误都集中在操作系统的核心部分,这其中又有很大比例集中在驱动程序部分[2]。因此,错误跟踪技术被看成是提高系统安全完整性等级的一个重要措施[1],大多数现代操作系统均为发展提供了操作系统内核“崩溃转储”机制,即在软件系统宕机时,将内存内容保存到磁盘[3],或者通过网络发送到故障服务器[3],或者直接启动内核调试器[4]等,以供事后分析改进。 基于Linux操作系统内核的崩溃转储机制近年来有以下几种: (1) LKCD(Linux Kernel Crash Dump)机制[3]; (2) KDUMP(Linux Kernel Dump)机制[4]; (3) KDB机制[5]; (4) KGDB机制[6]。 综合上述几种机制可以发现,这四种机制之间有以下三个共同点: (1) 适用于为运算资源丰富、存储空间充足的应用场合; (2) 发生系统崩溃后恢复时间无严格要求; (3) 主要针对较通用的硬件平台,如X86平台。 在嵌入式应用场合想要直接使用上列机制中的某一种,却遇到以下三个难点无法解决: (1) 存储空间不足 嵌入式系统一般采用Flash作为存储器,而Flash容量有限,且可能远远小于嵌入式系统中的内存容量。因此将全部内存内容保存到Flash不可行。

探究linux内核,超详细解析子系统

探究linux内核,超详细解析子系统 Perface 前面已经写过一篇《嵌入式linux内核的五个子系统》,概括性比较强,也比较简略,现在对其进行补充说明。 仅留此笔记,待日后查看及补充!Linux内核的子系统 内核是操作系统的核心。Linux内核提供很多基本功能,如虚拟内存、多任务、共享库、需求加载、共享写时拷贝(Copy-On-Write)以及网络功能等。增加各种不同功能导致内核代码不断增加。 Linux内核把不同功能分成不同的子系统的方法,通过一种整体的结构把各种功能集合在一起,提高了工作效率。同时还提供动态加载模块的方式,为动态修改内核功能提供了灵活性。系统调用接口用户程序通过软件中断后,调用系统内核提供的功能,这个在用户空间和内核提供的服务之间的接口称为系统调用。系统调用是Linux内核提供的,用户空间无法直接使用系统调用。在用户进程使用系统调用必须跨越应用程序和内核的界限。Linux内核向用户提供了统一的系统调用接口,但是在不同处理器上系统调用的方法

各不相同。Linux内核提供了大量的系统调用,现在从系统 调用的基本原理出发探究Linux系统调用的方法。这是在一个用户进程中通过GNU C库进行的系统调用示意图,系 统调用通过同一个入口点传入内核。以i386体系结构为例,约定使用EAX寄存器标记系统调用。 当加载了系统C库调用的索引和参数时,就会调用0x80软件中断,它将执行system_call函数,这个函数按照EAX 寄存器内容的标示处理所有的系统调用。经过几个单元测试,会使用EAX寄存器的内容的索引查system_call_table表得到系统调用的入口,然后执行系统调用。从系统调用返回后,最终执行system_exit,并调用resume_userspace函数返回用户空间。 linux内核系统调用的核心是系统多路分解表。最终通过EAX寄存器的系统调用标识和索引值从对应的系统调用表 中查出对应系统调用的入口地址,然后执行系统调用。 linux系统调用并不单层的调用关系,有的系统调用会由

linux内核的网络配置

文章来源 https://www.wendangku.net/doc/3818356579.html,/p/2088592067 第9节, Networking support 关于网络支持 上图 讲解; RF switch subsystem support 这个一般是要的,因为有些无线和蓝牙放在一张卡上 选m,wireless(无线)里面的一些选项随之会自动选m,上图 注意: cfg80211 wireless extensions compatibility 这个兼容选项要选择,3.7默认是没有选择

如果没有选择,iwconfig会报告没有扩展 Bluetooth subsystem support 蓝牙,可以自己选择,如果有m就行 还有子选项自己看下 如果还有红外线,无线电,对应选择,这个设备应该是很少networking option最上面的,全局网络选项,上图

Packet socket和Unix domain sockets 备必,而且不能成模块,不然udev会报一段信息给你 Transformation user configuration interface 选m,其实也很少用,像ipsec,下面的ipsec也可以选成模块 TCP/IP networking 要的,要的,子选项大部分不用,你也可以选上 IP: multicasting 多播 IP: advanced router 高级路由 你需要选上 IP: TCP syncookie support ~~sync flooding,同时还必须。。。个人没什么意义Large Receive Offload提高网络的东西,这个Y,如果你觉得现在不用,先m TCP: advanced congestion control这个你也可以Y The IPv6 protocol 很多要用到,虽然在兲现在没用,像systemd就要了 Security Marking和Network packet filtering framework (Netfilter) 个人没什么意义,你可以试下

嵌入式Linux内核移植详解(顶嵌)

内核移植阶段 内核是操作系统最基本的部分。它是为众多应用程序提供对计算机硬件的安全访问的一部分软件,这种访问是有限的,并且内核决定一个程序在什么时候对某部分硬件操作多长时间。直接对硬件操作是非常复杂的,所以内核通常提供一种硬件抽象的方法来完成这些操作。硬件抽象隐藏了复杂性,为应用软件和硬件提供了一套简洁,统一的接口,使程序设计更为简单。 内核和用户界面共同为用户提供了操作计算机的方便方式。也就是我们在windows下看到的操作系统了。由于内核的源码提供了非常广泛的硬件支持,通用性很好,所以移植起来就方便了许多,我们需要做的就是针对我们要移植的对象,对内核源码进行相应的配置,如果出现内核源码中不支持的硬件这时就需要我们自己添加相应的驱动程序了。 一.移植准备 1. 目标板 我们还是选用之前bootloader移植选用的开发板参数请参考上文的地址: https://www.wendangku.net/doc/3818356579.html,/thread-80832-5-1.html。bootloader移植准备。 2. 内核源码 这里我们选用比较新的内核源码版本linux-2.6.25.8,他的下载地址是 ftp://https://www.wendangku.net/doc/3818356579.html,/pub/linux/kernel/v2.6/linux-2.6.25.8.tar.bz2。 3. 烧写工具 我们选用网口进行烧写这就需要内核在才裁剪的时候要对网卡进行支持 4. 知识储备 要进行内核裁剪不可缺少的是要对内核源码的目录结构有一定的了解这里进 行简单介绍。 (1)arch/: arch子目录包括了所有和体系结构相关的核心代码。它的每一个子 目录都代表一种支持的体系结构,例如i386就是关于intel cpu及与之相兼容体 系结构的子目录。PC机一般都基于此目录。 (2)block/:部分块设备驱动程序。 (3)crypto:常用加密和散列算法(如AES、SHA等),还有一些压缩和CRC校验 算法。 (4) documentation/:文档目录,没有内核代码,只是一套有用的文档。 (5) drivers/:放置系统所有的设备驱动程序;每种驱动程序又各占用一个子目 录:如,/block 下为块设备驱动程序,比如ide(ide.c)。 (6)fs/:所有的文件系统代码和各种类型的文件操作代码,它的每一个子目录支持 一个文件系统, 例如fat和ext2。

linux内核IMQ源码实现分析

本文档的Copyleft归wwwlkk所有,使用GPL发布,可以自由拷贝、转载,转载时请保持文档的完整性,严禁用于任何商业用途。 E-mail: wwwlkk@https://www.wendangku.net/doc/3818356579.html, 来源: https://www.wendangku.net/doc/3818356579.html,/?business&aid=6&un=wwwlkk#7 linux2.6.35内核IMQ源码实现分析 (1)数据包截留并重新注入协议栈技术 (1) (2)及时处理数据包技术 (2) (3)IMQ设备数据包重新注入协议栈流程 (4) (4)IMQ截留数据包流程 (4) (5)IMQ在软中断中及时将数据包重新注入协议栈 (7) (6)结束语 (9) 前言:IMQ用于入口流量整形和全局的流量控制,IMQ的配置是很简单的,但很少人分析过IMQ的内核实现,网络上也没有IMQ的源码分析文档,为了搞清楚IMQ的性能,稳定性,以及借鉴IMQ的技术,本文分析了IMQ的内核实现机制。 首先揭示IMQ的核心技术: 1.如何从协议栈中截留数据包,并能把数据包重新注入协议栈。 2.如何做到及时的将数据包重新注入协议栈。 实际上linux的标准内核已经解决了以上2个技术难点,第1个技术可以在NF_QUEUE机制中看到,第二个技术可以在发包软中断中看到。下面先介绍这2个技术。 (1)数据包截留并重新注入协议栈技术

(2)及时处理数据包技术 QoS有个技术难点:将数据包入队,然后发送队列中合适的数据包,那么如何做到队列中的数

激活状态的队列是否能保证队列中的数据包被及时的发送吗?接下来看一下,激活状态的队列的 证了数据包会被及时的发送。 这是linux内核发送软中断的机制,IMQ就是利用了这个机制,不同点在于:正常的发送队列是将数据包发送给网卡驱动,而IMQ队列是将数据包发送给okfn函数。

如何安装Linux内核源代码

如何获取Linux内核源代码 下载Linux内核当然要去官方网站了,网站提供了两种文件下载,一种是完整的Linux 内核,另一种是内核增量补丁,它们都是tar归档压缩包。除非你有特别的原因需要使用旧版本的Linux内核,否则你应该总是升级到最新版本。 使用Git 由Linus领头的内核开发队伍从几年前就开始使用Git版本控制系统管理Linux内核了(参考阅读:什么是Git?),而Git项目本身也是由Linus创建的,它和传统的CVS不一样,Git是分布式的,因此它的用法和工作流程很多开发人员可能会感到很陌生,但我强烈建议使用Git下载和管理Linux内核源代码。 你可以使用下面的Git命令获取Linus内核代码树的最新“推送”版本: $ git clone git://https://www.wendangku.net/doc/3818356579.html,/pub/scm/linux/kernel/git/torvalds/linux-2.6.git 然后使用下面的命令将你的代码树与Linus的代码树最新状态同步: $ git pull 安装内核源代码 内核包有GNU zip(gzip)和bzip2格式。Bzip2是默认和首选格式,因为它的压缩比通常比gzip更好,bzip2格式的Linux内核包一般采用linux-x.y.z.tar.bz2形式的文件名,这里的x.y.z是内核源代码的具体版本号,下载到源代码包后,解压和抽取就很简单了,如果你下载的是bzip2包,运行: $ tar xvjf linux-x.y.z.tar.bz2 如果你下载的是gzip包,则运行: $ tar xvzf linux-x.y.z.tar.gz 无论执行上面哪一个命令,最后都会将源代码解压和抽取到linux-x.y.z目录下,如果你使用Git下载和管理内核源代码,你不需要下载tar包,只需要运行git clone命令,它就会自动下载和解压。 内核源代码通常都会安装到/usr/src/linux下,但在开发的时候最好不要使用这个源代码树,因为针对你的C库编译的内核版本通常也链接到这里的。 应用补丁

Linux内核结构详解教程

Linux内核结构详解教程 ─────Linux内核教程 linux内核就像人的心脏,灵魂,指挥中心。 内核是一个操作系统的核心,它负责管理系统的进程,内存,设备驱动程序,文件和网络系统,决定着系统的性能和稳定性。内核以独占的方式执行最底层任务,保证系统正常运行。协调多个并发进程,管理进程使用的内存,使它们相互之间不产生冲突,满足进程访问磁盘的请求等等. 严格说Linux并不能称做一个完整的操作系统.我们安装时通常所说的Linux,是有很多集合组成的.应称为GNU/Linux. 一个Linux内核很少1.2M左右,一张软盘就能放下. 内容基础,语言简短简洁 红联Linux论坛是致力于Linux技术讨论的站点,目前网站收录的文章及教程基本能满足不同水平的朋友学习。 红联Linux门户: https://www.wendangku.net/doc/3818356579.html, 红联Linux论坛: https://www.wendangku.net/doc/3818356579.html,/bbs 红联Linux 论坛大全,所有致力点都体现在这 https://www.wendangku.net/doc/3818356579.html,/bbs/rf/linux/07.htm

目录 Linux内核结构详解 Linux内核主要五个子系统详解 各个子系统之间的依赖关系 系统数据结构 Linux的具体结构 Linux内核源代码 Linux 内核源代码的结构 从何处开始阅读源代码 海量Linux技术文章

Linux内核结构详解 发布时间:2006-11-16 19:05:29 Linux内核主要由五个子系统组成:进程调度,内存管理,虚拟文件系统,网络接口,进程间通信。

Linux内核主要五个子系统详解 发布时间:2006-11-16 19:05:54 1.进程调度(SCHED):控制进程对CPU的访问。当需要选择下一个进程运行时,由调度程序选择最值得运行的进程。可运行进程实际上是仅等待CPU资源的进程,如果某个进程在等待其它资源,则该进程是不可运行进程。Linux使用了比较简单的基于优先级的进程调度算法选择新的进程。 2.内存管理(MM)允许多个进程安全的共享主内存区域。Linux的内存管理支持虚拟内存,即在计算机中运行的程序,其代码,数据,堆栈的总量可以超过实际内存的大小,操作系统只是把当前使用的程序块保留在内存中,其余的程序块则保留在磁盘中。必要时,操作系统负责在磁盘和内存间交换程序块。内存管理从逻辑上分为硬件无关部分和硬件有关部分。硬件无关部分提供了进程的映射和逻辑内存的对换;硬件相关的部分为内存管理硬件提供了虚拟接口。 3.虚拟文件系统(VirtualFileSystem,VFS)隐藏了各种硬件的具体细节,为所有的设备提供了统一的接口,VFS提供了多达数十种不同的文件系统。虚拟文件系统可以分为逻辑文件系统和设备驱动程序。逻辑文件系统指Linux所支持的文件系统,如ext2,fat等,设备驱动程序指为每一种硬件控制器所编写的设备驱动程序模块。 4.网络接口(NET)提供了对各种网络标准的存取和各种网络硬件的支持。网络接口可分为网络协议和网络驱动程序。网络协议部分负责实现每一种可能的网络传输协议。网络设备驱动程序负责与硬件设备通讯,每一种可能的硬件设备都有相应的设备驱动程序。 5.进程间通讯(IPC) 支持进程间各种通信机制。 处于中心位置的进程调度,所有其它的子系统都依赖它,因为每个子系统都需要挂起或恢复进程。一般情况下,当一个进程等待硬件操作完成时,它被挂起;当操作真正完成时,进程被恢复执行。例如,当一个进程通过网络发送一条消息时,网络接口需要挂起发送进程,直到硬件成功地完成消息的发送,当消息被成功的发送出去以后,网络接口给进程返回一个代码,表示操作的成功或失败。其他子系统以相似的理由依赖于进程调度。

Linux设置内核参数的方法

Linux设置内核参数的方法 1内核参数的查看方法 使用“sysctl -a”命令可以查看所有正在使用的内核参数。内核参数比较多(一般多达500项),按照前缀主要分为以下几大类:net.ipv4、net.ipv6、net.core、vm、fs、dev.parport、dev.cdrom 、dev.raid、kernel等等。相同的linux,安装的组件和使用的方式不一样,正在使用的内核参数是不一样的。 所有的内核参数的说明文档是放到/usr/src/linux/Documentation/sysctl中的,如果想知道对内核参数的说明,可以到该目录下查看相应的说明文档。 2内核参数的的设置方法 由于Linux的内核参数信息都存在内存中,因此可以通过命令直接修改,并且修改后直接生效。也可以通过文件的方式进行设置。下面就介绍这两种修改方法。 2.1命令设置的方式 可以用两种方法实现。 1、使用“sysctl -w 参数名=值”的方式 假设我们把net.ipv4.ip_forward的值修改为1,使用命令“sysctl -w net.ipv4.ip_forward=1”。 2、修改内核参数对应的proc文件 内核参数位于/proc/sys/之下,参数名称是以文件所在的路径,并将“/”以“.”来取代。举例来说,/proc/sys/net/ip_forward的参数名称为net.ipv4.ip_forward。 同样把net.ipv4.ip_forward的值修改为1,使用命令“echo “1”> /proc/sys/net/ipv4/ip_forward”。 注意,这里proc文件跟普通的文件不一样。一般一个文件用echo写入内容之后,会变成一个文本文件,但echo修改proc文件之后还是个空文件。 2.2文件设置的方式 更改的内核参数默认保存在/etc/sysctl.conf文件中。修改的时候可以直接用vi编辑sysctl.conf文件,增加要修改的内核参数内容,修改的格式为:参数名=值。例如,把net.ipv4.ip_forward的值修改为1,在sysctl.conf中增加下面这行内容:net.ipv4.ip_forward=1 文件修改好后,进行保存。然后使用“sysctl -p 配置文件名”来使配置生效,如果配置文件是默认的,可以不用输配置文件名,即使用“sysctl -p”。 通过文件设置的方式修改的内核参数是在系统重启后将失效(我之前认为修改后的内核参数放在文件中,系统启动的时候会读这个文件,重启后设置应该不会失效。但经过验证,一般会失效,但如果把将默认的boot.sysctl服务打开,所以系统启动时就会执行这个文件的设置)。把我们修改参数的命令写入启动执行脚本文件里/etc/rc.local,这样系统重启后配置就不会失效。 文件方式的好处是内核参数设置的值可以用文件保留下来,调用“sysctl -p”可以使文

史上最全linux内核配置详解

对于每一个配置选项,用户可以回答"y"、"m"或"n"。其中"y"表示将相应特性的支持或设备驱动程序编译进内核;"m"表示将相应特性的支持或设备驱动程序编译成可加载模块,在需要时,可由系统或用户自行加入到内核中去;"n"表示内核不提供相应特性或驱动程序的支持。只有<>才能选择M 1. General setup(通用选项) [*]Prompt for development and/or incomplete code/drivers,设置界面中显示还在开发或者还没有完成的代码与驱动,最好选上,许多设备都需要它才能配置。 [ ]Cross-compiler tool prefix,交叉编译工具前缀,如果你要使用交叉编译工具的话输入相关前缀。默认不使用。嵌入式linux更不需要。 [ ]Local version - append to kernel release,自定义版本,也就是uname -r可以看到的版本,可以自行修改,没多大意义。 [ ]Automatically append version information to the version string,自动生成版本信息。这个选项会自动探测你的内核并且生成相应的版本,使之不会和原先的重复。这需要Perl的支持。由于在编译的命令make-kpkg 中我们会加入- –append-to-version 选项来生成自定义版本,所以这里选N。 Kernel compression mode (LZMA),选择压缩方式。 [ ]Support for paging of anonymous memory (swap),交换分区支持,也就是虚拟内存支持,嵌入式不需要。 [*]System V IPC,为进程提供通信机制,这将使系统中各进程间有交换信息与保持同步的能力。有些程序只有在选Y的情况下才能运行,所以不用考虑,这里一定要选。 [*]POSIX Message Queues,这是POSIX的消息队列,它同样是一种IPC(进程间通讯)。建议你最好将它选上。 [*]BSD Process Accounting,允许进程访问内核,将账户信息写入文件中,主要包括进程的创建时间/创建者/内存占用等信息。可以选上,无所谓。 [*]BSD Process Accounting version 3 file format,选用的话统计信息将会以新的格式(V3)写入,注意这个格式和以前的v0/v1/v2 格式不兼容,选不选无所谓。 [ ]Export task/process statistics through netlink (EXPERIMENTAL),通过通用的网络输出工作/进程的相应数据,和BSD不同的是,这些数据在进程运行的时候就可以通过相关命令访问。和BSD类似,数据将在进程结束时送入用户空间。如果不清楚,选N(实验阶段功能,下同)。 [ ]Auditing support,审计功能,某些内核模块需要它(SELINUX),如果不知道,不用选。 [ ]RCU Subsystem,一个高性能的锁机制RCU 子系统,不懂不了解,按默认就行。 [ ]Kernel .config support,将.config配置信息保存在内核中,选上它及它的子项使得其它用户能从/proc/ config.gz中得到内核的配置,选上,重新配置内核时可以利用已有配置Enable access to .config through /proc/config.gz,上一项的子项,可以通过/proc/ config.gz访问.config配置,上一个选的话,建议选上。 (16)Kernel log buffer size (16 => 64KB, 17 => 128KB) ,内核日志缓存的大小,使用默认值即可。12 => 4 KB,13 => 8 KB,14 => 16 KB单处理器,15 => 32 KB多处理器,16 => 64 KB,17 => 128 KB。 [ ]Control Group support(有子项),使用默认即可,不清楚可以不选。 Example debug cgroup subsystem,cgroup子系统调试例子 Namespace cgroup subsystem,cgroup子系统命名空间 Device controller for cgroups,cgroups设备控制器

linux内核启动 Android系统启动过程详解

linux内核启动+Android系统启动过程详解 第一部分:汇编部分 Linux启动之 linux-rk3288-tchip/kernel/arch/arm/boot/compressed/ head.S分析这段代码是linux boot后执行的第一个程序,完成的主要工作是解压内核,然后跳转到相关执行地址。这部分代码在做驱动开发时不需要改动,但分析其执行流程对是理解android的第一步 开头有一段宏定义这是gnu arm汇编的宏定义。关于GUN 的汇编和其他编译器,在指令语法上有很大差别,具体可查询相关GUN汇编语法了解 另外此段代码必须不能包括重定位部分。因为这时一开始必须要立即运行的。所谓重定位,比如当编译时某个文件用到外部符号是用动态链接库的方式,那么该文件生成的目标文件将包含重定位信息,在加载时需要重定位该符号,否则执行时将因找不到地址而出错 #ifdef DEBUG//开始是调试用,主要是一些打印输出函数,不用关心 #if defined(CONFIG_DEBUG_ICEDCC)

……具体代码略 #endif 宏定义结束之后定义了一个段, .section ".start", #alloc, #execinstr 这个段的段名是 .start,#alloc表示Section contains allocated data, #execinstr表示Section contains executable instructions. 生成最终映像时,这段代码会放在最开头 .align start: .type start,#function /*.type指定start这个符号是函数类型*/ .rept 8 mov r0, r0 //将此命令重复8次,相当于nop,这里是为中断向量保存空间 .endr b 1f .word 0x016f2818 @ Magic numbers to help the loader

Linux内核分析-网络[五]:网桥

看完了路由表,重新回到netif_receive_skb ()函数,在提交给上层协议处理前,会执行下面一句,这就是网桥的相关操作,也是这篇要讲解的容。 view plaincopy to clipboardprint? 1. s kb = handle_bridge(skb, &pt_prev, &ret, orig_dev); 网桥可以简单理解为交换机,以下图为例,一台linux机器可以看作网桥和路由的结合,网桥将物理上的两个局域网LAN1、LAN2当作一个局域网处理,路由连接了两个子网1.0和2.0。从eth0和eth1网卡收到的报文在Bridge模块中会被处理成是由Bridge收到的,因此Bridge也相当于一个虚拟网卡。 STP五种状态 DISABLED BLOCKING LISTENING LEARNING FORWARDING 创建新的网桥br_add_bridge [net\bridge\br_if.c] 当使用SIOCBRADDBR调用ioctl时,会创建新的网桥br_add_bridge。 首先是创建新的网桥: view plaincopy to clipboardprint?

1. d ev = new_bridge_dev(net, name); 然后设置dev->dev.type为br_type,而br_type是个全局变量,只初始化了一个名字变量 view plaincopy to clipboardprint? 1. S ET_NETDEV_DEVTYPE(dev, &br_type); 2. s tatic struct device_type br_type = { 3. .name = "bridge", 4. }; 然后注册新创建的设备dev,网桥就相当一个虚拟网卡设备,注册过的设备用ifconfig 就可查看到: view plaincopy to clipboardprint? 1. r et = register_netdevice(dev); 最后在sysfs文件系统中也创建相应项,便于查看和管理: view plaincopy to clipboardprint? 1. r et = br_sysfs_addbr(dev); 将端口加入网桥br_add_if() [net\bridge\br_if.c] 当使用SIOCBRADDIF调用ioctl时,会向网卡加入新的端口br_add_if。 创建新的net_bridge_port p,会从br->port_list中分配一个未用的port_no,p->br会指向br,p->state设为BR_STATE_DISABLED。这里的p实际代表的就是网卡设备。 view plaincopy to clipboardprint? 1. p = new_nbp(br, dev); 将新创建的p加入CAM表中,CAM表是用来记录mac地址与物理端口的对应关系;而刚刚创建了p,因此也要加入CAM表中,并且该表项应是local的[关系如下图],可以看到,CAM表在实现中作为net_bridge的hash表,以addr作为hash值,链入 net_bridge_fdb_entry,再由它的dst指向net_bridge_port。

Linux内核中的Kconfig用法与说明

Linux内核中的Kconfig文件 本节不对内核的Kconfig文件进行深入展开,更多Kconfig语法和说明请阅读 。 内核源码树每个目录下都还包含一个Kconfig文件,用于描述所在目录源代码相关的内核配置菜单,各个目录的Kconfig文件构成了一个分布式的内核配置数据库。通过make menuconfig(make xconfig或者make gconfig)命令配置内核的时候,从Kconfig文件读取菜单,配置完毕保存到文件名为.config的内核配置文件中,供Makefile文件在编译内核时使用。 1.1.1 Kconfig基本语法 如程序清单0.1所示代码摘自文件,是一个比较典型的Kconfig 文件片段,包含了Kconfig的基本语法。 程序清单0.1drivers/char/Kconfig片段 menu "Character devices" source "drivers/tty/Kconfig" config DEVKMEM bool "/dev/kmem virtual device support" default y help Say Y here if you want to support the /dev/kmem device. The /dev/kmem device is rarely used, but can be used for certain kind of kernel debugging operations. When in doubt, say "N". …… endmenu 1.子菜单 通过menu和endmenu来定义一个子菜单,程序清单0.1所示代码定义了一个“Character devices”子菜单,子菜单在界面中用“--->”表示,如图0.1所示。 图0.1menu定义的子菜单 子菜单的菜单项则由config来定义,随后的“bool”、“default”、“help”等都是该菜单 项的属性:

基于ARM的嵌入式linux内核的裁剪与移植.

基于ARM的嵌入式linux内核的裁剪与 移植 0引言微处理器的产生为价格低廉、结构小巧的CPU和外设的连接提供了稳定可靠的硬件架构,这样,限制嵌入式系统发展的瓶颈就突出表现在了软件方面。尽管从八十年代末开始,已经陆续出现了一些嵌入式操作系统(比较著名的有Vxwork、pSOS、Neculeus和WindowsCE)。但这些专用操作系统都是商业化产品,其高昂的价格使许多低端产品的小公司望而却步;而且,源代码封闭性也大大限制了开发者的积极性。而Linux的开放性,使得许多人都认为Linu 0 引言 微处理器的产生为价格低廉、结构小巧的CPU和外设的连接提供了稳定可靠的硬件架构,这样,限制嵌入式系统发展的瓶颈就突出表现在了软件方面。尽管从八十年代末开始,已经陆续出现了一些嵌入式操作系统(比较著名的有Vxwork、pSOS、Nec uleus和Windows CE)。但这些专用操作系统都是商业化产品,其高昂的价格使许多低端产品的小公司望而却步;而且,源代码封闭性也大大限制了开发者的积极性。而Linux的开放性,使得许多人都认为Linux 非常适合多数Intemet设备。Linux操作系统可以支持不同的设备和不同的配置。Linux对厂商不偏不倚,而且成本极低,因而很快成为用于各种设备的操作系统。嵌入式linux是大势所趋,其巨大的市场潜力与酝酿的无限商机必然会吸引众多的厂商进入这一领域。 1 嵌入式linux操作系统 Linux为嵌入操作系统提供了一个极有吸引力的选择,它是个和Unix 相似、以核心为基础、全内存保护、多任务、多进程的操作系统。可以支持广泛的计算机硬件,包括X86、Alpha、Sparc、MIPS、PPC、ARM、NEC、MOTOROLA 等现有的大部分芯片。Linux的程序源码全部公开,任何人都可以根据自己的需要裁剪内核,以适应自己的系统。文章以将linux移植到ARM920T内核的 s3c2410处理器芯片为例,介绍了嵌入式linux内核的裁剪以及移植过程,文中介绍的基本原理与方法技巧也可用于其它芯片。 2 内核移植过程 2.1 建立交叉编译环境 交叉编译的任务主要是在一个平台上生成可以在另一个平台上执行的程序代码。不同的CPU需要有不同的编译器,交叉编译如同翻译一样,它可以把相同的程序代码翻译成不同的CPU对应语言。 交叉编译器完整的安装涉及到多个软件安装,最重要的有binutils、gcc、glibc三个。其中,binutils主要用于生成一些辅助工具;gcc则用来生成交叉编译器,主要生成arm—linux—gcc交叉编译工具;glibc主要是提供用户程序所使用的一些基本的函数库。 自行搭建交叉编译环境通常比较复杂,而且很容易出错。本文使用的是

实例解析linux内核I2C体系结构(2)

实例解析linux内核I2C体系结构(2) 华清远见刘洪涛四、在内核里写i2c设备驱动的两种方式 前文介绍了利用/dev/i2c-0在应用层完成对i2c设备的操作,但很多时候我们还是习惯为i2c设备在内核层编写驱动程序。目前内核支持两种编写i2c驱动程序的方式。下面分别介绍这两种方式的实现。这里分别称这两种方式为“Adapter方式(LEGACY)”和“Probe方式(new style)”。 (1)Adapter方式(LEGACY) (下面的实例代码是在2.6.27内核的pca953x.c基础上修改的,原始代码采用的是本文将要讨论的第2种方式,即Probe方式) ●构建i2c_driver static struct i2c_driver pca953x_driver = { .driver = { .name= "pca953x", //名称 }, .id= ID_PCA9555,//id号 .attach_adapter= pca953x_attach_adapter, //调用适配器连接设备 .detach_client= pca953x_detach_client,//让设备脱离适配器 }; ●注册i2c_driver static int __init pca953x_init(void) { return i2c_add_driver(&pca953x_driver); } module_init(pca953x_init); ●attach_adapter动作 执行i2c_add_driver(&pca953x_driver)后会,如果内核中已经注册了i2c适配器,则顺序调用这些适配器来连接我们的i2c设备。此过程是通过调用i2c_driver中的attach_adapter方法完成的。具体实现形式如下: static int pca953x_attach_adapter(struct i2c_adapter *adapter) { return i2c_probe(adapter, &addr_data, pca953x_detect); /* adapter:适配器 addr_data:地址信息 pca953x_detect:探测到设备后调用的函数 */ } 地址信息addr_data是由下面代码指定的。 /* Addresses to scan */ static unsigned short normal_i2c[] = {0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,I2C_CLIENT_END}; I2C_CLIENT_INSMOD;

基于Linux内核编程的实验报告(Linux内核分析实验报告)

基于Linux内核编程的实验报告(Linux内核分析实验 报告) 以下是为大家整理的基于Linux内核编程的实验报告(Linux内核分析实验报告)的相关范文,本文关键词为基于,Linux,内核,编程,实验,报告,分析,,您可以从右上方搜索框检索更多相关文章,如果您觉得有用,请继续关注我们并推荐给您的好友,您可以在教育文库中查看更多范文。 Linux内核分析实验报告

实验题目:文件系统实验 实验目的:linux文件系统使用虚拟文件系统VFs作为内核文件子系统。可以安装多种 不同形式的文件系统在其中共存并协同工作。VFs对用户提供了统一的文件访问接口。本实验的要求是 (1)编写一个get_FAT_boot函数,通过系统调用或动态模块调用它可以提 取和显示出FAT文件系统盘的引导扇区信息。这些信息的格式定义在内核文件的fat_boot_sector结构体中。函数可通过系统调用或动态模块调用。 (2)编写一个get_FAT_dir函数,通过系统调用或动态模块调用它可以 返回FAT文件系统的当 前目录表,从中找出和统计空闲的目录项(文件名以0x00打头的为从未使用过目录项,以0xe5打头的为已删除的目录项),将这些空闲的目录项集中调整到目录表的前部。这些信息的格式定义在内核文件的msdos_dir_entry结构体中。 硬件环境:内存1g以上 软件环境:Linux(ubuntu)2-6实验步骤: 一:实验原理: 以实验4为蓝本,在优盘中编译并加载模块,启动测试程序,查

/proc/mydir/myfile的文件内容。从优盘得到fat文件系统的内容存在msdos_sb_info结构中,然后得到msdos_sb_info结构相应的属性值,得到实验一的数据。实验二中,得到fat文件系统第一个扇区的十六个文件信息。然后按照文件名头文字的比较方法,应用归并排序的方法,将头文件是0x00和0xe5的文件调到前面,其他的文件调到后面 二:主要数据结构说明: (1)超级块对象: 数据结构说明:一个已经安装的文件系统的安装点由超级块对象代表。 structsuper_block{... conststructsuper_operations*s_op;} (2)索引i节点对象 数据结构说明:索引i节点对象包含了内核要操作的文件的全部控制信息,对应着打开文件的i节点表。structinode{ conststructinode_operations*i_op;...} (3)目录项对象 数据结构说明:录项对象代表了文件路径名的各个部分,目录文件名和普 通文件名都属于目录项对象。structdentry{

相关文档
相关文档 最新文档