您的位置:首页 > 其它

内核模块

2016-02-16 17:15 225 查看


以下内容只是记录的要点,详细看参考资料:



一、内核模块的编写:


1、内核模块的代码编写没有外部的函数库可以用,只能使用内核导出的函数;这点于应用程序是有区别的,应用程序习惯于使用外部的库函数,在编译的时候将程序与库函数链接在一起。比如说:内核模块中不能使用printf(),而只能使用printk()函数。



2、内核模块至少包含两个函数:模块加载函数、卸载函数;


内核版本2.3.13以前,使用init_module()和cleanup_module作为模块的初始和结束函数; 在内核Linux
2.4中,你可以为你的模块的“开始”和“结束”函数起任意的名字了。它们不再必须使用 init_module()和cleanup_module()的名字。这可以通过宏 module_init()和module_exit()实现。这些宏在头文件kernel../include/linux/init.h定义。唯一需要注意的地方是函数必须在宏的使用前定义,否则会有编译
错误。


3、内核模块包含头文件及宏说明:


#include<linux/module.h>:它定义了模块的 API、类型和宏(MODULE_LICENSE、MODULE_AUTHOR等等),所有的内核模块都必须包含这个头文件。


#include<linux/kernel.h>:使用内核信息优先级时要包含这个文件,一般在使用printk函数时使用到优先级信息。


#include<linux/init.h>头文件:module_init、module_exit等宏定义。


#include<linux/proc_fs.h>: 在对/proc文件系统的文件目录进行操作时使用到的函数等在这个文件中定义,比如:create_proc_entry()在/proc下创建文件。



接下来解释一下常用宏定义:


MODULE_LICENSE
定义了这个模块使用的许可证。此处,我们定义的是 GPL,从而防止会污染到内核。




二、内核模块的编译(2.6系列内核):



编译模块的时候,你可以将模块放在代码树中,用make modules来编译你的模块;你也可以将模块相关文件目录放在代码树以外的位置,用如下命令来编译模块:


make -C path/to/kernel/source M=$PWD modules


参数说明: -C 指定代码树的位置;M=$PWD或者M=`PWD`告诉kbuild回到当前目录执行build操作。


参考资料: 2.6内核Makefile简单语法与应用

http://blog.chinaunix.net/u/24474/showart_237820.html


在我的机子上运行的内核版本是2.6.22-14-generic,编译内核模块mymodule的步骤如下


内核模块的编译:


1)写内核模块源文件(任意目录), 如mymodule.c


2)在模块源文件目录编写Makefile,内容:obj-m+=mymodule.o


3) 在终端执行: make -C /usr/src/linux版本目录 SUBDIRS=$PWD modules


网上搜到的命令是:


make -C /usr/src/linux-`uname
-r` SUBDIRS=$PWD modules


其中:linux-`uname
-r`应该根据自己的实际情况进行调整,比如我的就是linux-headers-2.6.22-14-generic,也就是linux-headers-'uname -r'。


说明:SUBDIRS=..和
M=..是一样的,



疑点:对kbuild的理解?


2.6内核引入了kbuild,将外部的内核模块的编译和内核源码树的编译统一起来了。



三、插入模块:


$insmod ./mymodule.ko


查看信息:demsg | tail -5 , 并且 cat /proc/modules 可看到插入的模块:mymodule。


卸载模块:


$rmmod ./mymodule.ko



参考资料:2.6内核模块的编写框架和编译方法

http://hi.baidu.com/skie/blog/item/87ab59a9b45b46fc1f17a208.html

四、 模块的安装


安装模块。模块的默认安装目录是 /lib/modules/<内核-版本>


当你需要将模块安装到非默认位置的时候,你可以用INSTALL_MOD_PATH 指定一个前缀,如:


make INSTALL_MOD_PATH=/foo modules_install


模块将被安装到 /foo/lib/modules目录下。



四、内核模块的运行:


内核模块运行在内核空间,而应用程序在用户空间。应用程序的运行会形成新的进程,而内核模块一般不会。每当应用程序执行系统调用时,linux执行模式从用户空间切换到内核空间。




