module_init详解(转)-4.1

就像你写C程序需要包含C库的头文件那样,Linux内核编程也需要包含Kernel头文件,大多的Linux驱动程序需要包含下面三个头文件:

 

    #include <linux/init.h>

    #include <linux/module.h>

    #include <linux/kernel.h>

其中,init.h 定义了驱动的初始化和退出相关的函数,kernel.h 定义了经常用到的函数原型及宏定义,module.h 定义了内核模块相关的函数、变量及宏。

 

几乎每个linux驱动都有个module_init(与module_exit的定义在Init.h (/include/linux)中)。没错,驱动的加载就靠它。为什么需要这样一个宏?原因是按照一般的编程想法,各部分的初始化函数会在一个固定的函数里调用比如:

 

    void init(void)

    {

        init_a();

        init_b();

    }

 

如果再加入一个初始化函数呢,那么在init_b()后面再加一行:init_c();这样确实能完成我们的功能,但这样有一定的问题,就是不能独立的添加初始化函数,每次添加一个新的函数都要修改init函数。可以采用另一种方式来处理这个问题,只要用一个宏来修饰一下:

 

    void init_a(void)

    {

  

    }

    __initlist(init_a, 1);

 

它是怎么样通过这个宏来实现初始化函数列表的呢?先来看__initlist的定义:

    #define __init __attribute__((unused, __section__(".initlist")))

    

    #define __initlist(fn, lvl) /

        static initlist_t __init_##fn __init = { /

        magic:    INIT_MAGIC, /

        callback: fn, /

        level:   lvl }

                          

    #define __init __attribute__((unused, __section__(".initlist")))

  

    #define __initlist(fn, lvl) /

  

    static initlist_t __init_##fn __init = { /

     magic:    INIT_MAGIC, /

     callback: fn, /

     level:   lvl }

 

请注意:__section__(".initlist"),这个属性起什么作用呢?它告诉连接器这个变量存放在.initlist区段,如果所有的初始化函数都是用这个宏,那么每个函数会有对应的一个initlist_t结构体变量存放在.initlist区段,也就是说我们可以在.initlist区段找到所有初始化函数的指针。怎么找到.initlist区段的地址呢?

 

    extern u32 __initlist_start;

    extern u32 __initlist_end;

 

这两个变量起作用了,__initlist_start是.initlist区段的开始,__initlist_end是结束,通过这两个变量我们就可以访问到所有的初始化函数了。这两个变量在那定义的呢?在一个连接器脚本文件里

    . = ALIGN(4);

    .initlist : {

      __initlist_start = .;

      *(.initlist)

      __initlist_end = .;

    }

 

 

 

   . = ALIGN(4);

   .initlist : {

    __initlist_start = .;

    *(.initlist)

    __initlist_end = .;

   }

 

   . = ALIGN(PAGE_SIZE);       /* Init code and data. */

   __init_begin = .;

   INIT_TEXT_SECTION(PAGE_SIZE)

   .init.data : { INIT_DATA }

   .init.setup : { INIT_SETUP(16) }

   .initcall.init : {

       INIT_CALLS

   }

 

具体的关于链接文件的定义mlinux.lds.h

 

#define INIT_CALLS                              \

      VMLINUX_SYMBOL(__initcall_start) = .;        \

      *(.initcallearly.init)                         \

      INIT_CALLS_LEVEL(0)                       \

      INIT_CALLS_LEVEL(1)                       \

      INIT_CALLS_LEVEL(2)                       \

      INIT_CALLS_LEVEL(3)                       \

      INIT_CALLS_LEVEL(4)                       \

      INIT_CALLS_LEVEL(5)                       \

      INIT_CALLS_LEVEL(rootfs)                   \

       INIT_CALLS_LEVEL(6)                      \

       INIT_CALLS_LEVEL(7)                      \

       VMLINUX_SYMBOL(__initcall_end) = .;

 

这两个变量的值正好定义在.initlist区段的开始和结束地址,所以我们能通过这两个变量访问到所有的初始化函数。

 

与此类似,内核中也是用到这种方法,所以我们写驱动的时候比较独立,不用我们自己添加代码在一个固定的地方来调用我们自己的初始化函数和退出函数,连接器已经为我们做好了。先来分析一下module_init。定义如下:

 

