注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

时间记录器

记录我的Linux、Android学习之路

 
 
 

日志

 
 

Linux 启动时干了什么?(一)  

2010-08-16 13:54:31|  分类: Linux |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

转载自http://hi.baidu.com/carbens/blog/item/f10f8288aa1d2391a4c27216.html

Linux-2.6.20的cs8900驱动分析(一)

一、初始化阶段

    网络初始化被调用的路径为:

init->do_basic_setup->do_initcalls->net_olddevs_init->ethif_probe2->probe_list2->cs89x0_probe->cs89x0_probe1

真是不容易啊,终于进到cs89x0_probe1了,在这里开始探测和初始化cs8900了。下面就按照这个顺序来说明网络驱动第一阶段的工作。注意:这里的调用顺序是将cs8900驱动编入内核所产生的,如果将cs8900驱动选为模块,这个路径:init->do_basic_setup->do_initcalls->net_olddevs_init->ethif_probe2->probe_list2也会执行。

1.1 init函数

我们知道当start_kernel函数完成后就会启动init进程执行,在真正的应用程序init进程(如busybox的/sbin/init)之前,Linux还需要执行一些初始化操作。init的代码可以在\init\main.c中找到,它的代码如下:

static int init(void * unused)
       {
               lock_kernel();
                ……                                                         //省略多cpu的初始化代码先
                do_basic_setup();                                   //我们所关注的初始化函数
                ……

        if (!ramdisk_execute_command)
                    ramdisk_execute_command = "/init";

      if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0)    {
                     ramdisk_execute_command = NULL;
                     prepare_namespace();                                       //挂接根文件系统     
             }
             ……

free_initmem();                                                       //释放初始化代码的空间

       unlock_kernel();

……                                                                      //这几段没看懂

       if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0) //检查控制台console是否存在

              printk(KERN_WARNING "Warning: unable to open an initial console.\n");

……//这几段没看懂

       if (ramdisk_execute_command) {           //运行ramdisk_execute_command指定的init用户进程

              run_init_process(ramdisk_execute_command);

              printk(KERN_WARNING "Failed to execute %s\n",

                            ramdisk_execute_command);

       }

       ……

       if (execute_command) {       //判断在启动时是否指定了init参数,如果指定,此值将赋给execute_command

              run_init_process(execute_command);              //开始执行用户init进程,如果成功将不会返回。

              printk(KERN_WARNING "Failed to execute %s. Attempting "

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

       }

//如果没有指定init启动参数,则查找下面的目录init进程,如果找到则不会返回

       run_init_process("/sbin/init");

       run_init_process("/etc/init");

       run_init_process("/bin/init");

       run_init_process("/bin/sh");

   //如果上面的程序都出错,则打印下面的信息,如果内核找到init进程,则程序不会指向到此处

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

}

1.2 do_basic_setup函数

在这里我们最关心的是do_basic_setup函数,顾名思义该函数的功能就是“做基本设置”,它的实现代码也在\init\main.c中。do_basic_setup()完成外设及其驱动程序的加载和初始化。该函数代码如下所示:

static void __init do_basic_setup(void)

{

       /* drivers will send hotplug events */

       init_workqueues();     //初始化工作队列

       usermodehelper_init(); //初始化khelper内核线程,还没弄清楚

       driver_init();           //初始化内核的设备管理架构需要的数据结构,很复杂,以后在谈这部分。

#ifdef CONFIG_SYSCTL

       sysctl_init();          //没搞懂

#endif

       do_initcalls();         //重点函数,初始化的主要工作就靠它了

}

1.3 do_ initcalls函数

       do_initcalls函数将会调用内核中所有的初始化函数,它的代码同样在\init\main.c中。do_initcalls函数调用其他初始化函数相当简洁,它的关键代码如下所示:

initcall_t *call;