五、几个可以用来开发有用LKM(可加载内核模块)的内核API:

要使用/proc文件系统,首先一定要包含procfs的头文件:include<proc_fs.h>(这些函数是在此文件中声明的),其次利用procfs提供的如下API函数。


首先值得一提的是:结构体proc_dir_entry,他的部分成员如下:


struct proc_dir_entry {



const char *name; // virtual file name

mode_t mode; // mode permissions

uid_t uid; // File's user id

gid_t gid; // File's group id

struct inode_operations *proc_iops; // Inode operations functions

struct file_operations *proc_fops; // File operations functions

struct proc_dir_entry *parent; // Parent directory

...

read_proc_t *read_proc; // /proc read function

write_proc_t *write_proc; // /proc write function

void *data; // Pointer to private data

atomic_t count; // use count

...

};

为了创建可读可写的proc文件并指定该proc文件的写操作函数,必须设置下面那些创建proc文件的函数返回指针指向的struct proc_dir_entry结构的write_proc字段,并指定该proc文件的访问权限有写权限。

我们会使用
read_proc
write_proc
命令来插入对这个虚拟文件进行读写的函数。

读者可以通过cat和echo等文件操作函数来查看和设置这些proc文件。


create_proc_entry:

1)原型:

struct proc_dir_entry *create_proc_entry( const char *name, mode_t mode,struct proc_dir_entry *parent );

2)作用:该函数用于在/proc文件系统中创建一个虚拟文件(proc条目)。

3)参数:

name: 给出要创建的文件名称;

mode:给出建立的该proc文件的访问权限;

parent:指定建立的proc文件所在的目录;

如果要在/proc根目录下建立proc文件,parent应当为NULL,否则他应当为proc_mkdir,也就是说我们也可以在自己创建的目录下面再创建虚拟文件.(详细解释见remove_proc_entry).

4)返回struct proc_dir_entry结构的指针,我们可以使用这个返回的指针来配置这个虚拟文件的其他参数,比如对该文件执行读操作时应调用的函数。

若返回NULL,说明create出错。

remove_proc_entry:

1)原型:

void remove_proc_entry( const char *name, struct proc_dir_entry *parent );

2)作用:用于删除上面函数创建的proc文件。

3)参数:

name:给出要删除的proc文件的名称;

parent:指定创建的proc文件所在的目录。

parent参数可以是NULL(指/proc根目录),也可以取其他值,这取决于我们创建时将这个文件放到了什么地方。可以使用的其他一些值 proc_dir_entry:proc_root_fs、proc_net、proc_bus、proc_root_driver等。这些取值在linux/proc_fs.h中引入,如下:

extern struct proc_dir_entry proc_root; (/proc目录)

extern struct proc_dir_entry *proc_root_fs; (/proc/fs目录)

extern struct proc_dir_entry *proc_net; (/proc/net)

extern struct proc_dir_entry *proc_bus; (/proc/bus)

extern struct proc_dir_entry *proc_root_driver; (/proc/driver)

extern struct proc_dir_entry *proc_root_kcore; (/proc/kcore)

注意:上面的定义中,proc_root和其他变量的类型不同,其他的都是指针,在使用时要注意。利用这些位置值,我们可在/proc文件系统的根目录(/proc)及其内部的目录下(如/proc/net、/proc/bus等)建立文件。除此之外,我们还可以用自己创建的目录(proc_mkdir)。

删除目录和文件都是使用这一个函数。

proc_mkdir:

1)原型:

struct proc_dir_entry *proc_mkdir(const char * name, struct proc_dir _entry *parent)

2)作用:该函数用于创建一个proc目录。

3)参数:

name:指定要创建的目录的名称;

parent:该proc目录所在的目录。

proc_mkdir_mode:

1)原型:

extern struct proc_dir_entry *proc_mkdir_mode(const char *name,

mode_t mode, struct proc_dir_entry *parent)

2)作用:该函数用于以一定的模式创建proc目录。

3)参数:

name:指定要创建的目录的名称;

mode:给出了建立该proc目录的访问权限;

parent:该proc目录所在的目录。

proc_symlink:

1)原型:

truct proc_dir_entry *proc_symlink(const char * name,struct proc_ dir_entry * parent, const char * dest)

2)作用:该函数用于建立一个proc文件的符号连接。

3)参数:

name:给出要建立链接的proc文件的名称;

parent:指定符号链接所在的目录;

dest:指定链接到的proc文件名称。

creat_proc_read_entry:

1)原型:

struct proc_dir_entry *create_proc_read_entry(const char *name,mode_t mode, struct proc_dir_entry *base,read_proc_t *read_proc, void * data)

2)作用:该函数用于建立一个规则的只读proc文件

3)参数:

name: 给出要创建的文件名称;

mode:给出了建立该proc文件的访问权限;

base:指定建立proc文件所在的目录(指定方式同上);

read_proc:给出读取该文件的操作函数;

data:为该proc文件的专用数据,它将保存在该proc文件对应的struct file结构中的

private_data字段中。

creat_proc_info_entry:

1)原型:

struct proc_dir_entry *create_proc_info_entry(const char *name,mode_t mode, struct proc_dir_entry *base, get_info_t *get_info)

2)作用:该函数用于创建一个info型的proc文件。

3)参数:

name: 给出要创建的proc文件名称;

mode:给出了建立该proc文件的访问权限;

base:指定建立proc文件所在的目录(指定方式同上);

get_info:指定该proc文件的get_info操作函数。实际上get_info等同于read_proc,如果proc文件没有定义read_proc,对该文件的read操作将由

get_info取代,因此它在功能上非常类似于函数creat_proc_read_entry.

proc_net_creat:

1)原型:

struct proc_dir_entry *proc_net_create(const char *name, mode_t mode, get_info_t *get_info)

2)作用:该函数用于在/proc/net目录下创建一个proc文件。

3)参数:

name:给出了要创建的proc文件名称;

mode:给出了建立该proc文件的访问权限;

get_info:指定该proc文件的get_info操作函数。

proc_net_fops_create:

1)原型:

struct proc_dir_entry *proc_net_fops_create(const char *name, mode_t mode, struct file_operations *fops)

2)作用:该函数也用于在/proc/net目录下创建一个proc文件,但是他同时指定了对该文件的文件操作函数。
proc_net_remove:

1)原型:void proc_net_remove(const char *name)

2)作用:该函数用于删除前面两个函数在/proc/net目录下创建的文件。

3)参数:

name:指定要删除的proc文件;

回调函数:

我们可以使用write_proc函数向/proc中写入一项。此函数原型:

int mod_write( struct file *filp, const char __user *buff, unsigned long len, void *data
);

参数:
filp
参数实际上是一个打开文件结构(我们可以忽略这个参数);

buff
参数是存放在缓冲区的要写入的字符串数据;

len
参数定义了在
buff
中有多少数据要被写入;

data
参数是一个指向私有数据的指针(proc_dir_entry中)。

注意:缓冲区buff地址实际上是一个用户空间的缓冲区,因此我们不能直接读取它。Linux 提供了一组 API 来在用户空间和内核空间之间移动数据,我们使用了
copy_from_user
函数来维护用户空间的数据。

读回调函数:

我们可以使用read_proc函数从一个/proc项中读取数据(从内核空间到用户空间)。此函数原型:

int mod_read( char *page, char **start, off_t off, int count, int *eof, void *data );

参数:
page
参数是这些数据写入到的位置;

count
定义了可以写入的最大字符数;

在返回多页数据(通常一页是 4KB)时,我们需要使用
start
off
参数;

当所有数据全部写入之后,就需要设置
eof
(文件结束参数);

write
类似,
data
表示的也是私有数据。

注意:此处提供的page缓冲区在内核空间中,因此我们可以直接读出,而不用调用copy_to_user。

其他有用的/proc函数:


对于只需要一个read函数的简单/proc项来说,可以使用create_proc_read_entry,这会创建一个/proc项,并在一个调用中对read_proc函数进行初始化。


其他有用的/proc函数:


copy_to_user/* Copy buffer to user-space from kernel-space */


copy_from_user/* Copy buffer to kernel-space from user-space */


