linux内核笔记(一)

需要了解知识:

Linux

C语言

Makefile

概述

Linux是宏内核(或单内核)的操作体系统。(Windows微内核)

宏内核是所有的内核功能被整体编译在一起,形成一个单独的内核镜像,内核中各模块的交互是通过直接的函数调用,效率非常高。

微内核是实现内核中相当关键和核心的一部分,其他功能模块被单独编译,功能模块之间的交互通过微内核提供的某种通信机制来建立的。

宏内核如果要增加、删除、修改内核的某个功能,就不得不重新编译整个内核,为了解决这一问题,引入了内核模块。内核模块是被单独编译的一段内核代码,它可以在需要的时候动态加载到内核,不需要时,动态卸载(需要内核配置了可卸载的选项),不需要重启整个系统,这种特性非常适合驱动程序开发。

第一个内核模块

#include
#include
#include

int init_module(void){
    printk("module init\n");
    return 0;
}

void cleanup_module(void){
    printk("cleanup module\n");
}

 内核源码树中的头文件。包含了init_module和cleanup_module两个函数原型声明

包含了printk函数的原型声明

暂时没用

一个模块程序几乎都要直接或间接包含上述三个模块。

init_module函数时模块的初始化函数(非必要),不接受参数,成功返回0;失败返回一个负数。

printk函数类似于printf,但支持额外的打印级别,用于调试目的。

cleanup_module函数是模块的清除函数(非必要),在模块从内核中卸载时调用。但要是提供了模块初始化函数,则清除函数是要提供的(除非内核模块不允许卸载)。

Makefile

简单的做法是把刚才的代码添加到内核源码树中,然后修改对应的Makefile文件即可,但这回修改内核的源码;另一种是将c文件放到内核源码树外一个单独的目录中,然后在该目录下编写一个对应的Makefile。

ifeq($(KERNELRELEASE),)

ifeq($(ARCH),arm)
KERNELDIR?=/home/farsight/fs4412/linux-3.14.25-fs4412
ROOTFS?=/nfs/rootfs
else
KERNELDIR?=/lib/moudle/$(shell uname -r)/build
endif
PWD:=$(shell pwd)
module:
    $(MAKE) -C $(KERNELDIR)M=$(PWD)modules
modules_install:
    $(MAKE) -C $(KERNELDIR)M=$(PWD)INSTALL_MOD_PATH=$(ROOTFS)modules_install
clean:
    rm -rf *.0 *.ko *.cmd *.mod modules.order Module.symvers.tmp_versions
else

obj-m:=vser.o

endif

略 

内核模块的相关工具

模块加载

insmod:加载指定目录下的一个.ko文件到内核

模块加载成功后使用dmesg命令可以看到控制台输出信息

modprobe:自动加载模块岛内和,相比insmod更智能,推荐使用。但莫要要执行安装操作,在运行该命令前最好运行一次depmod命令来更新模块的依赖信息。modeprobe不指定路径及后缀

模块信息

modinfo:查看模块信息,在安装了模块并运行depmod命令后,可以不指定路径和后缀,也可以指定查看某一特定的.ko文件的信息

模块卸载

rmod:如果内核配置允许卸载模块,那么rmmod将指定的模块从内核中卸载。

内核模块一般的形式

GPL许可协议略。在代码中需要添加代码表示改代码接收响应的许可协议。

MODULE_LICENSE("GPL");

MOUDLE_LICENSE是一个宏,里面的参数是一个字符串,代表相应的许可证协议。可以是GPL,GPL v2,GPL and additional rights等。这个宏将会生成一些模块信息,放在ELF文件中的一个特殊的段中,模块在加载时会将该信息复制到内存中,并检查该信息。不加这段代码。会导致内核报警告或关闭某些调试功能,某些功能函数是不能调用的,开发驱动调用内核的一些基础设施(一些内核的API函数)是必不可少的。

