动态链接与可安装模块

这些现象在我接触linux之前一直困扰着我:

1.为什么有些游戏软件2~3G的大小,只需要更新几兆的包,就可以有一些差异性的功能?

2.为什么在安装驱动前不能操作IO,但是在安装驱动后,我们的应用软件就可以通过驱动程序访问IO,那么应用软件是如何找到驱动操作IO的函数的呢?

3.对于linux而言,驱动属于内核的一部分,那么是不是意味这能随便改变内核的代码结构去访问硬件,linux是如何做到安全性和灵活性的平衡呢?

之所以存在上面的疑惑,是因为在嵌入式开发过程中,无论是多么微小的更改,都需要将整个工程重新编译链接,然后得到一个可执行文件,刷进cpu中执行。

1.动态链接

这部分内容主要来自《深入理解计算机系统》第7章内容,以下是对其总结:

1.1.编译之后,生成可重定位的目标文件,其格式为:

.text:存放代码

.rodata:存放只读数据,例如printf中的第一个传参字符串

.data:存放初始化的全局或者静态变量

.bss:未初始化的全局或者静态变量(为了节省空间,这个在磁盘上只是占位符,并不占空间)

.symtab:存放该编译后文件全局(或者静态)变量(或者函数,之后统称为符号)的entry,单个 entry的构成是:

        

         name:对应.strtab段的第几个字符串

        value:表示符号在具体段内的偏移

        size:表示符号的大小

        type:表示数据或者函数

        bingding:表示是全局还是静态(local)

        section:表示具体在那个段(根据书中描述:1.表示.text;3表示.data;COM表示.bss;udef表示在本文件中没被定义,即在头文件中声明的符号)

.rel.text:引用全局函数的信息

.rel.data:引用全局变量信息

这两者是通过可重定位entry来表示:

    

         offset:表示符号引用位置在具体段内的偏移

        symbol:在.strtab中的偏移

        type:重定位时的类型(R_386_PC32表示用相对寻址来重定位(即PC+offset,注意书中在可重定位的目标文件中那个offset对应的初始值是0xFFFFFC,是因为pc指向下一个指令);R_386_32表示以绝对地址来重定位)

.debug,.line:记录调试信息,debug是调试符号表,line是行号和.text段的映射

.strtab:字符串列表,以null结尾

注:

        1).a的静态链接库是一组连接起来的可重定位目标文件的集合,可以通过如下方式生成静态链接库libvector.a:

        2)静态库的解析是从左向右进行的。有三个集合:E,文件集合;U未解析符号集合;D输入文件符号定义集合。解析.c时,会将文件,未定义符号,已定义符号分别放入E,U,D中。解析.a时,会查看.a中的那些文件包含了U中符号,才会将特定的文件和其符号放入E,U,D中。

1.2.整个链接过程大概可以分成两个过程

      1.将输入文件集合E中的各个段,根据链接脚本为各个段和符号赋予地址。

注:

        1)这个链接地址一般是和物理地址相同。但是也有不同的,比如intel寄存器中提供了cs,ds等段寄存器,那么cpu实际访问的地址是:段地址+链接地址(称为逻辑地址,两者的和为线性地址);进一步如果使用了MMU,那么线性地址也不是实际的物理地址,而是虚拟地址。

例如在linux2.4.0(段地址是0)中,vmlinux.lds在刚开始就将.text段放在了0xC0000000 + 0x100000,这实际对应的就是内核虚拟地址的起始地址(0xC0100000 ),实际物理地址是0x100000。所以在刚开始没打开MMU时,要访问内存是通过(symbol(虚拟地址)-0xC0000000)访问的。

更进一步,cpu访问的物理地址其实也只是通过地址总线发的总线地址,通过总线解析之后,那一块连接的可能是内存,也有可能是flash,也有可能是IO,也有可能什么都没有,所以物理地址也不是实际存在的,也是对硬件的总线结构的一层抽象而已。

        2)链接时如果两个.c文件中具有相同的符号,函数和初始化的全局变量被设置为强符号,而未初始化的全局变量被设置为弱符号。1.两者都为强符号时会链接错误;2.两者一个强一个弱时,链接以强符号为准;3.都为弱时,任意选一个

         2.将.rel.text和.rel.data中根据其type替换符号引用的地方offset处访问符号的值。