for (call = __initcall_start; call < __initcall_end; call ) {

……

       result = (*call)();

……

       简洁归简洁,但这段代码是什么意思呢?这说来就话长了,最重要的应该是先了解Linux处理初始化的大体思想,由于Linux有很多部分需要初始化,每个部分都有自己的初始化函数,如果按照常理一个一个的调用未免显得冗长,而且也不便于扩展。那么Linux是怎么处理的呢?首先,Linux将各个部分的初始化函数编译到一个块内存区中,当初始化完了以后释放这块内存区,这就是init函数中free_initmem所要做的事。然后,再在另外的内存区中存放一些函数指针,让每个指针指向一个初始化函数。然后在do_initcalls中依次根据这些指针调用初始化函数。

上面一段就是Linux实现初始化的大体思想,下面我们看看它最终是怎么实现的。首先要了解的是__define_initcall宏,该宏的定义在\ include\linux\init.h中,它的原型如下所示:

#define __define_initcall(level,fn,id) static initcall_t __initcall_##fn##id __attribute_used__ \

       __attribute__((__section__(".initcall" level ".init"))) = fn

__define_initcall宏有三个参数,level表示初始化函数的级别,level值的大小觉得了调用顺序,level越小越先被调用,fn就是具体的初始化函数,id简单标识初始化函数,现在还没找到有什么用^_^。__define_initcall的功能为,首先声明一个initcall_t类型的函数指针__initcall_##fn##id,initcall_t的原型为:

typedef int (*initcall_t)(void);

该类型可简单理解为函数指针类型^_^。然后,让该函数指针指向fn。最后,通过编译器的编译参数将此指针放到指定的空间".initcall" level ".init"中,__attribute_used向编译器说明这段代码有用,即使在没用到的时候,编译器也不会警告。__attribute__的__section__参数表示该段代码放入什么内存区域中,也即指定编译到什么地方,编译参数更详细的地方可以查阅GCC文档,在gcc官方网站http://gcc.gnu.org/onlinedocs/中能找到各个版本的手册。这样说来还是比较抽象,下面举个例子来说明:

       假如有初始化函数init_foolish函数,现在使用__define_initcall宏向内核加入该函数。假如调用方式如下:

__define_initcall("0",init_foolish,1);

那么,__define_initcall宏首先申请一个initcall_t类型的函数指针__initcall_init_foolish1(注意替换关系),且使该指针指向了init_foolish,函数指针__initcall_init_foolish1被放到.initcall.0.init内存区域中,这个标志在连接时会用到。

       有了上面的基础知识,现在回到do_initcalls函数中,首先注意到是__initcall_start和__initcall_end,它们的作用就是界定了存放初始化函数指针区域的起始地址,也即从__initcall_start开始到__initcall_end结束的区域中存放了指向各个初始化函数的函数指针。换句话说,只要某段程序代码从__initcall_start开始依次调用函数指针,那么就可以完成各个部分的初始化工作,这显得十分优雅而且便于扩充,再看看do_initcalls,它何尝不是如此呢。这里还有一个有用的技巧就是__initcall_start和__initcall_end的原型是initcall_t型的数组,以后可以使用这种技巧^_^。

       现在我们知道了do_initcalls函数的实现原理,那么到底它调用了多少初始化函数呢?我们怎样才能知道呢?根据上面的分析,我们知道所有的初始化函数的指针都放在__initcall_start和__initcall_end区域期间,而函数指针与它指向的函数之间又有固定的关系,如上面的例子,初始化函数名为init_foolish,指向它的函数指针就是__initcall_init_foolish1,即在此函数加上前缀__initcall_和一个数字后缀,反之,从函数指针也可推出初始化函数名。有了这两个信息,我们就可以很方便的找个初始化函数。怎么找呢??首先打开Linux完后产生的System.map文件,然后找到__initcall_start和__initcall_end字符串,你会发现它们之间有很多类似于__initcall_xxx1这样的符号,这些符号就是我们需要的函数指针了,这样就可推出初始化函数的名字。比如,我们这里需要的函数指针__initcall_net_olddevs_init6,按照上面的名字规则,很容易推出它所指向的初始化函数名字是net_olddevs_init。

       得到了初始化函数的名字又怎么样呢?又不知道它在哪个文件里,不要着急!请打开你的浏览器登陆http://lxr.linux.no/ident网站,然后选择Linux版本和架构,然后可以搜索我们想要的信息。比如我输入net_olddevs_init,然后我就会得到该函数所在文件的相关信息。

  评论这张
 
阅读(293)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017