#define module_init(x)                   __initcall(x);              //include/linux/init.h

#define __initcall(fn)                   device_initcall(fn)

#define device_initcall(fn)            __define_initcall("6",fn,6)

#define __define_initcall(level,fn,id) /

          static initcall_t __initcall_##fn##id __used /

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

 

#define module_init(x)     __initcall(x);              //include/linux/init.h

 

#define __initcall(fn) device_initcall(fn)

 

#define device_initcall(fn)                 __define_initcall("6",fn,6)

 

#define __define_initcall(level,fn,id) /

         static initcall_t __initcall_##fn##id __used /

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

 

 

如果某驱动想以func作为该驱动的入口,则可以如下声明:module_init(func);被上面的宏处理过后,变成__initcall_func6__used加入到内核映像的".initcall"区。内核的加载的时候,会搜索".initcall"中的所有条目,并按优先级加载它们,普通驱动程序的优先级是6。其它模块优先级列出如下:值越小,越先加载。

#define pure_initcall(fn)                 __define_initcall("0",fn,0)

#define core_initcall(fn)                 __define_initcall("1",fn,1)

#define core_initcall_sync(fn)            __define_initcall("1s",fn,1s)

#define postcore_initcall(fn)             __define_initcall("2",fn,2)

#define postcore_initcall_sync(fn)        __define_initcall("2s",fn,2s)

#define arch_initcall(fn)                 __define_initcall("3",fn,3)

#define arch_initcall_sync(fn)            __define_initcall("3s",fn,3s)

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

#define subsys_initcall_sync(fn)          __define_initcall("4s",fn,4s)

#define fs_initcall(fn)                   __define_initcall("5",fn,5)

#define fs_initcall_sync(fn)              __define_initcall("5s",fn,5s)

#define rootfs_initcall(fn)               __define_initcall("rootfs",fn,rootfs)

#define device_initcall(fn)               __define_initcall("6",fn,6)

#define device_initcall_sync(fn)          __define_initcall("6s",fn,6s)

#define late_initcall(fn)                 __define_initcall("7",fn,7)

#define late_initcall_sync(fn)            __define_initcall("7s",fn,7s)

#define pure_initcall(fn)                 __define_initcall("0",fn,0)

#define core_initcall(fn)                 __define_initcall("1",fn,1)

#define core_initcall_sync(fn)            __define_initcall("1s",fn,1s)

#define postcore_initcall(fn)             __define_initcall("2",fn,2)

#define postcore_initcall_sync(fn)        __define_initcall("2s",fn,2s)

#define arch_initcall(fn)                 __define_initcall("3",fn,3)

#define arch_initcall_sync(fn)            __define_initcall("3s",fn,3s)

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

#define subsys_initcall_sync(fn)          __define_initcall("4s",fn,4s)

#define fs_initcall(fn)                   __define_initcall("5",fn,5)

#define fs_initcall_sync(fn)              __define_initcall("5s",fn,5s)

#define rootfs_initcall(fn)               __define_initcall("rootfs",fn,rootfs)

#define device_initcall(fn)               __define_initcall("6",fn,6)

#define device_initcall_sync(fn)          __define_initcall("6s",fn,6s)

#define late_initcall(fn)                 __define_initcall("7",fn,7)

#define late_initcall_sync(fn)            __define_initcall("7s",fn,7s)

 

可以看到,被声明为pure_initcall的最先加载。

 

module_init除了初始化加载之外,还有后期释放内存的作用。linuxkernel中有很大一部分代码是设备驱动代码,这些驱动代码都有初始化和反初始化函数,这些代码一般都只执行一次,为了有更有效的利用内存,这些代码所占用的内存可以释放出来。

 

linux就是这样做的,对只需要初始化运行一次的函数都加上__init属性,__init宏告诉编译器如果这个模块被编译到内核则把这个函数放到(.init.text)段,module_exit的参数卸载时同__init类似,如果驱动被编译进内核,则__exit宏会忽略清理函数,因为编译进内核的模块不需要做清理工作,显然__init和__exit对动态加载的模块是无效的,只支持完全编译进内核。

 