注:1)如果是静态链接,这里生成完全的可执行文件,就可以通过os加载到内存中执行了

        2)如果是动态链接,在之前需要通过如下命令生成动态链接库libvector.so:

         然后通过编译链接生成可执行文件p2,但是这个p2和之前的可执行文件不同,是里面有一个       .interp段(存储着动态链接器的路径),os通过这个路径加载运行这个动态连接器

         动态链接器需要将动态链接库加载到进程的地址空间内,因为动态链接库是以位置无关(fPIC)的方式编译链接的,符号的访问是以相对寻址的方式,所以无论动态链接库加载到哪个地址空间,动态链接库内部都可以自由的访问,但是用户程序却需要动态链接库的接口访问地址,而这是通过全局偏量表(GOT)实现的

        

        用户程序在链接时,因为需要动态链接库,链接器给用户程序设置了一个GOT的表,表中的每一项表示着动态链接库中相应的接口(例如printf)在当前地址空间中的地址。而GOT的填充有两种方式:

  • 加载时链接,就是动态加载器在加载动态库时,动态加载器本身填写用户程序中的GOT,然后,用户程序在访问数据和代码时通过以下方式访问(编译链接时生成好的):
//通过GOT获取数据
        call L1
L1:     popl %ebx         //其实是将ebx=L1,这里以相对寻址方式编译,可以知道pc的绝对地址
        addl $VAROFF,%ebx  //获取变量位置的指针
        movl (%ebx),%eax    //获取变量位置
        movl(%eax),%eax    //获取变量
//通过GOT获取函数指针
        call L1
L1:     popl %ebx         //其实是将ebx=L1,这里以相对寻址方式编译,可以知道pc的绝对地址
        addl $PROCOFF,%ebx  //获取函数指针的位置
        call *(%ebx)        //调用函数    
  • 运行时链接,在动态连接器加载动态库时,并不对GOT更改,但是当应用程序执行到对应的动态链接函数时,先执行一个wrapper(例如PLT[1]).

         这个wrapper在第一次调用时,因为GOT[3]中存储的是下一句pushl地址,所以继续向下执行(调用GOT[2]中的动态连接器函数,传参是调用函数的标志0,和连接器的标志信息GOT[1]);在第二次调用时,因为第一次动态连接器填充了GOT[4],所以就可以直接跳到对应的动态链接库地址。

2.可安装模块

        动态链接虽然可以解决某个模块的动态安装,但是这种方式并不能对进程的权限进行管控。而驱动模块恰恰是需要具有一定的权限才能操作硬件。所以需要有一种方式能将驱动模块动态安装,并且是安装到内核中,通过内核对其权限做管控。

        在linux中,用户一般通过/sbin/insmod安装驱动模块(/sbin/rmmod卸载)。内核在运行的过程中如果缺少相关的驱动程序,也会通过request_module安装驱动。在linux2.4.0中request_module会开启一个内核线程运行exec_modprobe。

/*
	modprobe_path is set via /proc/sys.
*/
char modprobe_path[256] = "/sbin/modprobe";
static int exec_modprobe(void * module_name)
{
	static char * envp[] = { "HOME=/", "TERM=linux", "PATH=/sbin:/usr/sbin:/bin:/usr/bin", NULL };
	char *argv[] = { modprobe_path, "-s", "-k", "--", (char*)module_name, NULL };
	int ret;
//相当于执行/sbin/modprobe -s -k -- module_nameo,在环境变量envp中搜索module_name
//exec_usermodehelper主要是1.更换其root和pwd,2.消除其signal处理,3.关闭内核线程所有fd,4.更新其user是init_task,并赋予所有的进程权限cap_effective,5.整个内存空间的访问权限KERNEL_DS,6.最后通过execve执行/sbin/modprobe
	ret = exec_usermodehelper(modprobe_path, argv, envp);
	if (ret) {
		printk(KERN_ERR
		       "kmod: failed to exec %s -s -k %s, errno = %d\n",
		       modprobe_path, (char*) module_name, errno);
	}
	return ret;
}

注:根据busybox1.24.0,modprobe主要的执行函数是modprobe_main,这个函数通过读取配置文件,查看要求安装模块的依赖模块(如果依赖模块没被安装(被安装的模块在/proc/modules中),需要被安装),其他大致和insmode类似。

2.1.insmode安装驱动模块

        insmode主要通过以下步骤将驱动模块安装到内核中:

        1)通过mmap将obj映射至虚拟内存,并通过obj_load对obj_file进行解析(主要是按照header对段和符号进行解析,段装入f->sections中,符号装入hash表f->symtab中)

busybox1.24.0:    insmod_main->bb_init_module_24->obj_load
static struct obj_file *obj_load(char *image, size_t image_size, int loadprogbits){

//从内存映像image中获取头部信息,
    memcpy(&f->header, image, sizeof(f->header));

...
//映像中段的数目,并申请段的指针数组
shnum = f->header.e_shnum;
f->sections = xzalloc(sizeof(f->sections[0]) * (shnum + 4));
...
//获取段的管理信息section_header
memcpy(section_headers, image + f->header.e_shoff, sizeof(ElfW(Shdr)) * shnum);

//遍历段的指针数组,初始化
for (i = 0; i < shnum; ++i) {

    f->sections[i] = sec = arch_new_section();
    sec->header = section_headers[i];
	sec->idx = i;
    ...
    //如果段存在(sh_size不为0),查看段的类型
    switch (sec->header.sh_type) {
            case SHT_SYMTAB:            //所有引用符号表
	        case SHT_STRTAB:            //name数组
		    case SHT_RELM:              //引用符号表
                ...
                sec->contents = xmalloc(sec->header.sh_size)
                ...
                memcpy(sec->contents, image + sec->header.sh_offset, sec->header.sh_size);
            break;
		}
        
        }
    }
//初始化每个段的名称
shstrtab = f->sections[f->header.e_shstrndx]->contents;
for (i = 0; i < shnum; ++i) {
		struct obj_section *sec = f->sections[i];
		sec->name = shstrtab + sec->header.sh_name;
}
//再次遍历所有段
for (i = 0; i < shnum; ++i) {

//需要对段进行重定位的,按优先级的从大到小顺序插入到链表load_order_search_start(load_order)中
    ...
    if (sec->header.sh_flags & SHF_ALLOC)
			obj_insert_section_load_order(f, sec);
    ...
    switch (sec->header.sh_type) {
        case SHT_SYMTAB:        //符号表
            ....
            //申请local变量的符号指针数组
            j = f->local_symtab_size = sec->header.sh_info;
			f->local_symtab = xzalloc(j * sizeof(struct obj_symbol *));
            ....
            //遍历段中的所有引用的符号
            for (j = 1, ++sym; j < nsym; ++j, ++sym) {
                ...
                //将符号加入到hash中,局部变量要加入到local_symtab中
                obj_add_symbol(f, name, j, sym->st_info, sym->st_shndx,
							val, sym->st_size);
                ....
            }
}
}
busybox1.24.0:    insmod_main->bb_init_module_24->obj_load->obj_add_symbol
static struct obj_symbol *
obj_add_symbol(struct obj_file *f, const char *name,
		unsigned long symidx, int info,
		int secidx, ElfW(Addr) value,
		unsigned long size){

//查找映像中是否有和其相同的符号,这个在第一步中没有执行,之后在加内核符号的时候会用到
for (sym = f->symtab[hash]; sym; sym = sym->next) {
		if (f->symbol_cmp(sym->name, name) == 0) {

        ...
            //如果符号被外部引用,返回映像中的符号
            if (secidx == SHN_UNDEF)
				return sym;

            //如果映像引用外部符号,更改hash中的符号信息
			else if (o_secidx == SHN_UNDEF)
				goto found;

            //如果外部是全局的,内部是静态的,用全局替换静态的
            else if (n_binding == STB_GLOBAL && o_binding == STB_LOCAL) {
            ...
            nsym = arch_new_symbol();
            //将其从hash表中替换
			nsym->next = sym->next;
            ...
            for (p = &f->symtab[hash]; *p != sym; p = &(*p)->next)
					continue;
			*p = sym = nsym;  //原来的sym结构体还有local_symtab能访问
			goto found;

            ...

            //如果内部是弱类型,将其转换成外部变量
            else if (o_binding == STB_WEAK)
				goto found;
            ...
}

...
//如果在映像中没找到符号,将其加入到hash中
sym = arch_new_symbol();
sym->next = f->symtab[hash];
f->symtab[hash] = sym;
...
//如果是静态的,将其加入到静态表中
if (ELF_ST_BIND(info) == STB_LOCAL && symidx != (unsigned long)(-1)) {
...
f->local_symtab[symidx] = sym;
}

//将符号赋成外部信息
found:
	sym->name = name;
	sym->value = value;
	sym->size = size;
	sym->secidx = secidx;
	sym->info = info;

}

        2)通过query_module向内核询问内核中的模块,及模块中export的符号定义(通过ext_modules 管理所有module,通过ksyms 管理内核自身export的符号)

busybox1.24.0:    insmod_main->bb_init_module_24->add_kernel_symbols
static void add_kernel_symbols(struct obj_file *f){

    bufsize = 256;
	module_names = xmalloc(bufsize)
    //向内核请求内核中所有模块的名称
    if (query_module(NULL, QM_MODULES, module_names, bufsize, &ret)) {
    ...
    //内核模块的数目
    n_ext_modules = nmod = ret;
    ...

    ...
    //申请nmod 个module的数据结构,通过ext_modules 管理所有的模块(不包含内核本身),遍历数组module_names 
    ext_modules = modules = xzalloc(nmod * sizeof(*modules));
		for (i = 0, mn = module_names, m = modules;
				i < nmod; ++i, ++m, mn += strlen(mn) + 1) {
            
            ...
            //向对应的模块请求模块管理结构的地址
            if (query_module(mn, QM_INFO, &info, sizeof(info), &ret)) {
            ...

            //向对应的模块请求其符号信息
            bufsize = 1024;
			syms = xmalloc(bufsize);
            if (query_module(mn, QM_SYMBOLS, syms, bufsize, &ret))

            ...
            //初始化模块信息
            m->name = mn;
			m->addr = info.addr;
			m->nsyms = nsyms;
			m->syms = syms;
    ...

    //请求内核自身的符号信息,通过ksyms 管理
    bufsize = 16 * 1024;
	syms = xmalloc(bufsize);
    if (query_module(NULL, QM_SYMBOLS, syms, bufsize, &ret)) {
    ...
    nksyms = nsyms = ret;
	ksyms = syms;

}
linux2.4.0: sys_query_module
asmlinkage long
sys_query_module(const char *name_user, int which, char *buf, size_t bufsize,
		 size_t *ret){

    ...
    //如果没有传名称,查询的就是内核,否则利用find_module通过name在module_list链表中查找相应的模块
    if (name_user == NULL)
		mod = &kernel_module;
	else {
        ...
        if (namelen == 0)
			mod = &kernel_module;
        else if ((mod = find_module(name)) == NULL) 
    ...
    ...
    //如果找到了要查询的module,根据类型将查询的内容返回给用户
    switch (which)
	{
	case 0:
		err = 0;
		break;
	case QM_MODULES: //查询内核中的模块名称(module_list链表中所有模块)
		err = qm_modules(buf, bufsize, ret);
		break;
	case QM_DEPS:
		err = qm_deps(mod, buf, bufsize, ret);
		break;
	case QM_REFS:
		err = qm_refs(mod, buf, bufsize, ret);
		break;
	case QM_SYMBOLS://查询对应模块中的符号(module的管理结构中有一个数组mod->syms,里面有该模块所有的(name,value)的信息)
		err = qm_symbols(mod, buf, bufsize, ret);
		break;
	case QM_INFO:
		err = qm_info(mod, buf, bufsize, ret);
		break;
	default:
		err = -EINVAL;
		break;
	}

}

注:内核中有一个宏EXPORT_SYMBOL

linux2.4.0:
#define __EXPORT_SYMBOL(sym, str)			\
const char __kstrtab_##sym[]				\
__attribute__((section(".kstrtab"))) = str;		\
const struct module_symbol __ksymtab_##sym 		\
__attribute__((section("__ksymtab"))) =			\
{ (unsigned long)&sym, __kstrtab_##sym }

#define __MODULE_STRING_1(x)	#x
#define __MODULE_STRING(x)	__MODULE_STRING_1(x)

#define EXPORT_SYMBOL(var)  __EXPORT_SYMBOL(var, __MODULE_STRING(var))

//整合之后,有一个module_symbol结构体__ksymtab_var被链接到"__ksymtab"段,其val=&var,name指向__kstrtab_var;__kstrtab_var指向的内容被链接到".kstrtab"段,其值是#var

#define EXPORT_SYMBOL(var) \
const char __kstrtab_var[] __attribute__((section(".kstrtab")))=#var(var字符串形式);\
const struct module_symbol __ksymtab_var __attribute__((section("__ksymtab"))) ={(unsigned long)&var, __kstrtab_var};

        3)将内核和其他模块的符号信息加入到映像中,并将驱动模块中的SHF_ALLOC段分配地址。

  • 添加内核和模块符号到映像中
    busybox1.24.0:    insmod_main->bb_init_module_24->add_kernel_symbols
    static void add_kernel_symbols(struct obj_file *f){
    
        ....
        //遍历所有的外部模块,如果外部模块中的符号又被驱动模块引用,m->used = 1
        for (i = 0, m = ext_modules; i < n_ext_modules; ++i, ++m) {
    		if (m->nsyms
    		 && add_symbols_from(f, SHN_HIRESERVE + 2 + i, m->syms, m->nsyms)
    		)
            {
    			m->used = 1;
    			++nused;
    		}
    
        }
        //被驱动模块使用的模块的数量
        n_ext_modules_used = nused;
    
        //如果驱动模块引用内核符号,将其添加到映像中
        if (nksyms)
    		add_symbols_from(f, SHN_HIRESERVE + 1, ksyms, nksyms);
    }
    busybox1.24.0:    insmod_main->bb_init_module_24->add_kernel_symbols->add_symbols_from
    static int add_symbols_from(struct obj_file *f,
    		int idx,
    		struct new_module_symbol *syms,
    		size_t nsyms){
    
    
            ....
            //如果映像中存在外部的一些符号(使用或者被使用),并且不是local
            sym = obj_find_symbol(f, name);
            if (sym && !(ELF_ST_BIND(sym->info) == STB_LOCAL)) {
                
                sym = obj_add_symbol(f, name, -1,
    					ELF_ST_INFO(STB_GLOBAL,
    						STT_NOTYPE),
    					idx, s->value, 0);
                //如果是用外部符号解析映像中的一些UNDEF(引用的符号),返回1
            	    if (sym->secidx == idx)
    				    used = 1;
    		}
            ....
    }
  • 通过obj_check_undefineds分析是否还存在没被解析的符号(SHN_UNDEF),通过obj_allocate_commons为SHN_COMMON的符号分配bss段
  • 链接所有的SHF_ALLOC段
busybox1.24.0:    insmod_main->bb_init_module_24->obj_load_size
static unsigned long obj_load_size(struct obj_file *f){
    ....
    //遍历链表load_order(注意load_order在obj_load中建立,之后也会在加一些SHF_ALLOC段时,也会加到load_order链表中),为链表中每个段都赋予地址sh_addr ,整体链接的长度是dot 
    for (sec = f->load_order; sec; sec = sec->load_next) {
    ...
    sec->header.sh_addr = dot;
	dot += sec->header.sh_size;
    }
    ....

}

        4)通过create_module让内核分配空间

linux2.4.0: sys_create_module
asmlinkage unsigned long
sys_create_module(const char *name_user, size_t size){
    ....
    //内核中要没有这个模块
    if (find_module(name) != NULL) {
		error = -EEXIST;
        	goto err1;
	}
    //申请module的内存空间
	if ((mod = (struct module *)module_map(size)) == NULL) {
		error = -ENOMEM;
		goto err1;
	}

    //初始化,并链入到链表module_list 
    memset(mod, 0, sizeof(*mod));
	mod->size_of_struct = sizeof(*mod);
	mod->next = module_list;
	mod->name = (char *)(mod + 1);
	mod->size = size;
	memcpy((char*)(mod+1), name, namelen+1);
    ...
    module_list = mod;
    ...
}

        5)将模块中对变量的引用重定位

busybox1.24.0:    insmod_main->bb_init_module_24->obj_relocate
static int obj_relocate(struct obj_file *f, ElfW(Addr) base){
    ...
    //base是向内核申请内存空间的基地址,这里对每个段进行重定位
    f->baseaddr = base;
	for (i = 0; i < n; ++i)
		f->sections[i]->header.sh_addr += base;

    ...
    for (i = 0; i < n; ++i) {
        ...
        //对段中引用变量的位置重定位
        relsec = f->sections[i];
		if (relsec->header.sh_type != SHT_RELM)
			continue;

        ...

        for (; rel < relend; ++rel) {
            ...
            //找到符号及其对应的值
            symndx = ELF_R_SYM(rel->r_info);
            ...
            value = obj_symbol_final_value(f, intsym);
            

            ...
            //对符号重定位
            /*arch_apply_relocation中主要内容
                ...
                //符号引用的地址
                ElfW(Addr) dot = targsec->header.sh_addr + rel->r_offset;
                ...
                //直接寻址
                case R_386_32:
			        *loc += v;
			         break;
                //相对寻址
                case R_386_PLT32:
		        case R_386_PC32:
		        case R_386_GOTOFF:
			        *loc += v - dot;
			        break;
            */
            switch (arch_apply_relocation
					(f, targsec, /*symsec,*/ intsym, rel, value)
			)
        ...
        }
      }
}

        6)准备好内核数据,并调用init_module

busybox1.24.0:    insmod_main->bb_init_module_24->new_init_module
static int
new_init_module(const char *m_name, struct obj_file *f, unsigned long m_size){

    ...
    //将初始化的信息放在".this"段的开始,根据new_create_this_module可知".this"段在load_order链表中的首个段,后面看到contents会被复制到内存空间,所以其前sizeof(struct new_module)是存储module的初始化信息的
    sec = obj_find_section(f, ".this");
    ...
    module = (struct new_module *) sec->contents;
    //之后根据各个段的内容("__ksymtab"、".kmodtab","init_module","cleanup_module"等)对module中的各个字段进行初始化
    ...

    ...
    //申请用户内存,复制content,然后经过init_module传递到内核
    image = xmalloc(m_size);
    /*

        for (sec = f->load_order; sec; sec = sec->load_next) {

            ...
            secimg = image + (sec->header.sh_addr - base);//复制到申请内存的相对地址上
            memcpy(secimg, sec->contents, sec->header.sh_size);
            ...
        }

    */
	obj_create_image(f, image);

    //要求操作系统复制module信息和load_order段链表中的content内容到内核,并由内核进行模块的初始化操作
    ret = init_module(m_name, (struct new_module *) image);
    ...

}
linux2.4.0: sys_init_module
asmlinkage long
sys_init_module(const char *name_user, struct module *mod_user){

    ...
    //从module_list中找到sys_create_module申请的module结构体
    if ((mod = find_module(name)) == NULL) 
    ...

    //前面有大量的module结构的检查,这里只列举对name的检查
    //用户空间中的name是在new_create_this_module中通过obj_string_patch打了patch(reloc_offset是offsetof(struct new_module, name)),在obj_relocate的最后将string的patch根据reloc_offset添加到段的content中。
    if ((n_namelen = get_mod_name(mod->name - (unsigned long)mod
				      + (unsigned long)mod_user,
				      &n_name)) < 0)
    ...

    //为驱动模块的依赖模块建立结构管理
    //将依赖的模块在busybox中通过new_create_module_ksymtab建立,并打patch,在obj_relocate中复制到段的content中,最后在new_init_module和module中的相关字段关联
    for (i = 0, dep = mod->deps; i < mod->ndeps; ++i, ++dep)
    ...

    //调用mod->init,完成内核访问驱动模块的路径。
    if (mod->init && (error = mod->init()) != 0)
    ...

}

2.2.mod->init向文件系统注册

linux2.4.0:硬盘驱动
static int hwif_init (ide_hwif_t *hwif){
    ...
    //向devfs注册硬盘设备
    if (devfs_register_blkdev (hwif->major, hwif->name, ide_fops))
    ...

}

        IO设备在内核中被抽象成“一组能与CPU交换数据的接口”(这里是ide_fops),将这个接口向文件系统注册(可以是物理存在的文件系统,比如ext2;也可以是虚拟文件系统procfs,devfs),然后在文件系统中形成相应的设备文件。通过文件的常规操作open,write,read,ioctl等对设备文件进行操作,最后通过注册的路径而去访问ide_fops。

        创建设备节点的具体过程涉及到文件系统的相关操作,具体请参考《Linux内核源代码情景分析》或者linux2.4.0流程图-Linux文档类资源-CSDN下载中相关细节。

3.总结 

        1.为什么有些游戏软件2~3G的大小,只需要更新几兆的包,就可以有一些差异性的功能?

        答:这些更新的都是.so(linux)/.dll(windows)这样的动态链接库,他们在操作系统运行游戏软件时会加载到内存空间,并且会由动态链接加载器进行动态链接。

        2.为什么在安装驱动前不能操作IO,但是在安装驱动后,我们的应用软件就可以通过驱动程序访问IO,那么应用软件是如何找到驱动操作IO的函数的呢?

        答:在安装设备的时候,操作系统会创建设备文件,通过这个设备文件可以找到对设备操作的接口(ide_fops),从而访问设备。

        3.对于linux而言,驱动属于内核的一部分,那么是不是意味这能随便改变内核的代码结构去访问硬件,linux是如何做到安全性和灵活性的平衡呢?

        答:驱动只能访问内核或者其他模块export出来的符号,而不能随意访问内核的任意符号(否则在insmode时会因为找不到符号而报错)。

参考书籍和视频:

《深入理解计算机系统》

《Linux内核源代码情景分析》

[完结] 2020 南京大学 “操作系统:设计与实现” (蒋炎岩)_哔哩哔哩_bilibili

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值