linux内核的学习方法

     学习linux内核的最大工作就是对内核代码的分析,如果抱着走马观花、得过且过的态度,最终很可能没有多大收获。学习内核应该遵循科学、严谨的态度,要做到真正理解每一段代码的实现,并且在学习的过程中多问、多想、多记。

    上述学习Linux内核的方法非常重要,接下来将通过两个具体的应用来演示学习linux内核的过程

一、分析usb子系统的代码

     linux内核中usb子系统的代码位于driver/usb目录下,ls结果:

     atm c67x00 class core early gadget host image misc mon musb otg renesas_usbhs serial storage wusbcore Kconfig Makfile README usb-skeleton.c

     共16个子目录和4个文件,为了理解每个子目录的作用,有必要首先阅读README文件:

"

要了解所有的Linux-USB的框架,你可以使用这些资源:
    
*此源代码。这必然是一个不断变化的工作,kerneldoc应该可以帮助您的当前概况。("make pdfdocs",然后主机端看的"usb.pdf",外围侧看"gadget.pdf"),Documentation/usb也可以了解更多信息。
    
* USB 2.0规范(www.usb.org),与补充如USB OTG和各种设备类。USB规范有一个很好的“概述”第1章和USB外设配置是广为人知的“第9章”。
    
* USB控制器芯片规格。例子包括:主机控制器(电脑,服务器等);外设 控制器(的Linux固件的设备,如打印机或手机)和硬线连接的外围设备,如以太网适配器。
    
*规格由USB外围设备的其他协议的实施功能。有些是特定于供应商的,有些则是厂商中立的,但不仅仅是标准化的外面在www.usb.org团队。

下面列表,说明了每个子目录是什么,包含些什么。
   (1)core
- 这是为核心的主机代码,包括的USBFS文件和中心类驱动程序(“khubd”)。

     ——这是内核开发者对部分核心的功能特意编写的代码,用于为其他的设备驱动程序提供服务,如申请内存,实现一些所有的设备都需要的公共函数,并命名为USB core.   

   (2)host - 这是USB主控制器驱动程序。包括UHCI,OHCI,EHCI和其他可能使用专门的“嵌入式系统”。

      ——早期的内核结构并不像现在这样富有层次感,几乎所有的文件都直接堆砌在driver/usb目录下,只其中包括usb core和其他各种设备驱动程序的代码。后来在driver/usb目录下面单独列出了core子目录,用于存放一些比较核心的代码,比如整个USB子系统的初始化、root hub的初始化、host controller的初始化代码。随着技术的发展,出现了多种USB host controller,于是内核开发者把host controller有关的公共代码保留在core目录下,而其他各种host controller对应的特殊代码则移植到了host目录下,让相应的负责人去维护。
     (3)gadget(小工具) ​​ - 这是USB外设控制器驱动程序,并各种小工具,驱动程序和他们交谈。

  ——用于存放USB gadget的驱动程序,控制外围设备如何作为一个USB设备和主机通信,比如嵌入式开发板通常会支持SD卡,使用USB连接线将开发板连接到PC时,通过USB gadget架构的驱动,可以江SD卡模拟成U盘。

独立的USB驱动程序目录。新的驱动程序应该被添加到在以下列表中融入一个子目录。
    (4)image(图像) - 这是静态图像的驱动器,扫描仪等数码相机。
         ../input(输入)/ - 这是任何驱动程序,使用输入子系统,如键盘,鼠标,触摸屏,平板电脑等。
       ../media(媒体)/ - 这是多媒体驱动程序,如摄像机,收音机,以及其他任何驱动程序,V4L的谈话子系统。
      ../net(网络) / - 这是网络驱动程序。
    (5)serial(串行) - 这是USB到串口的驱动程序。
    (6) storage(存储) - 这是USB大容量存储驱动程序。
    (7)class(类) - 这是不适合所有USB设备驱动程序成任何的上述类别,并且其工作在USB Class指定的范围内的设备。
    (8)MISC / - 这是不适合所有USB设备驱动程序成任何上述类别。

    因为我们的目的是研究内核对USB子系统的实现,而不是特定设备或host controller的驱动,所以通过对README文件的分析,应该进一步关注core子目录。

二、分析USB系统的初始化代码

    通过分析Kconfig和Makefile文件,可以帮助我们在庞大、复杂的内核代码中定位以及缩小了目标范围。为了研究内核对USB子系统的实现,需要在目标代码中找到USB子系统的初始化代码。

     Linux内核针对某个子系统或某个驱动,是使用subsys_initcall或module_init的宏来指定初始化函数。在内核文件driver/usb/core/usb.c中,发现 subsys_initcall(usb_init);module_exit(usb_exit);可以将subsys_initcall理解为module_init,只不过因为该部分代码比较核心,开发者们把它看作了一个子系统,而不仅仅是一个模块。在Linux中类似此类别的设备驱动都被归结为一个子系统,比如PCI子系统和SCSI子系统,通常在Driver目录下的第一层的每一个目录代表一个子系统,因为他们分别代表了一类设备。

    要研究USB子系统在内核中的实现,需要从USB的初始化函数usb_init()函数开始分析,对应代码如下:

static int __init usb_init(void)
{
 int retval;
 if (nousb) {
  pr_info("%s: USB support disabled\n", usbcore_name);
  return 0;
 }

 retval = usb_debugfs_init();
 if (retval)
  goto out;

 retval = bus_register(&usb_bus_type);
 if (retval)
  goto bus_register_failed;
 retval = bus_register_notifier(&usb_bus_type, &usb_bus_nb);
 if (retval)
  goto bus_notifier_failed;
 retval = usb_major_init();
 if (retval)
  goto major_init_failed;
 retval = usb_register(&usbfs_driver);
 if (retval)
  goto driver_register_failed;
 retval = usb_devio_init();
 if (retval)
  goto usb_devio_init_failed;
 retval = usbfs_init();
 if (retval)
  goto fs_init_failed;
 retval = usb_hub_init();
 if (retval)
  goto hub_init_failed;
 retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE);
 if (!retval)
  goto out;

 usb_hub_cleanup();
hub_init_failed:
 usbfs_cleanup();
fs_init_failed:
 usb_devio_cleanup();
usb_devio_init_failed:
 usb_deregister(&usbfs_driver);
driver_register_failed:
 usb_major_cleanup();
major_init_failed:
 bus_unregister_notifier(&usb_bus_type, &usb_bus_nb);
bus_notifier_failed:
 bus_unregister(&usb_bus_type);
bus_register_failed:
 usb_debugfs_cleanup();
out:
 return retval;
}

接下来开始分析上述代码:

(1)标记 __init

    关于usb_init,第一个问题就是上述第一行的代码中的__init标记有什么意义?在前面的GCC扩展的特殊属性section时曾提到,__init修饰的所有代码都会被在.init.text节,当初始化结束后就可以释放这部分内存。所以它的调用函数不少有就该标记。但是内核是如何调用到__init所修饰的这些初始化函数的呢?为了回答这个问题,需要用到subsys_initcall的宏的知识,它在文件include/linux/init.h中的定义格式如下:

  #define subsys_initcall(fn)   __define_initcall("4",fn,4)

此时出现一个新的宏__define_initcall,它用来将指定的函数指针fn存放到.initcall.init节。对于subsys_initcall宏,则表示把fn存放到.initcall.init的子节.initcall4.init。为了理解.initcall.init、.init.text和.initcall4.init之类的符号,还需要了解和内核可执行文件相关的概念。内核可执行文件有许多链接在一起的对象文件组成。对象文件有许多节,如文本、数据、init数据、bass等(段页式管理,可以理解为编译原理或者汇编中的代码段 数据段等),这些对象文件都是由一个称为链接器脚本的文件连接并装入的。这个链接器脚本的功能是将输入的对象文件的各节映射到输出文件中。换句话说,它将所有输入对象文件都链接到一个单一的可执行文件中,将该可执行文件的各节装入指定地址处。vmlinux.lds是保存在arch/<target>/目录中的内核链接器脚本,它负责链接内核的各节并将它们装入内存中特定偏移处。

    打开文件arch/i386/kernel/vmlinux.lds,搜索关键字initacall.init后便会看到如下结果:

    initcall_start = .;

    .initcall.init : AT(ADDR(.initcall.init) - 0xC0000000) {

     *(.initcall1.init)

     *(.initcall2.init)

     *(.initcall3.init)

     *(.initcall4.init)

     *(.initcall5.init)

     *(.initcall6.init)

     *(.initcall7.init)

  }

 __initcall_end = .;

 其中,__initcall_start指向.initcall.init节的开始,__initcall_end指向.initcall.init节的结尾。而.initcall.init节又被分为了7个节。宏subsys_initcall将指定的函数指针放在了.initcall4.init子节,至于其他的宏的功能也类似,例如,core_initcall将函数指针放在.initcall1.init子节,device_initcall将把函数指针放在.initcall6.init子节等。各子节的顺序是确定的,即先调用.initcall1.init中的函数指针,然后调用.initcall2.init中的函数指针。__init修饰的初始化函数在内核初始化过程中的调用顺序和.initcall.init节里函数指针的顺序有关,不同的初始化函数被放到不同的子节中,因此也就决定了它们的调用顺序。

  (2)模块参数

    在前面的usb_init函数的代码中,代码nousb在driver/usb/core/usb.c文件中定义为如下的格式:

   static int nousb; /* Disable USB when built into kernel image */

  module_param_named(autosuspend, usb_autosuspend_delay, int, 0644);
  MODULE_PARM_DESC(autosuspend, "default autosuspend delay");

   从中可知nousb是一个模块参数,用于在内核启动时候禁止USB子系统。关于模块参数,而已在加载模块时候可以指定,但是如何在内核启动时指定呢?打开系统的grub文件,然后找到kernel行,例如,下面的代码:

    kernel  /boot/vimlinuz-2.6.18-kdb root=/dev/sda1 ro splash=slient vga=0x314

   其中的root、splash、vga等都表示内核参数。当某一模块被编译进内核时候,它的模块参数便需要在kernel行来指定,其格式如下:

       模块名.参数=值

例如: modprobe usbcore autosuspend=2;对应到kernel行的代码就是:usbcore.autosuspend=2

    通过命令modinfo -p ${modulename}可以得到一个模块有哪些参数可以使用。而对于已经加载到内核中的模块,其模块参数会列举在/sys/module/${modulename}/parameters/目录下面,可以使用如下的命令去修改:

     echo -n ${value} > /sys/module/${modulename}/parameters/${param}

     关于usb_init()函数,除了上面介绍的代码外,余下的代码分别完成usb各部分的初始化,其他代码的具体分析工作可以参阅下载linux内核代码,具体韩式可以参阅相关的书籍和资料。

 

posted @ 2013-01-10 20:45  蓝天碧海Eric  阅读(502)  评论(0编辑  收藏  举报