FreeBSD kernel是一个膨大的系统, 对于这样一个大系统, 里面往往包含了大量的子系统和  
模块,当系统初始化时这些模块就需要初始化, 按照通常的思路,这些初始化过程必须在某处  
被显式地调用,这样一来,当你新增某个模块,你必须再修改那个系统初始化的地方来调用这  
个新增模块的初始化过程, 而且由于ANSI C语言的限制,调用某个函数最好先声明,这样当系  
统的初始化过程开始增加时, 那个调用初始化过程的文件开始大量包含那些本来不相关的头  
文件, 偶合度就增加了, 这是一种不好的设计.  

FreeBSD为了应付这种情况, 使用一种叫做SYSINIT的机制. 我们知道FreeBSD使用一种叫做  
ELF的二进制目标执行文件格式. 这种文件格式允许文件内部组织成结构化的方式, 文件内  
部可以由不同的组成部分(section), FreeBSD正是利用了这种机制.   

FreeBSD使用GNU GCC作为其C语言编译器, 这种编译器允许在C源程序中嵌入汇编语言代码,  
FreeBSD通过在C源程序中加入汇编指令来在目标文件中增加额外的section, 在文件  
/sys/sys/linker_set.h中定义如下:  

#ifdef __alpha__  
#define MAKE_SET(set, sym)                                              \  
        static void const * const __set_##set##_sym_##sym = &sym;       \  
        __asm(".align 3");                                              \  
        __asm(".section .set." #set ",\"aw\"");                         \  
        __asm(".quad " #sym);                                           \  
        __asm(".previous")  
#else  
#define MAKE_SET(set, sym)     
#define MAKE_SET(set, sym)                                              \  
        static void const * const __set_##set##_sym_##sym = &sym;       \  
        __asm(".section .set." #set ",\"aw\"");                         \  
        __asm(".long " #sym);                                           \  
        __asm(".previous")  
#endif  
#define TEXT_SET(set, sym) MAKE_SET(set, sym)  
#define DATA_SET(set, sym) MAKE_SET(set, sym)  

程序一旦在某处调用DATA_SET宏指令, 就会将相应的汇编符号加入到目标文件. 例如:  
int myint;  
DATA_SET(myset, myint);  
这两句话将导致在目标文件中创建一个myset section, 并且myint的地址将被放入这个  
section中.  

系统的初始化必须按严格的顺序进行, 为此FreeBSD定义了很多子系统的顺序号, 这些顺序  
连同SYSINIT的许多相关定义在/sys/sys/kernel.h头文件中:  

enum sysinit_sub_id {  
        SI_SUB_DUMMY            = 0x0000000,    /* not executed; for linker*/  
        SI_SUB_DONE             = 0x0000001,    /* processed*/  
        SI_SUB_CONSOLE          = 0x0800000,    /* console*/  
        SI_SUB_COPYRIGHT        = 0x0800001,    /* first use of console*/  
        SI_SUB_TUNABLES         = 0x0700000,    /* establish tunable values */  
        SI_SUB_VM               = 0x1000000,    /* virtual memory system init*/  
        SI_SUB_KMEM             = 0x1800000,    /* kernel memory*/  
        SI_SUB_KVM_RSRC         = 0x1A00000,    /* kvm operational limits*/  
        SI_SUB_CPU              = 0x1e00000,    /* CPU resource(s)*/  
        SI_SUB_KLD              = 0x1f00000,    /* KLD and module setup */  
        SI_SUB_INTRINSIC        = 0x2000000,    /* proc 0*/  
        SI_SUB_VM_CONF          = 0x2100000,    /* config VM, set limits*/  
        SI_SUB_RUN_QUEUE        = 0x2200000,    /* the run queue*/  
        SI_SUB_CREATE_INIT      = 0x2300000,    /* create the init process */  
        SI_SUB_DRIVERS          = 0x2400000,    /* Let Drivers initialize */  
        SI_SUB_CONFIGURE        = 0x3800000,    /* Configure devices */  
        SI_SUB_VFS              = 0x4000000,    /* virtual file system*/  
        SI_SUB_CLOCKS           = 0x4800000,    /* real time and stat clocks*/  
        SI_SUB_MBUF             = 0x5000000,    /* mbufs*/  
        SI_SUB_CLIST            = 0x5800000,    /* clists*/  
        SI_SUB_SYSV_SHM         = 0x6400000,    /* System V shared memory*/  
        SI_SUB_SYSV_SEM         = 0x6800000,    /* System V semaphores*/  
        SI_SUB_SYSV_MSG         = 0x6C00000,    /* System V message queues*/  
        SI_SUB_P1003_1B         = 0x6E00000,    /* P1003.1B realtime */  
        SI_SUB_PSEUDO           = 0x7000000,    /* pseudo devices*/  
        SI_SUB_EXEC             = 0x7400000,    /* execve() handlers */  
        SI_SUB_PROTO_BEGIN      = 0x8000000,    /* XXX: set splimp (kludge)*/  
        ...  
};  

子系统内还有顺序号:  
enum sysinit_elem_order {  
        SI_ORDER_FIRST          = 0x0000000,    /* first*/  
        SI_ORDER_SECOND         = 0x0000001,    /* second*/  
        SI_ORDER_THIRD          = 0x0000002,    /* third*/  
        SI_ORDER_MIDDLE         = 0x1000000,    /* somewhere in the middle */  
        SI_ORDER_ANY            = 0xfffffff     /* last*/  
};  

FreeBSD为每个想要在系统初始化时被调用的函数, 定义两个函数类型:  
typedef void (*sysinit_nfunc_t) __P((void *));  
typedef void (*sysinit_cfunc_t) __P((const void *));  
它们是系统初始化被调用时使用的函数原型.  
两个重要的宏使得初始化函数能够在系统开始时被执行:  

#define C_SYSINIT(uniquifier, subsystem, order, func, ident)    \  
        static struct sysinit uniquifier ## _sys_init = {       \  
                subsystem,                                      \  
                order,                                          \  
                func,                                           \  
                ident                                           \  
        };                                                      \  
        DATA_SET(sysinit_set,uniquifier ## _sys_init);  

#define SYSINIT(uniquifier, subsystem, order, func, ident)      \  
        C_SYSINIT(uniquifier, subsystem, order,                 \  
        (sysinit_cfunc_t)(sysinit_nfunc_t)func, (void *)ident)  

其中每个初始化函数被存储成这样一个结构:  
        struct sysinit {  
           unsigned int    subsystem;              /* subsystem identifier*/  
           unsigned int    order;                  /* init order within subsystem*/  
           sysinit_cfunc_t func;                   /* function             */  
           const void      *udata;                 /* multiplexer/argument */  
        };  
这个结构包含了子系统编号, 子系统中的顺序号, 初始化函数的地址, 以及这个函数  
使用的参数.  

现在如果有个函数想要在系统启动时自动被调用, 并且知道这个函数是为VM子系统做准备工  
作, 可以这样申明:  

long myvar;  
void init_myvar(void *p)  
{  
     *(long *)p = 2;  
}  
SYSINIT(init_myvar, SI_SUB_VM, 1000, init_myvar, &myvar)  

这样声明的初始化过程分布在很多目标文件中, 当gcc的连接编辑器ld运行时就会把属于同  
一个section的数据合并到一个连续的地址块中.  
由于在这个section中包含的只能是指向sysinit结构的指针,这样FreeBSD就可以把这个地址  
当成一个sysinit* 的数组, FreeBSD找出这个sysinit_set地址, 边历这个数组并调用其中  
的初始化函数. 为了确切知道这个section的大小(直接读ELF是可能的,但是那样太复杂,要  
知道kernel调用初始化过程时文件系统可能还没有初始化呢), 系统中包含一个工具  
gensetdefs, 这个工具能扫描给出的一组.o目标文件, 并找到任何名字是由.set.开头的  
section, 它统计有多少个这样的的初始化函数, 并在sysinit_set的开头生成一个长整形  
计数器. gensetdefs生成三个文件:  
setdef0.c setdef1.c setdefs.h  

文件setdef0.c的内容:  

--------------------------------------------------------  
/* THIS FILE IS GENERATED, DO NOT EDIT. */  

#define DEFINE_SET(set, count)                  \  
__asm__(".section .set." #set ",\"aw\"");       \  
__asm__(".globl " #set);                        \  
__asm__(".type " #set ",@object");              \  
__asm__(".p2align 2");                          \  
__asm__(#set ":");                              \  
__asm__(".long " #count);                       \  
__asm__(".previous")  

#include "setdefs.h"            /* Contains a `DEFINE_SET' for each set */  
--------------------------------------------------------  

这里的DEFINE_SET效果就是申明一C结构:  
struct linker_set {  
        int     ls_length;  
        void    *ls_items[1];           /* really ls_length of them,  
                                                * trailing NULL */  
};  

文件setdef1.c的内容:  

--------------------------------------------------------  
/* THIS FILE IS GENERATED, DO NOT EDIT. */  

#define DEFINE_SET(set, count)                          \  
__asm__(".section .set." #set ",\"aw\"");       \  
__asm__(".long 0");                     \  
__asm__(".previous")  

#include "setdefs.h"            /* Contains a `DEFINE_SET' for each set */  

这个DEFINE_SET在某个section中放入一个 long 0.  
--------------------------------------------------------  

文件setdefs.h的内容:  

DEFINE_SET(cons_set, 3);  
DEFINE_SET(kbddriver_set, 2);  
DEFINE_SET(periphdriver_set, 5);  
DEFINE_SET(scrndr_set, 9);  
DEFINE_SET(scterm_set, 1);  
DEFINE_SET(sysctl_set, 552);  
DEFINE_SET(sysinit_set, 323);  
DEFINE_SET(sysuninit_set, 155);  
DEFINE_SET(vga_set, 9);  
DEFINE_SET(videodriver_set, 4);  

当kernel被连接时, 在Makefile中setdef0.o被安排最前面, 这样ld就把这个初始化函数的  
计数器安排在这个section的最前面. FreeBSD kernel就能从这个section的开头读到这个计  
数器, 也就知道了有多少个初始化函数. 在Makefile中被安排在中间的的是FreeBSD的其他  
.o文件, 最后由setdef1.o压阵. setdef1.c定义了一个空指针,用以表示这个section的结束  
,这种安排, 我把它叫做夹三明治.  

初始化过程的调用被安排在内核 /sys/kern/init_main.c的mi_startup函数中, mi_startup  
是系统启动过程中, 第一个被执行的C语言函数,  它做的第一件事情就是调用这些初始化函  
数, 开始时对所有的初始化过程做优先级排序, 然后顺序调用它们.  

void                      
mi_startup(void)  
{                 
                          
        register struct sysinit **sipp;         /* system initialization*/  
        register struct sysinit **xipp;         /* interior loop of sort*/  
        register struct sysinit *save;          /* bubble*/  

restart:          
          
        这是优先级别排序, 这里没有使用那个在setdef0.c中定义的计数器, 而是使用  
        了setdef1.c中定义的空指针作为结束标志.  
          
        /*        
         * Perform a bubble sort of the system initialization objects by  
         * their subsystem (primary key) and order (secondary key).  
         */       
        for (sipp = sysinit; *sipp; sipp++) {  
                for (xipp = sipp + 1; *xipp; xipp++) {  
                        if ((*sipp)->subsystem < (*xipp)->subsystem ||  
                             ((*sipp)->subsystem == (*xipp)->subsystem &&  
                              (*sipp)->order <= (*xipp)->order))  
                                continue;       /* skip*/  
                        save = *sipp;  
                        *sipp = *xipp;  
                        *xipp = save;  
                }  
        }  

        /*  
         * Traverse the (now) ordered list of system initialization tasks.  
         * Perform each task, and continue on to the next task.  
         *  
         * The last item on the list is expected to be the scheduler,  
         * which will not return.  
         */  
        for (sipp = sysinit; *sipp; sipp++) {  

                if ((*sipp)->subsystem == SI_SUB_DUMMY)  
                        continue;       /* skip dummy task(s)*/  

这是按顺序调用:  
/*  
         * Traverse the (now) ordered list of system initialization tasks.  
         * Perform each task, and continue on to the next task.  
         *  
         * The last item on the list is expected to be the scheduler,  
         * which will not return.  
         */  
        for (sipp = sysinit; *sipp; sipp++) {  

                if ((*sipp)->subsystem == SI_SUB_DUMMY)  
                        continue;       /* skip dummy task(s)*/  

                if ((*sipp)->subsystem == SI_SUB_DONE)  
                        continue;  

                /* Call function */  
                (*((*sipp)->func))((*sipp)->udata);  

                /* Check off the one we're just done */  
                (*sipp)->subsystem = SI_SUB_DONE;  

                /* Check if we've installed more sysinit items via KLD */  
                if (newsysinit != NULL) {  
                        if (sysinit != (struct sysinit **)sysinit_set.ls_items)  
                                free(sysinit, M_TEMP);  
                        sysinit = newsysinit;  
                        newsysinit = NULL;  
                        goto restart;  
                }  
        }  

        panic("Shouldn't get here!");  
}  
      

SRC=http://www.moon-soft.com/program/bbs/readelite432617.htm

FreeBSD 内核中的SYSINIT分析【转】的更多相关文章

  1. linux内核中链表代码分析---list.h头文件分析(一)【转】

    转自:http://blog.chinaunix.net/uid-30254565-id-5637596.html linux内核中链表代码分析---list.h头文件分析(一) 16年2月27日17 ...

  2. linux内核中链表代码分析---list.h头文件分析(二)【转】

    转自:http://blog.chinaunix.net/uid-30254565-id-5637598.html linux内核中链表代码分析---list.h头文件分析(二) 16年2月28日16 ...

  3. 内核中 EXPORT_SYMBOL 标志分析

    内核版本:Linux-4.19 1. EXPORT_SYMBOL 的作用: EXPORT_SYMBOL 定义的函数或者符号对全部内核代码公开,不用修改内核代码就可以在其它内核模块中直接调用,即使用 E ...

  4. linux内核中mtd架构分析

    一. 引言 MTD(memory technology device内存技术设备)是用于访问memory设备(RAM.ROM.flash)的Linux的子系统.MTD的主要目的是为了使新的memory ...

  5. Linux内核分析--内核中的数据结构双向链表【转】

    本文转自:http://blog.csdn.net/yusiguyuan/article/details/19840065 一.首先介绍内核中链表 内核中定义的链表是双向链表,在上篇文章--libev ...

  6. FreeBSD中的SYSINIT框架【转】

    SYSINIT是一个通用的调用排序与分别执行机制的框架.FreeBSD目前使用它来进行内核的动态初始化.SYSINIT使得FreeBSD的内核各子系统可以在内核或模块动态加载链接时被重整.添加.删除. ...

  7. Linux内核中的GPIO系统之(3):pin controller driver代码分析

    一.前言 对于一个嵌入式软件工程师,我们的软件模块经常和硬件打交道,pin control subsystem也不例外,被它驱动的硬件叫做pin controller(一般ARM soc的datash ...

  8. Linux内核中的list用法和实现分析

    这些天在思考知识体系的完整性,发现总是对消息队列的实现不满意,索性看看内核里面的链表实现形式,这篇文章就当做是学习的i笔记吧.. 内核代码中有很多的地方使用了list,而这个list的用法又跟我们平时 ...

  9. Linux内核中SPI总线驱动分析

    本文主要有两个大的模块:一个是SPI总线驱动的分析 (研究了具体实现的过程): 另一个是SPI总线驱动的编写(不用研究具体的实现过程). 1 SPI概述 SPI是英语Serial Peripheral ...

随机推荐

  1. 微服务实践(五):微服务的事件驱动数据管理 - DockOne.io

    原文:微服务实践(五):微服务的事件驱动数据管理 - DockOne.io [编者的话]本文是使用微服务创建应用系列的第五篇文章.第一篇文章介绍了微服务架构模式,并且讨论了使用微服务的优缺点:第二和第 ...

  2. Windows服务安装命令:

    sc create YY.SmsPlatform.RemoteDataCenter binPath= "E:\YY.SmsPlatform\YY.SmsPlatform.RemoteData ...

  3. 如何使stm32程序更好移植, 结构体相当于define

    原创:转载请标注引用地址 如何定义 led1对应于PA8  呢 :对于我一开始学习的方法:: #include "main.h" #define led1      GPIO_Pi ...

  4. jQuery实现多种切换效果的图片切换的五款插件

    1:Nivo SliderNivoslider:丰富的图片切换效果 官方网址:https://themeisle.com/plugins/nivo-slider 查看演示:https://www.he ...

  5. [AngularFire2] Update multi collections at the same time with FirebaseRef

    At some point, you might need to udpate multi collections and those collections should all updated s ...

  6. Yii Framework2.0开发教程(1)配置环境及第一个应用HelloWorld

    准备工作: 我用的开发环境是windows下的apache+mysql+php 编辑器不知道该用哪个好.临时用dreamweaver吧 我自己的http://localhost/相应的根文件夹是E:/ ...

  7. [Docker] Download and Remove Docker Images

    Learn the basics of downloading and pulling Docker images from Docker Hub. Learn the difference betw ...

  8. [Elm] Installing and setting up Elm

    Before writing any Elm we need to first install the runtime locally. In this lesson we install the E ...

  9. jquery ajax实现省市二级联动

    今天给大家带来使用jQuery ajax实现的省市联动效果.我们直奔主题,先说下实现思路: 准备数据 这里数据库我使用的是mysql,先看下表格: provience表 city表 这里使用provi ...

  10. NavMesh动态碰撞

    今天遇到一个问题,就是怎样处理一些动态的障碍物. NavMesh是能够躲避静态的障碍物.NavMeshObstacle的作用就是动态添加障碍. 可是有个问题,NavMeshObstacle是圆,连椭圆 ...