模块的初始化函数和清楚函数的名字是固定的,入口函数基本都为main。内核借助于GUN的函数别名机制,可以灵活的指定模块的初始化函数和清楚函数的别名。

module_init(vser_init);
module_exit(vser_exit);

 module_init和module_exit是两个宏,分别用于指定init_module和函数别名vser_init和cleanup_exit的别名是vser_exit。

为了避免因为重名带来重读定义的问题,函数可以加static关键字修饰,经过static修饰后的函数的链接属性为内部。这也是几乎所有驱动程序的函数前都要加static关键字修饰的原因。

Linux是节约内存的操作系统。上面的初始化函数仅会被调用1次,之后就可以释放掉这函数占用的内存,在函数名前加__init可以达到此目的。__init是把这些标记的函数放到ELF文件的特定代码段,在模块加载这些段时将会单独分配内存,这些函数调用成功后,模块的加载程序会释放这部分内存。__exit用于修饰清除函数,类似于__init,如果模块不允许且在,那么这段代码完全就不用加载。

#include
#include
#include

static int __init vser_init(void){
    printk("init\n");
    return 0;
}
static void __exit vser_exit(void){
    printk("exit\n");
}

module_init(vser_init);
module_exit(vser_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("XXX");
MODULE_DESCRIPTION("XXXXXX");
MODULE_ALIAS("virtual-serial");

将多个源文件编译生成一个内核模块

对于一个较复杂的驱动程序,将所有代码写在一个源文件不太现实,通常会把程序功能拆分,由不同的源文件来实现对应的功能。

使用make,将多个目标文件共同生成的。

内核模块参数

模块的初始化函数在模块被加载时调用,但该函数不接受参数。模块提供了另外一种形式来支持参数传递信息,叫做模块参数。

模块参数允许用户在加载模块时通过命令行指定参数,并转换成相应类型的值,然后赋值给对应的辩论,这个过程发生在调用模块初始化函数之前。内核支持的参数类型有:bool,inbool(反转值bool)、charp(字符串指针)、short、int、long、ushort、uint、ulong这些类型又可以复合成对应的数组类型。

module_param(name,type,perm)

module_param_array(name,type,nump,perm)

name:变量的名字

type:变量或数组元素的类型

nump:数组元素个数的指针(可选)

perm:在sysfs文件系统中对应文件的权限属性,如果为0,则sysfs文件系统中不会出现任何对应文件。

虽然代码中增加模块参数的写权限可以通过sysfs文件系统来修改模块参数的值,但不推荐。因为这种方式对模块参数惊醒的修改模块本身是一无所知。

内核模块依赖

使用file命令和nm命令可以得到ELF目标文件的相关细节信息

  1. 如果要使用insmod命令加载模块,则必须线加载dep模块再加载vser模块。
  2. 两个模块存在依赖关系,如果分别编译,会出现警告信息,即使加载顺序正确,加载也不会成功
  3. 卸载模块要先卸载vser模块在写在dep模块。内核会创建模块依赖关系的链表,只有当依赖于这个模块的链表为空时,模块才能被卸载。

内核模块信息

Linux内核是由全世界的志愿者开发的,不同版本函数接口可能完全不一样。

内核模块和普通应用程序的差异:

  • 内核模块是操作系统内核的一部分,运行在内核空间;应用程序运行在用户空间
  • 内核模块中的函数是被动调用的,应用程序是顺序执行的
  • 内核模块处于C函数库之下,不能调用C库函数;应用程序可以随意调用C库函数
  • 内核模块如果产生非法访问(野指针等)可能系统崩溃;应用程序最多影响自己
  • 内核模块的并发更多(中断、多处丽丽);应用程序一般考虑多进程或多线程
  • 整个内核空间的调用链上只有4KB或8KB的栈,相对于应用程序来说很小。需要大的内存空间,通常应该动态分配
  • printk和printf相似,但printk不支持浮点数

 

 

你可能感兴趣的:(linux,笔记,c)