vmalloc/* Allocate a 'virtually' contiguous block of memory */


vfree/* Free a vmalloc'd block of memory */


EXPORT_SYMBOL( symbol )/* Export a symbol to the kernel (make it visible to the kernel)*/


EXPORT_SYMTAB* Export all symbols in a file to the kernel (declare before module.h) */


5、参考文献:The Linux Kernel Module Programming Guide




* 每一个内核模块都需要包含linux/module.h;当需要使用printk()记录级别的宏扩展KERN_ALERT等时,需要包含linux/kernel.h。



*printk()函数中,当指定的优先级低于consle_loglevel时,信息会直接打印到终端上(不是在Xwindows下的虚拟终端下,而是真正的字符界面下);如果同时syslogd和klogd都在运行,信息也同时添加在文件/var/log/messages中,不管是否显示在控制台上。


说明:printk()函数不是设计用来同用户交互的,而是为内核提供日志功能,记录内核信息或给出警告。


注意,内核的输出进到了内核回环缓冲区中,而不是打印到
stdout
上,这是因为
stdout
是进程特有的环境。要查看内核回环缓冲区中的消息,可以使用
dmesg
工具(或者通过
/proc 本身使用
cat /proc/kmsg
命令)。下面指令 给出了
dmesg
显示的最后几条消息:dmesg | tail -5.



* 一种称为kbuild的新方法被引入,现在外部的可加载内核模块的编译的方法已经同内核编译统一起来。 想了解更多的编
译非内核代码树中的模块,请参考帮助文件linux/Documentation/kbuild/modules.txt。



* 2.6的内核现在引入一种新的内核模块命名规范:内核模块现在使用.ko的文件后缀(代替
以往的.o后缀),这样内核模块就可以同普通的目标文件区别开。更详细的文档请参考 linux/Documentation/kbuild/makefiles.txt。


* 内核模块证书 :


在2.4或更新的内核中,一种识别代码是否在GPL许可下发布的机制被引入,
因此人们可以在使用非公开的源代码产品时得到警告。 利用宏 MODULE_LICENSE(),即当你设置在GPL证书下发布你的代码时,
你可以取消这些警告。这种证书机制在头文件linux/module.h 实现 。


类似的,宏MODULE_DESCRIPTION()用来描述模块的用途。


宏MODULE_AUTHOR()用来声明模块的作者。


宏MODULE_SUPPORTED_DEVICE() 声明模块支持的设备。


这些宏都在头文件linux/module.h定义,
并且内核本身并不使用这些宏。它们只是用来提供识别信息,可用工具程序像objdump查看。


* 版本印戳作为一个静态的字符串存在于内核模块中,以 vermagic:。
版本信息是在连接阶段从文件init/vermagic.o中获得的。 查看版本印戳和其它在模块中的一些字符信息,可以使用下面的命令 modinfo
module.ko(模块名).


*


模块是在insmod加 载时才连接的目标文件。那些要用到的函数(如:printk)的符号链接是内核自己提供的。 也就是说, 你可以在内核模块中使用的函数只能来自内核本身。如果你对内核提供了哪些函数符号
链接感兴趣,看一看文件/proc/kallsyms。 文件/proc/kallsyms保存着内核知道的所有的符号,你可以访问它们,
因为它们是内核代码空间的一部分。




*


库函数和系统调用的区别: 库函数是高层的,完全运行在用户空间, 为程序员提供调用真正的在幕后 完成实际事务的系统调用的更方便的接口。系统调用在内核 态运行并且由内核自己提供。


一般库函数在用户态执行。 库函数调用一个或几个系统调用,而这些系统调用为库函数完成工作,但是在超级状态。 一旦系统调用完成工作后系统调用就返回同时程序也返回用户态。

* 宏 __init 和 __exit 可以使函数在运行完成后自动回收内存(限模块中),__initdata用于变量,

举例:

#include //需要包含的头文件

static int ntest __initdata = 3;

static int __init test_init(void) {...}

static void __exit test_exit(void) {...}

module_init(test_init); //申明放在实现函数后

module_exit(test_exit);
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: