Chinaunix首页 | 论坛 | 博客
  • 博客访问: 317345
  • 博文数量: 239
  • 博客积分: 481
  • 博客等级: 下士
  • 技术积分: 1170
  • 用 户 组: 普通用户
  • 注册时间: 2008-07-15 17:28
文章分类

全部博文(239)

文章存档

2014年(13)

2013年(6)

2012年(122)

2011年(98)

分类:

2011-07-29 20:38:10

一、linux内核模块
    Linux 内核具有模块化设计,提供了可伸缩的、动态的内核。在引导时,只有少量的驻留内核被载入内存。这之后,无论何时用户要求使用驻留内核中没有的功能,某内核模块(kernel module),有时又称驱动程序(driver)。就会被动态地载入内存。 通过 Linux 内核模块(LKM)可以在运行时动态地更改 Linux。可动态更改 是指可以将新的功能加载到内核、从内核去除某个功能,甚至添加使用其他 LKM 的新 LKM。LKM 的优点是可以最小化内核的内存占用,只加载需要的元素(这是嵌入式系统的重要特性)。
    Linux 的众多优良特性之一就是可以在运行时扩展由内核提供的特性的能力. 这意味着你可以在系统正在运行着的时候增加内核的功能( 也可以去除 ).每块可以在运行时添加到内核的代码, 被称为一个模块. Linux 内核提供了对许多模块类型的支持, 包括但不限于, 设备驱动. 每个模块由目标代码组成( 没有连接成一个完整可执行文件 ), 可以动态连接到运行中的内核中, 通过 insmod 程序, 以及通过 rmmod 程序去连接.“图内核的划分”表示了负责特定任务的不同类别的模块, 一个模块是根据它提供的功能来说它属于一个特别类别的. “图内核的划分”中模块的安排涵盖了最重要的类别, 但是远未完整, 因为在 Linux 中越来越多的功能被模块化了.


                        图 内核的划分
二、在Debian下编译内核需要安装内核头文件: 
#aptitude install linux-headers-`uname -r` 
(把目前所使用的内核的头文件安装上,也同样会安装上build-essential,linux-kbuild等包,如果之前没安装过的话。然后在/usr/src下会出现一个linux-header-xxxxx的目录,这个目录里就是当前使用的内核的头文件以及build模块的时候所需要用到的scripts和Makefile,这些scripts是linux-kbuild中提供的。)

三、简单的hello world 内核模块
(先写一个简单的hello world 模块,熟悉linux内核模块编程,为procfs做准备)
unanao@debian:~/programming/modules/hello$ cat hello.c 
#include  
#include  

static int hello_init(void)//void can't be abbreviation,or we will get a warning 

    printk(KERN_EMERG "hello, world\n"); 
    return 0 


static void hello_exit(void) 

    printk(KERN_ALERT "Goodbye!\n"); 


module_init(hello_init);//There is no "",This is not a string 
module_exit(hello_exit); 

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("unanao"); 
MODULE_DESCRIPTION("simple module ,just output some words\n"); 
MODULE_ALIAS("hello"); 

   这个模块定义了两个函数, 一个在模块加载到内核时被调用( hello_init )以及一个在模块被去除时被调用( hello_exit ). moudle_init 和 module_exit 这几行使用了特别的内核宏来指出这两个函数的角色. 另一个特别的宏 (MODULE_LICENSE) 是用来告知内核, 该模块带有一个自由的许可证; 没有这样的说明, 在模块加载时内核会抱怨.

四、编写Makefile
unanao@debian:~/programming/modules/hello$ cat Makefile 
ifneq ($(KERNELRELEASE),) # after ifneq ,there is a space! or,will get an error
    obj-m := hello.o 
else 
    KERNELDIR := /lib/modules/$(shell uname -r)/build 
    PWD := $(shell pwd) 
default: 
     $(MAKE) -C $(KERNELDIR) M=$(PWD) modules 
clean: 
    rm -rf *.o *.ko *mod.c [mM]odule* .*.cmd *.o.d .tmp_versions 
endif 

详细的信息参考linux内核源代码中“Documentation/kbuild/modules.txt”文件

五、内核模块的编译,插入,查看,卸载
debian:/home/unanao/hello$make
debian:/home/unanao/hello# insmod hello.ko 
debian:/home/unanao/hello# lsmod
Module                  Size  Used by
hello                   1344  0 
procfs                  2096  0 
ipv6                  235396  10 
ppdev                   6468  0 
lp                      8164  0 
vboxvfs                29536  0 
nls_base                6820  1 vboxvfs
loop                   12748  0 
parport_pc             22500  0 
parport                30988  3 ppdev,lp,parport_pc
ac                      4196  0 
serio_raw               4740  0 
battery                10180  0 
psmouse                32336  0 
button                  6096  0 
i2c_piix4               7216  0 
pcspkr                  2432  0 
i2c_core               19828  1 i2c_piix4
vboxadd                47008  4 vboxvfs
evdev                   8000  3 
ext3                  105576  1 
jbd                    39476  1 ext3
mbcache                 7108  1 ext3
ide_cd_mod             27684  0 
cdrom                  30176  1 ide_cd_mod
ide_disk               10496  3 
floppy                 47716  0 
piix                    6568  0 [permanent]
ide_pci_generic         3908  0 [permanent]
ide_core               96168  4 ide_cd_mod,ide_disk,piix,ide_pci_generic
ata_generic             4676  0 
libata                140448  1 ata_generic
pcnet32                27396  0 
mii                     4896  1 pcnet32
scsi_mod              129548  1 libata
dock                    8304  1 libata
thermal                15228  0 
processor              32576  1 thermal
fan                     4196  0 
thermal_sys            10856  3 thermal,processor,fan
debian:/home/unanao/hello# rmmod hello

六、查看内核的输出
6.1、使用dmesg查看:
debian:/home/unanao/hello# dmesg |tail -2
[ 9344.308292] hello, world
[ 9389.298477] Goodbye, I will Come to a more beautiful word

6.2、直接通过pritk输出到终端:
6.2.1在xwindow环境下开的终端窗口,
    printk(KERN_EMERG "hello, world\n"); //输出
    printk(KERN_ALERT "Goodbye,I will Come to a more beautiful word\n");//不输出 
6.2.2在黑屏的Linux控制台模式(Ctrl+Alt+(F1~F6))两个都可以显示输出。

6.3、printk()函数的总结 
我们在使用printk()函数中使用日志级别为的是使编程人员在编程过程中自定义地进行信息的输出,更加容易地掌握系统当前的状况,对程序的调试起到了很重要的作用。 
(下文中的日志级别和控制台日志控制级别是一个意思) 

printk(日志级别 "消息文本");这里的日志级别通俗的说指的是对文本信息的一种输出范围上的指定。 
日志级别一共有8个级别,printk的日志级别定义如下(在linux26/includelinux/kernel.h中): 
#defineKERN_EMERG"<0>"/*紧急事件消息,系统崩溃之前提示,表示系统不可用*/ 
#defineKERN_ALERT"<1>"/*报告消息,表示必须立即采取措施*/ 
#defineKERN_CRIT"<2>"/*临界条件,通常涉及严重的硬件或软件操作失败*/ 
#defineKERN_ERR"<3>"/*错误条件,驱动程序常用KERN_ERR来报告硬件的错误*/ 
#defineKERN_WARNING"<4>"/*警告条件,对可能出现问题的情况进行警告*/ 
#defineKERN_NOTICE"<5>"/*正常但又重要的条件,用于提醒。常用于与安全相关的消息*/ 
#defineKERN_INFO"<6>"/*提示信息,如驱动程序启动时,打印硬件信息*/ 
#defineKERN_DEBUG"<7>"/*调试级别的消息*/ 

没有指定日志级别的printk语句默认采用的级别是 DEFAULT_ MESSAGE_LOGLEVEL(这个默认级别一般为<4>,即与KERN_WARNING在一个级别上),其定义在linux26/kernel/printk.c中可以找到。 
下面是一个比较简单的使用 
printk(KERN_INFO "INFO\n"); //这里可以使用数字代替 KERN_INFO,即可以写成printk("<6> INFO\n"); 
在这个格式的定义中,日志级别和信息文本之间不能够使用逗号隔开,因为系统在进行编译的时候,将日志级别转换成字符串于后面的文本信息进行连接。 


七、/proc文件系统  
 /proc 文件系统在linux 内核中是一个特殊的文件系统。他是一个许你的文件系统,它不和块设备联系,仅仅在内存中存在。/proc 文件系统包含目录(作为组织信息的方式)和虚拟文件虚拟文件可以表示内核到用户的信息,并作为从用户发送信息到内核的手段。 
 
八、利用procfs实现“数据从用户空间-->内核空间-->用户空间” 
8.1 源代码: 
unanao@debian:~/programming/modules/read_write$ cat procfs.c 
#include  
#include  
#include //如果要使用procfs的函数,需要包含头文件: 
#include  
#include  
#include  
 
#define MAX_LENGTH PAGE_SIZE 
static struct proc_dir_entry *proc_entry; 
 
static char *buf; //space where in the kernel sapce for input data 
 
/*function to write the data*/ 
int data_write(struct file *filp,const char __user *buff,unsigned long len, void *data) 

    if(len >MAX_LENGTH){ 
        printk(KERN_EMERG "virtual_proc:buf is full"); 
        return -ENOSPC;//no space left on device 
    } 
    /* Copy buffer to kernel-space from user-space function:  
     *unsigned long copy_from_user( void *to, const void __user *from,\ 
     *unsigned long n ); 
     */ 
    if(copy_from_user(buf,buff,len)){ 
        return -EFAULT;    //asm-generic/errno-bash.h ,bad address     
    } 
    return len; 

/* Function to read a fortune*/ 
int data_read(char *page,char **start,off_t off,int count,int *eof,void *data) 

    int len; 
    if(off > 0){ 
        *eof=1; 
        return 0; 
    } 
    /*wrap around*/ 
    len=sprintf(page,"%s\n",buf); 
    return len; 

 
int init_virtual_proc(void) 

    int ret=0; 
    /* 
    *when vmalloc come across the error return 0(NULL address) 
    *success return back a pointer 
     */ 
    buf=(char *)vmalloc(MAX_LENGTH); 
    if(!buf){ 
        return -ENOMEM;//out of memory 
    } 
    else{ 
        memset(buf,0,MAX_LENGTH); 
        /* 
        *create_proc_entry function. This function accepts a file name, a  
        *set of permissions, and a location in the /proc filesystem in  
        *which the file is to reside. The return value of  
        *create_proc_entry is a proc_dir_entry pointer (or NULL,indicating  
        *an error in create). You can then use the return pointer to  
        *configure other aspects of the virtual file, such as the function  
        *to call when a read is performed on the file 
         */ 
        proc_entry=create_proc_entry("virtual_proc",0644,NULL); 
        if(proc_entry==NULL){ 
            ret=-ENOMEM;//no memory 
            vfree(buf); 
            printk(KERN_EMERG "virtual_proc:couldn't create virtual_proc entry\n"); 
        } 
        else{ 
            /* 
            *use the read_proc and write_proc commands to plug in functions  
            *forreading and writing the virtual file.  
             */ 
            proc_entry->read_proc=data_read;//proc read function 
            proc_entry->write_proc=data_write;//proc write function 
            proc_entry->owner=THIS_MODULE; 
            proc_entry->mode=S_IRWXU|S_IRGRP|S_IROTH|S_IWOTH; 
            printk(KERN_EMERG "virtual_proc :Module loaded\n"); 
        } 
    } 
    return ret; 

 
void cleanup_virtual_proc(void) 

    remove_proc_entry("virtual_proc",NULL); 
    vfree(buf); 
    printk(KERN_EMERG "virtual:module unloaded\n"); 

module_init(init_virtual_proc); 
module_exit(cleanup_virtual_proc); 
 
MODULE_LICENSE("Dual BSD/GPL"); 
MODULE_DESCRIPTION("ProcFileSystem"); 
MODULE_AUTHOR("unanao"); 
 
8.2 Makefile 文件 
unanao@debian:~/programming/modules/read_write$ cat Makefile  
ifneq ($(KERNELRELEASE),) 
    obj-m := procfs.o 
else 
    KERNELDIR ?=/lib/modules/$(shell uname -r)/build 
    PWD := $(shell pwd) 
default: 
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules 
clean: 
    rm -rf *.o *.ko *.mod.c [mM]odule* .*.cmd .tmp_versions 
endif 
 
九、编译、插入,执行 
编译: 
debian:/home/unanao/programming/modules/read_write$ make 
插入模块: 
debian:/home/unanao/programming/modules/read_write# insmod procfs.ko  
在用户空间写入数据: 
unanao@debian:~/programming/modules/read_write$ echo "jianjiao" >/proc/virtual_proc  
在用户空间读出数据: 
unanao@debian:~/programming/modules/read_write$ cat /proc/virtual_proc  
jianjiao 
写在模块: 
debian:/home/unanao/programming/modules/read_write# rmmod procfs 
 
十、模块一些函数的解释 
 
struct proc_dir_entry 配置虚拟文件的其它方面,如:调用一个read函数。 
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 
 
    ... 
}; 
 
如果procfs只是被一个模块使用,一定要把struct proc_dir_entry 中的owner设为THIS_MODULE。 
struct proc_dir_entry* entry; 
entry->owner = THIS_MODULE; 
 
设置procfs的权限为和属主,如: 
struct proc_dir_entry* entry; 
entry->mode = S_IWUSR |S_IRUSR | S_IRGRP | S_IROTH; 
entry->uid = 0; 
entry->gid = 100; 
(上面只是例子,可以根据需要进行自己的设置) 
 
 
struct proc_dir_entry* create_proc_entry(const char* name, mode_t mode, struct proc_dir_entry* parent); 
 
该函数创建一个名为“name”普通文件,在父目录中文件的模式是“mode”。在procfs的根目录创建文件,使用NULL作为“parent”的参数。当成功,函数将返回一个struct proc_dir_entry指针,否则将返回NULL。 
 
移除一个入口(entry) 
void remove_proc_entry(const char* name, struct proc_dir_entry* parent); 
从procfs中,删除在“parent”目录中的“name”文件。entry是通过名字删除的,而不是由create_proc_entry 创建时返回的struct proc_dir_entry 的类型的变量。请注意,此功能不递归删除项目。 
在remove_proc_entry之前,一定要释放data (即:如果有一些数据分配了内存)。 
 
和用户空间通信 
create_proc_entry 的返回值struct proc_dir_entry* 的read_proc 和 write_proc 用来初始化read和write函数。 
struct proc_dir_entry* entry; 
entry->read_proc = read_proc_foo; 
entry->write_proc = write_proc_foo; 
 
read 函数负责把它的信息写入buffer,read函数的格式为: 
int read_func(char* buffer, char** start, off_t off, int count,int* peof, void* data); 
 
peof:当到达文件末尾时写“1” 
data:很少使用,用于回调函数 
 
write 函数用于将用户空间的数据写入内核空间,write函数的格式: 
int write_func(struct file* file, const char* buffer, unsigned long count, void* data); 
 
其它一些函数: 
* Create a directory in the proc filesystem */ 
struct proc_dir_entry *proc_mkdir( const char *name,struct proc_dir_entry *parent ); 
 
/* Create a symlink in the proc filesystem */ 
struct proc_dir_entry *proc_symlink( const char *name,struct proc_dir_entry *parent,const char *dest ); 
 
/* Create a proc_dir_entry with a read_proc_t in one call */ 
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 ); 
 
/* Copy buffer to user-space from kernel-space */ 
unsigned long copy_to_user( void __user *to,const void *from,unsigned long n ); 
 
/* Copy buffer to kernel-space from user-space */ 
unsigned long copy_from_user( void *to,const void __user *from,unsigned long n ); 
 
/* Allocate a 'virtually' contiguous block of memory */ 
void *vmalloc( unsigned long size ); 
 
/* Free a vmalloc'd block of memory */ 
void vfree( void *addr ); 
 
/* Export a symbol to the kernel (make it visible to the kernel) */ 
EXPORT_SYMBOL( symbol ); 
 
/* Export all symbols in a file to the kernel (declare before module.h) */ 
EXPORT_SYMTAB

十一、参考资料:
11.1
11.2 Jonathan Corbet, Alessandro Rubini 和 Greg Kroah-Hartman
《Linux 设备驱动》(第三版)
11.3 Linux 可加载内核模块剖析
http://www.ibm.com/developerworks/cn/linux/l-lkm/

11.4 Erik (J.A.K.) Mouw 《Linux Kernel Procfs Guide》

阅读(856) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~