Linux核心驅動程式初始化順序的調整(1)

來源:互聯網
上載者:User

        Linux核心驅動程式初始化順序的調整 ,當在做一個驅動的時候要用到另一個驅動提供的API時,此時核心初始化時便碰到了一個依賴問題。

而這也是面試老生常談的問題:

  經常讓你說下你做的驅動,然後你說XXX驅動後,很可能問下驅動細節後,會有另外一個問題:  
  1.你寫了一個XXX驅動,那麼它是怎麼被核心執行的呢?

  2.我有a,b,c三個裝置的驅動,怎麼讓他們按照b,a,c的順序載入

        那麼首先我們就得搞清楚驅動程式是如何被核心初始化的,這個問題很明顯就得追溯到核心啟動流程上了。簡單的講Linux的啟動過程可以分為兩個部分:架構/開發板相關的引導過程、後續的通用啟動過程。在這裡我們不多談核心啟動,為了使問題能夠解釋清楚,我們直接跳到第二階段init/main.cstart_kernel()函數(核心啟動流程的第一個C函數)說起。

        而驅動程式的初始化正是在這個階段的rest_init()函數中完成的,rest_init()函數主要目標有:載入驅動程式,掛載根檔案系統,執行使用者空間第一個進程init進程(pid=1)。

讓我們看看rest_init()主要流程:

------------------------------------------------------------------------------------------------------------------------------------------------------------------------

static void noinline __init_refok rest_init(void) __releases(kernel_lock)
{
 int pid;

 kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
 numa_default_policy();
 pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
 kthreadd_task = find_task_by_pid(pid);
 unlock_kernel();

 /*
  * The boot idle thread must execute schedule()
  * at least one to get things moving:
  */
 preempt_enable_no_resched();
 schedule();
 preempt_disable();

 /* Call into cpu_idle with preempt disabled */
 cpu_idle();
}
---------------------------------------------------------------------------------------------------------------------------------------------------------------------

從上面看出,rest_init()函數建立兩個核心線程:1)kernel_init;2)kthreadd

kernel_init線程主要就用來載入驅動程式、掛載根檔案系統、由核心線程蛻變成第一個使用者進程init(pid=1)

kthreadd線程將是所有其它核心線程的父線程(ps -aux 命令查看,所有由[]括起的那些)

----------------------------------------------------------------------------------------------------------------------------------------------------------------------

static int __init kernel_init(void * unused)
{
 lock_kernel();

   。。。。。。
  do_basic_setup();          //載入驅動程式工作的地方

 /*
  * check if there is an early userspace init.  If yes, let it do all
  * the work
  */

 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();        //掛載根檔案系統
 } 

 init_post();   //由核心線程蛻變成第一個使用者進程init
 return 0;
}

---------------------------------------------------------------------------------------------------------------------------------------------------------------------

讓我們來看看do_basic_setup()函數是如何載入驅動的:

--------------------------------------------------------------------------------------------------------------------------------------------------------------------

static void __init do_basic_setup(void)

{
 /* drivers will send hotplug events */
 init_workqueues();                 //初始化工作隊列
 usermodehelper_init();        //使用者態的khelper守護進程
 driver_init();                           //裝置模型中一些基礎結構體的初始化和裝置的註冊
init_irq_proc();                       //irq程式的初始化
do_initcalls();                        //(*call)()* 具體的initcall函數調用
}

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

現在開始我們有點眉目了。等不及了,進來看看吧!

----------------------------------------------------------------------------------------------------------------------------------------------------------------

static void __init do_initcalls(void)
{
 initcall_t *call;
 int count = preempt_count();

 for (call = __initcall_start; call <
__initcall_end; call++) {
  ktime_t t0, t1, delta;
  char *msg = NULL;
  char msgbuf[40];
  int result;

  。。。。。。

   result = (*call)();

 。。。。。。

     }

 /* Make sure there is no pending stuff from the initcall sequence */
 flush_scheduled_work();
}

-----------------------------------------------------------------------------------------------------------------------------------------------------------

這個__initcall_start是在檔案        arch/arm/kernel/vmlinux.lds
這個檔案是核心ld的時候使用的.其中定義了各個sectioin,看看就明白了。
在這個檔案中有個.initcall.init, 代碼如下:
----------------------------------------------------------------------------------------------------------------------------------------------------------

SECTIONS
{
 .init : {   /* Init code and data  */
   *(.init.text)
  _einittext = .;
  __proc_info_begin = .;
   *(.proc.info.init)
  __proc_info_end = .;
  __arch_info_begin = .;
   *(.arch.info.init)
  __arch_info_end = .;
  __tagtable_begin = .;
   *(.taglist.init)
  __tagtable_end = .;
  . = ALIGN(16);
  __setup_start = .;
   *(.init.setup)
  __setup_end = .;
  __early_begin = .;
   *(.early_param.init)
  __early_end = .;
  __initcall_start = .;
    *(.initcall1.init)
   *(.initcall2.init)
   *(.initcall3.init)
   *(.initcall4.init)
   *(.initcall5.init)
   *(.initcall6.init)
   *(.initcall7.init)
  __initcall_end = .;
  __con_initcall_start = .;
   *(.con_initcall.init)
  __con_initcall_end = .;

。。。。。。

  }

。。。。。。

}

--------------------------------------------------------------------------------------------------------------------------------------------------------

這裡有7個初始化的優先順序,核心會按照這個優先順序的順序依次載入.
這些優先順序是在檔案include/linux/init.h 中定義的. 你注意一下宏 __define_initcall的實現就明白了.
相關代碼如下:
#define __define_initcall(level,fn) \
        static initcall_t __initcall_##fn __attribute_used__ \
        __attribute__((__section__(".initcall" level ".init"))) = fn

#define core_initcall(fn)                __define_initcall("1",fn)
#define postcore_initcall(fn)                __define_initcall("2",fn)
#define arch_initcall(fn)                __define_initcall("3",fn)
#define subsys_initcall(fn)                __define_initcall("4",fn)
#define fs_initcall(fn)                        __define_initcall("5",fn)
#define device_initcall(fn)                __define_initcall("6",fn)
#define late_initcall(fn)                __define_initcall("7",fn)

---------------------------------------------------------------------------------------------------------------------------------------------------------

而我們經常寫的裝置驅動程式中就常用的module_init進行修飾,其實就是對應了優先順序 6
#define __initcall(fn) device_initcall(fn)

#define module_init(x)        __initcall(x);

正是這樣系統中的驅動程式會在核心啟動過程中,被連結在一個段中統一被載入。

----------------------------------------------------------------------------------------------------------------------------------------------------------

         其實所以的__init函數在區段.initcall.init中儲存了一份函數指標,在初始化時核心會通過這些函數指標調用這些__init函數指標,並在整個初始化完成後,釋放整個init區段(包括.init.text,.initcall.init等)。

注意,這些函數在核心初始化過程中的調用順序只和這裡的函數指標的順序有關,各個子區段之間的順序是確定的,即先調用. initcall1.init中的函數指標,再調用.initcall2.init中的函數指標,等等。而在每個子區段中的函數指標的順序是和連結順序相關的,是不確定的。

未完待續……

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.