- 浏览: 208960 次
- 性别:
- 来自: 杭州
文章分类
最新评论
-
Prepared:
Hadoop的几个明显缺点 -
CSunDNan:
...
openjdk jvm 方法字节码执行过程 -
幻影之蚀:
...
mysql 源码分析2 源码调试环境建立 -
shukongchengje:
紧急呼唤楼主,mysql代码从哪里弄?官网wiki上看的一头雾 ...
mysql源码分析 整体架构 -
yeshaoting:
好文章.不介意的话转载了.
jvm 字节码中文含义
Linux内核修改实验
实验目的:
1. 深入理解Linux启动过程;
2. 修改Linux内核,让Linux启动后不执行init进程,而执行自己编写的程序。
实验原理:
1. Linux的启动有一个清晰的过程,基于Linux的开放性,修改其内核是完全可能的;
2. 当内核被引导并进行初始化之后,内核就可以启动自己的第一个用户空间应用程序init进程。
实验内容:
1. Linux内核初始化过程
l 内核映像:
在Bootloader的第二阶段会调用内核镜像,当内核映像被加载到内存中,并且Bootloader阶段 2 的引导加载程序释放控制权之后,内核阶段就开始了。内核映像并不是一个可执行的内核,而是一个压缩过的内核映像。在这个内核映像前面是一个例程,它实现少量硬件设置,并对内核映像中包含的内核进行解压,然后将其放入高端内存中,如果有初始 RAM 磁盘映像,就会将它移动到内存中,并标明以后使用。然后该例程会调用内核,并开始启动内核引导的过程。
l 内核引导过程:
当 bzImage(用于 i386 映像)被调用时,我们从 /XSBase255_Linux_C/Kernel/2.4.18-rmk7-pxa1-XSBase/arch/i386/boot/head.S 的 start 汇编例程开始执行。这个例程会执行一些基本的硬件设置,并调用 /arch/i386/boot/compressed/head.S 中的 startup_32 例程。此例程会设置一个基本的环境(堆栈等),并清除 Block Started by Symbol(BSS)。然后调用一个叫做 decompress_kernel 的 C 函数(在 /arch/i386/boot/compressed/misc.c 中)来解压内核。当内核被解压到内存中之后,就可以调用它了。这是另外一个 startup_32 函数,但是这个函数在 /arch/i386/kernel/head.S 中。
在这个新的 startup_32 函数(也称为清除程序或进程 0)中,会对页表进行初始化,并启用内存分页功能。然后会为任何可选的浮点单元(FPU)检测 CPU 的类型,并将其存储起来供以后使用。然后调用 start_kernel 函数(在 init/main.c 中),它会将您带入与体系结构无关的 Linux 内核部分。实际上,这就是 Linux 内核的 main 函数。
通过调用 start_kernel,会调用一系列初始化函数来设置中断,执行进一步的内存配置,并加载初始 RAM 磁盘。最后,要调用 kernel_thread(在 arch/i386/kernel/process.c 中)来启动 init 函数,这是第一个用户空间进程(user-space process)。最后,启动空任务,现在调度器就可以接管控制权了(在调用 cpu_idle 之后)。
l 深入分析内核源代码
根据上面对内核引导过程的分析,我们可以进入Linux内核源代码跟踪其具体过程。这里我们将图示从./arch/i386/kernel/head.S的Startup_32开始的过程。
call SYMBOL_NAME(start_kernel)
head.S第270行
asmlinkage void __init start_kernel(void)
./init/main.c第545行,函数最后(第628行)调用rest_init();
static void rest_init(void)
int kernel_thread(int (*fn)(void *),
void * arg, unsigned long flags)
./init/main.c第533行:
static void rest_init(void)
{
kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL);
unlock_kernel();
current->need_resched = 1;
cpu_idle();
}
内核引导过程图示
在下一页我们可以看到kernel_thread函数的定义,我们关心的是 rest_init函数中对kernel_thread的调用,从上面我们可以看到这样的语句:
kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL);
在这里,init当作函数指针传给kernel_thread,这样init将被执行,也就 成为Linux启动后第一个用户进程。可以设想,如果我们将自己的函数指针传 给kernel_thread,并作适当的初始化,就可以实现我们实验的目的。
/XSBase255_Linux_C/Kernel/2.4.18-rmk7-pxa1-XSBase/arch/i386/kernel/process.c第488行开始:
int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags)
{
long retval, d0;
__asm__ __volatile__(
"movl %%esp,%%esi\n\t"
"int $0x80\n\t" /* Linux/i386 system call */
"cmpl %%esp,%%esi\n\t" /* child or parent? */
"je 1f\n\t" /* parent - jump */
/* Load the argument into eax, and push it. That way, it does
* not matter whether the called function is compiled with
* -mregparm or not. */
"movl %4,%%eax\n\t"
"pushl %%eax\n\t"
"call *%5\n\t" /* call fn */
"movl %3,%0\n\t" /* exit */
"int $0x80\n"
"1:\t"
:"=&a" (retval), "=&S" (d0)
:"0" (__NR_clone), "i" (__NR_exit),
"r" (arg), "r" (fn),
"b" (flags | CLONE_VM)
: "memory");
return retval;
}
kernel_thread图示
2. 修改Linux内核,创建自己的init进程
经过上面的分析,我们找到init的原型,它在main.c第85行:static int init(void *);
它的定义在main.c的第805行,具体内容如下图示,
static int init(void * unused)
{
lock_kernel();
do_basic_setup();
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..
*/
free_initmem();
unlock_kernel();
if (open("/dev/console", O_RDWR, 0) < 0)
printk("Warning: unable to open an initial console.\n");
(void) dup(0);
(void) dup(0);
/*
* 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.
*/
if (execute_command)
execve(execute_command,argv_init,envp_init);
execve("/sbin/init",argv_init,envp_init);
execve("/etc/init",argv_init,envp_init);
execve("/bin/init",argv_init,envp_init);
execve("/bin/sh",argv_init,envp_init);
panic("No init found. Try passing init= option to kernel.");
}
init定义图示
可以看到,init函数首先做一些基本的初始化设置,清除初始化过程用到的内 存空间后,就真正开始执行用户进程。首先尝试打开控制台,然后执行内核配置命令 行参数中给的程序,完成一些初始化工作。这里我们可以看到一个if语句,这个 execute_command是一个char* 类型的指针,在main.c中第426行的static void __init parse_options(char *line)中,将命令行参数传给了它(第456行,execute_command= line;)像我们在配置内核时,指定这样的字眼: init=/linuxrc ,这样linuxrc就是在这 开始执行的,当然,它可能还会递归调用其它的需要执行的程序。如果没有设置参数, 就尝试执行指定的初始进程,这也是真正的用户进程了。在上面的定义中我们可以看 到,系统依次尝试执行/sbin/init,/ect/init/,/bin/init,/bin/sh进程,直到有一个运行成功; 如果全都执行失败,系统也就启动失败。
下面开始我们的最终任务,那就是修改init,让系统启动时不执行/sbin/init。简单 的方法是先建立自己的可执行程序,如/sin/myinit,将init中对/sbin/init的调用改为对 /sbin/myinit的调用即可。
这里给出本人对init的修改,新建自己的函数myinit,函数原型如下:
static myint init(void *);//加至main.c头部,myinit定义如下,
static int myinit(void * unused)
{
lock_kernel();
do_basic_setup();
prepare_namespace();
free_initmem();
unlock_kernel();
if (open("/dev/console", O_RDWR, 0) < 0)
printk("Warning: unable to open an initial console.\n");
(void) dup(0);
(void) dup(0);
if (execute_command)
execve(execute_command,argv_init,envp_init);
execve("/sbin/myinit",argv_init,envp_init);
panic("No init found. Try passing myinit= option to kernel.");
}
在/XSBase255_Linux_C/Filesystem/root_XSBASE/sbin新建可执行程序myinit,其源 文件定义如下:
//myinit.c
#include <stdio.h>
int main()
{
printf(“Haha,This is my init process!\n”;
}
最后,转到/init/main.c第533行的rest_init(void)函数,修改其对 kernel_thread的调用如下,
kernel_thread(myinit, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL);
这样,我们就实现了内核启动的第一个用户进程是我们自己定义的myinit函数。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/DGerome/archive/2009/02/22/3920960.aspx
实验目的:
1. 深入理解Linux启动过程;
2. 修改Linux内核,让Linux启动后不执行init进程,而执行自己编写的程序。
实验原理:
1. Linux的启动有一个清晰的过程,基于Linux的开放性,修改其内核是完全可能的;
2. 当内核被引导并进行初始化之后,内核就可以启动自己的第一个用户空间应用程序init进程。
实验内容:
1. Linux内核初始化过程
l 内核映像:
在Bootloader的第二阶段会调用内核镜像,当内核映像被加载到内存中,并且Bootloader阶段 2 的引导加载程序释放控制权之后,内核阶段就开始了。内核映像并不是一个可执行的内核,而是一个压缩过的内核映像。在这个内核映像前面是一个例程,它实现少量硬件设置,并对内核映像中包含的内核进行解压,然后将其放入高端内存中,如果有初始 RAM 磁盘映像,就会将它移动到内存中,并标明以后使用。然后该例程会调用内核,并开始启动内核引导的过程。
l 内核引导过程:
当 bzImage(用于 i386 映像)被调用时,我们从 /XSBase255_Linux_C/Kernel/2.4.18-rmk7-pxa1-XSBase/arch/i386/boot/head.S 的 start 汇编例程开始执行。这个例程会执行一些基本的硬件设置,并调用 /arch/i386/boot/compressed/head.S 中的 startup_32 例程。此例程会设置一个基本的环境(堆栈等),并清除 Block Started by Symbol(BSS)。然后调用一个叫做 decompress_kernel 的 C 函数(在 /arch/i386/boot/compressed/misc.c 中)来解压内核。当内核被解压到内存中之后,就可以调用它了。这是另外一个 startup_32 函数,但是这个函数在 /arch/i386/kernel/head.S 中。
在这个新的 startup_32 函数(也称为清除程序或进程 0)中,会对页表进行初始化,并启用内存分页功能。然后会为任何可选的浮点单元(FPU)检测 CPU 的类型,并将其存储起来供以后使用。然后调用 start_kernel 函数(在 init/main.c 中),它会将您带入与体系结构无关的 Linux 内核部分。实际上,这就是 Linux 内核的 main 函数。
通过调用 start_kernel,会调用一系列初始化函数来设置中断,执行进一步的内存配置,并加载初始 RAM 磁盘。最后,要调用 kernel_thread(在 arch/i386/kernel/process.c 中)来启动 init 函数,这是第一个用户空间进程(user-space process)。最后,启动空任务,现在调度器就可以接管控制权了(在调用 cpu_idle 之后)。
l 深入分析内核源代码
根据上面对内核引导过程的分析,我们可以进入Linux内核源代码跟踪其具体过程。这里我们将图示从./arch/i386/kernel/head.S的Startup_32开始的过程。
call SYMBOL_NAME(start_kernel)
head.S第270行
asmlinkage void __init start_kernel(void)
./init/main.c第545行,函数最后(第628行)调用rest_init();
static void rest_init(void)
int kernel_thread(int (*fn)(void *),
void * arg, unsigned long flags)
./init/main.c第533行:
static void rest_init(void)
{
kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL);
unlock_kernel();
current->need_resched = 1;
cpu_idle();
}
内核引导过程图示
在下一页我们可以看到kernel_thread函数的定义,我们关心的是 rest_init函数中对kernel_thread的调用,从上面我们可以看到这样的语句:
kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL);
在这里,init当作函数指针传给kernel_thread,这样init将被执行,也就 成为Linux启动后第一个用户进程。可以设想,如果我们将自己的函数指针传 给kernel_thread,并作适当的初始化,就可以实现我们实验的目的。
/XSBase255_Linux_C/Kernel/2.4.18-rmk7-pxa1-XSBase/arch/i386/kernel/process.c第488行开始:
int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags)
{
long retval, d0;
__asm__ __volatile__(
"movl %%esp,%%esi\n\t"
"int $0x80\n\t" /* Linux/i386 system call */
"cmpl %%esp,%%esi\n\t" /* child or parent? */
"je 1f\n\t" /* parent - jump */
/* Load the argument into eax, and push it. That way, it does
* not matter whether the called function is compiled with
* -mregparm or not. */
"movl %4,%%eax\n\t"
"pushl %%eax\n\t"
"call *%5\n\t" /* call fn */
"movl %3,%0\n\t" /* exit */
"int $0x80\n"
"1:\t"
:"=&a" (retval), "=&S" (d0)
:"0" (__NR_clone), "i" (__NR_exit),
"r" (arg), "r" (fn),
"b" (flags | CLONE_VM)
: "memory");
return retval;
}
kernel_thread图示
2. 修改Linux内核,创建自己的init进程
经过上面的分析,我们找到init的原型,它在main.c第85行:static int init(void *);
它的定义在main.c的第805行,具体内容如下图示,
static int init(void * unused)
{
lock_kernel();
do_basic_setup();
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..
*/
free_initmem();
unlock_kernel();
if (open("/dev/console", O_RDWR, 0) < 0)
printk("Warning: unable to open an initial console.\n");
(void) dup(0);
(void) dup(0);
/*
* 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.
*/
if (execute_command)
execve(execute_command,argv_init,envp_init);
execve("/sbin/init",argv_init,envp_init);
execve("/etc/init",argv_init,envp_init);
execve("/bin/init",argv_init,envp_init);
execve("/bin/sh",argv_init,envp_init);
panic("No init found. Try passing init= option to kernel.");
}
init定义图示
可以看到,init函数首先做一些基本的初始化设置,清除初始化过程用到的内 存空间后,就真正开始执行用户进程。首先尝试打开控制台,然后执行内核配置命令 行参数中给的程序,完成一些初始化工作。这里我们可以看到一个if语句,这个 execute_command是一个char* 类型的指针,在main.c中第426行的static void __init parse_options(char *line)中,将命令行参数传给了它(第456行,execute_command= line;)像我们在配置内核时,指定这样的字眼: init=/linuxrc ,这样linuxrc就是在这 开始执行的,当然,它可能还会递归调用其它的需要执行的程序。如果没有设置参数, 就尝试执行指定的初始进程,这也是真正的用户进程了。在上面的定义中我们可以看 到,系统依次尝试执行/sbin/init,/ect/init/,/bin/init,/bin/sh进程,直到有一个运行成功; 如果全都执行失败,系统也就启动失败。
下面开始我们的最终任务,那就是修改init,让系统启动时不执行/sbin/init。简单 的方法是先建立自己的可执行程序,如/sin/myinit,将init中对/sbin/init的调用改为对 /sbin/myinit的调用即可。
这里给出本人对init的修改,新建自己的函数myinit,函数原型如下:
static myint init(void *);//加至main.c头部,myinit定义如下,
static int myinit(void * unused)
{
lock_kernel();
do_basic_setup();
prepare_namespace();
free_initmem();
unlock_kernel();
if (open("/dev/console", O_RDWR, 0) < 0)
printk("Warning: unable to open an initial console.\n");
(void) dup(0);
(void) dup(0);
if (execute_command)
execve(execute_command,argv_init,envp_init);
execve("/sbin/myinit",argv_init,envp_init);
panic("No init found. Try passing myinit= option to kernel.");
}
在/XSBase255_Linux_C/Filesystem/root_XSBASE/sbin新建可执行程序myinit,其源 文件定义如下:
//myinit.c
#include <stdio.h>
int main()
{
printf(“Haha,This is my init process!\n”;
}
最后,转到/init/main.c第533行的rest_init(void)函数,修改其对 kernel_thread的调用如下,
kernel_thread(myinit, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL);
这样,我们就实现了内核启动的第一个用户进程是我们自己定义的myinit函数。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/DGerome/archive/2009/02/22/3920960.aspx
发表评论
-
Linux内存:内存管理的实质
2010-04-13 08:49 11001. 内核初始化: * 内核建立好内核页目录页表数据 ... -
深入C++的new
2010-04-12 10:31 864“new”是C++的一个关键字,同时也是操作符。关于new的话 ... -
Linux 的多线程编程
2010-04-12 09:32 1934本文们针 Linux 线程编程主特性总结出 5 条经验,以改善 ... -
浅析Linux下core文件
2010-04-08 10:32 2160当我们的程序崩溃时,内核有可能把该程序当前内存映射到core文 ... -
Linux下的CPU利用率计算原理详解
2010-04-07 17:13 1640我们在搞性能测试的时 ... -
linux cpu负载原理
2010-04-07 16:41 1254linux cpu负载原理 内核分析 待补 -
从VFS inode到LFS inode的寻址过程
2010-03-30 17:16 1835我们知道Linux是借用虚拟文件系统作为上层抽象的管理者来统一 ... -
CPU学习
2010-03-30 14:16 1465这是作者学习硬件基本知识过程中的笔记,由于以前很少接触这方面的 ... -
Cache Cohernce with Multi-Processor
2010-03-30 13:57 858刚写完一篇关于Cache Coherence的文章,就发现BN ... -
Cache 的write back和write through
2010-03-30 13:54 2306Cache 的write back和write through ... -
linux 2.6 Makefile详解
2010-03-30 13:48 1487熟悉内核的Makefile对开发设备驱动、理解内核代码结构都是 ... -
linux1
2010-03-30 13:38 684linux1 linux1 linux1 -
nfs
2010-03-30 13:29 679nfs文件系统 -
修改Linux内核增加系统调用
2010-03-30 09:44 1229本文修改内核2.4.29,分两部分,第一部分修改内核并测试,第 ... -
Linux内核裁剪的具体步骤
2010-03-29 17:27 1857在menuconfig中配置: 详 ... -
Linux内核剪裁实验
2010-03-29 17:08 1343实验目的: 1. 配置、编译Linux内核; 2 ... -
c语言深度解析
2010-03-29 15:38 753c语言深度解析 嘿嘿 -
Linux线程实现机制分析
2010-03-29 15:12 934内容: ·基础 ... -
内存屏障原语
2010-03-29 15:11 2038来自于在CU的一个讨论: http://linux.chin ... -
linux 经典进程切换实现代码
2010-03-29 15:10 1237extern _inline void switch_to(i ...
相关推荐
### Linux内核编译实验知识点总结 #### 一、实验目的 - **了解Linux内核编译**: 学习如何从源代码构建Linux内核,理解内核编译的基本流程和关键技术点。 - **掌握内核配置**: 掌握如何通过`menuconfig`等工具配置...
### Linux内核实验手册知识点概览 #### Linux内核概览 - **定义**:“Linux” 或 “Linux内核” 是一种用C语言和汇编语言编写的操作系统内核,提供硬件抽象、磁盘及外部设备控制、文件系统控制、多任务等功能。 - *...
实验目的包括了解 Linux 内核相关知识与内核结构、了解 Linux 内核在 ARM 设备上移植的基本步骤和方法、掌握 Linux 内核裁剪与定制的基本方法等。 在实验步骤中,我们首先使用 vim 编辑器手动编写实验代码 hello...
【Linux内核修改与编译】是针对操作系统底层开发的一项技术实践,主要涉及对Linux内核源代码的改动以及重新编译的过程。在本实验中,广州大学松田学院的学生以2008030703学号的xxx同学为例,进行了一次操作系统课程...
Linux内核学习路径是指为学习者提供的一系列步骤和资源,以系统地掌握Linux内核的相关知识。Linux内核是Linux操作系统的核心部分,负责硬件资源的管理、内存管理、进程调度等关键任务。了解Linux内核的设计与原理...
书中涵盖了一系列的实验,旨在让学习者通过实际操作来掌握Linux内核的相关知识。以下是对实验内容的详细说明: 1. **重新编译内核**:这个实验要求读者以root权限修改内核源码,例如在`unistd.h`中添加新系统调用的...
- **Linux_ںԴ龰_ë².pdf**:这份文档可能是关于Linux内核源代码的二次开发或定制指南,适合希望对Linux内核进行修改和扩展的开发者参考。 ### 3. 学习Linux内核的方法 - **阅读官方文档**:Linux内核官方文档是...
《操作系统实验指导-基于Linux内核(第2版)》旨在帮助学生和学习者通过实践来探索和掌握操作系统的基本概念、设计与实现。这份指导文档特别关注于Linux内核,一个广泛使用的开源操作系统内核。 Linux内核是操作...
【Linux内核移植】实验指导涉及的关键知识点包括: 1. **获取Linux内核源码**: Linux内核源码可以在官方仓库kernel.org上下载,或者使用提供的光盘中的版本,例如`linux-2.6.24.4.tar.bz2`。在实验中,将压缩包...
源代码分析是Linux内核学习的一大特色,因为Linux是开源的,这意味着任何人都可以查看、学习甚至修改其源代码。"Linux1.0核心游记"很可能会引导读者逐步剖析源码,通过实例解析关键函数和数据结构,帮助读者更好地...
"Linux内核移植实验指南" 本实验旨在掌握 Linux 内核配置和编译的基本方法,通过实验步骤中逐步完成 Linux 内核的移植。 一、实验环境 * Ubuntu 14.04 发行版 * FS4412 实验平台 * 交叉编译工具:arm-none-linux-...
在本实验中,“Linux内核分析实验:模拟宾馆房间预订系统”旨在通过实践来深入理解Linux内核的工作机制,特别是进程间通信(IPC)和共享内存等关键概念。这个项目将帮助你熟悉如何在实际场景中应用这些核心操作系统...
编译Linux内核是Linux操作系统课程设计实验的一部分,目的是学习重新编译Linux内核,理解并掌握Linux内核和发行版本的区别。以下是编译Linux内核的详细知识点: 一、实验目的 * 了解Linux内核的编译和配置过程 * ...
在进行内核修改前,确保系统已经安装了必要的构建工具,比如`make`, `gcc`, `ncurses-devel`等。然后,进入内核源码目录,开始内核配置。使用`make menuconfig`或`make xconfig`可以打开图形化的配置界面,这里可以...
### Linux内核编译实验知识点总结 #### 实验目的 - **熟悉Linux的使用:** 学习如何在Linux环境下进行基本的操作,包括文件管理、命令行操作等。 - **编译内核:** 掌握Linux内核的编译过程,理解编译配置的重要性...