在kernel初始化后期,释放所有这些函数代码所占的内存空间。连接器把带__init属性的函数放在同一个section里,在用完以后,把整个section释放掉。当函数初始化完成后这个区域可以被清除掉以节约系统内存。Kenrel启动时看到的消息“Freeing unused kernel memory: xxxk freed”同它有关。

 

我们看源码,init/main.c中start_kernel是进入kernel()的第一个c函数,在这个函数的最后一行是rest_init();

 

static void rest_init(void)

{

     .....

     kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);

     unlock_kernel();

     cpu_idle();

     .....

}

创建了一个内核线程,主函数kernel_init末尾有个函数:

/*

 * 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();

 

 /*

  * 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();

 

这个init_post中的第一句就是free_initmem();就是用来释放初始化代码和数据的。

void free_initmem(void)

{

    if (!machine_is_integrator() && !machine_is_cintegrator()) {

    free_area((unsigned long)(&__init_begin),

     (unsigned long)(&__init_end),

     "init");

     }

}

 

void free_initmem(void)

 

{

    if (!machine_is_integrator() && !machine_is_cintegrator()) {

      free_area((unsigned long)(&__init_begin),

      (unsigned long)(&__init_end),

       "init");

    }

 

}

 

接下来就是kernel内存管理的事了。

 

初始化的调用顺序

main()->linux_main()->start_uml()->start_kernel_proc()

        ->start_kernelàrest_init->kernel_init->do_basic_setup->do_initcalls

1. --prefix=${PATH_INSTALL}/nginx: --prefix 指定安装目录,${PATH_INSTALL}/nginx 为安装目录的路径。可选值为任意路径,根据实际需要进行设置。此参数会影响到 nginx 的安装位置,例如配置文件的位置、日志文件的位置等。 2. --user=nginx: --user 指定 nginx 运行的用户,默认为 nobody。可选值为任意用户,根据实际需要进行设置。此参数会影响到 nginx 运行的权限。 3. --group=nginx: --group 指定 nginx 运行的用户组,默认为 nobody。可选值为任意用户组,根据实际需要进行设置。此参数会影响到 nginx 运行的权限。 4. --with-http_ssl_module: --with-http_ssl_module 开启 SSL/TLS 功能,支持 HTTPS 协议。可选值为 --with-http_ssl_module 或 --without-http_ssl_module。如果需要支持 HTTPS 协议,则必须开启此选项。 5. --with-http_realip_module: --with-http_realip_module 开启真实 IP 模块,用于获取客户端真实 IP 地址。可选值为 --with-http_realip_module 或 --without-http_realip_module。如果需要获取客户端真实 IP 地址,则必须开启此选项。 6. --with-http_addition_module: --with-http_addition_module 开启添加响应头模块,用于添加自定义的响应头信息。可选值为 --with-http_addition_module 或 --without-http_addition_module。 7. --with-http_sub_module: --with-http_sub_module 开启替换响应内容模块,用于替换响应内容中的关键字。可选值为 --with-http_sub_module 或 --without-http_sub_module。 8. --with-http_dav_module: --with-http_dav_module 开启 WebDAV 模块,用于支持 WebDAV 协议。可选值为 --with-http_dav_module 或 --without-http_dav_module。 9. --with-http_flv_module: --with-http_flv_module 开启 FLV 视频流模块,用于支持 FLV 格式的视频流。可选值为 --with-http_flv_module 或 --without-http_flv_module。 10. --with-http_mp4_module: --with-http_mp4_module 开启 MP4 视频流模块,用于支持 MP4 格式的视频流。可选值为 --with-http_mp4_module 或 --without-http_mp4_module。 11. --with-http_gunzip_module: --with-http_gunzip_module 开启 Gzip 解压缩模块,用于支持 Gzip 压缩格式。可选值为 --with-http_gunzip_module 或 --without-http_gunzip_module。 12. --with-http_gzip_static_module: --with-http_gzip_static_module 开启 Gzip 静态文件压缩模块,用于对静态文件进行 Gzip 压缩。可选值为 --with-http_gzip_static_module 或 --without-http_gzip_static_module。 13. --with-http_stub_status_module: --with-http_stub_status_module 开启状态页面模块,用于查看 nginx 的状态信息。可选值为 --with-http_stub_status_module 或 --without-http_stub_status_module。 14. --with-stream: --with-stream 开启 TCP/UDP 代理模块,用于支持 TCP/UDP 协议。可选值为 --with-stream 或 --without-stream。 15. --with-stream_ssl_module: --with-stream_ssl_module 开启 SSL/TLS 功能,支持 TCP/UDP 的 SSL/TLS 加密。可选值为 --with-stream_ssl_module 或 --without-stream_ssl_module。 16. --with-http_v2_module: --with-http_v2_module 开启 HTTP/2 模块,用于支持 HTTP/2 协议。可选值为 --with-http_v2_module 或 --without-http_v2_module。 17. --with-pcre: --with-pcre 指定使用 PCRE 库进行正则表达式匹配。可选值为 --with-pcre 或 --without-pcre。 18. --with-openssl=/www/server/nginx/src/openssl: --with-openssl 指定使用 OpenSSL 库进行 SSL/TLS 加密。可选值为 OpenSSL 库的路径。如果开启了 SSL/TLS 功能,则必须指定此选项。 19. --with-stream_ssl_preread_module: --with-stream_ssl_preread_module 开启 TCP/UDP SSL/TLS 握手前置模块,用于在握手前解析 SSL/TLS 协议。可选值为 --with-stream_ssl_preread_module 或 --without-stream_ssl_preread_module。 20. --with-http_image_filter_module: --with-http_image_filter_module 开启图片处理模块,用于对图片进行缩放、裁剪等操作。可选值为 --with-http_image_filter_module 或 --without-http_image_filter_module。 21. --with-ipv6: --with-ipv6 开启 IPv6 支持。可选值为 --with-ipv6 或 --without-ipv6。 22. --with-ld-opt=-Wl,-E: --with-ld-opt 指定链接器选项,-Wl,-E 表示启用链接器的 export-dynamic 选项。可选值为任意链接器选项,根据实际需要进行设置。此参数会影响到 nginx 的链接器选项。 23. --with-cc-opt=-Wno-error: --with-cc-opt 指定编译器选项,-Wno-error 表示忽略编译器的错误提示。可选值为任意编译器选项,根据实际需要进行设置。此参数会影响到 nginx 的编译器选项。 24. --with-ld-opt=-ljemalloc: --with-ld-opt 指定链接器选项,-ljemalloc 表示链接 jemalloc 库。可选值为任意链接器选项,根据实际需要进行设置。此参数会影响到 nginx 的链接器选项。 25. --add-module=/www/server/nginx/src/ngx_devel_kit: --add-module 指定添加第三方模块,/www/server/nginx/src/ngx_devel_kit 为第三方模块的路径。可选值为任意第三方模块的路径,根据实际需要进行设置。此参数会影响到 nginx 的模块加载顺序。 26. --add-module=/www/server/nginx/src/lua_nginx_module: --add-module 指定添加第三方模块,/www/server/nginx/src/lua_nginx_module 为第三方模块的路径。可选值为任意第三方模块的路径,根据实际需要进行设置。此参数会影响到 nginx 的模块加载顺序。 27. --add-module=/www/server/nginx/src/ngx_cache_purge: --add-module 指定添加第三方模块,/www/server/nginx/src/ngx_cache_purge 为第三方模块的路径。可选值为任意第三方模块的路径,根据实际需要进行设置。此参数会影响到 nginx 的模块加载顺序。 28. --add-module=/www/server/nginx/src/ngx_http_substitutions_filter_module-master: --add-module 指定添加第三方模块,/www/server/nginx/src/ngx_http_substitutions_filter_module-master 为第三方模块的路径。可选值为任意第三方模块的路径,根据实际需要进行设置。此参数会影响到 nginx 的模块加载顺序。 29. --add-module=/www/server/nginx/src/nginx-dav-ext-module: --add-module 指定添加第三方模块,/www/server/nginx/src/nginx-dav-ext-module 为第三方模块的路径。可选值为任意第三方模块的路径,根据实际需要进行设置。此参数会影响到 nginx 的模块加载顺序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值