http://guliqun1983.blog.163.com/blog/static/501116852011730535314/
全部博文(120)
分类: 嵌入式
2011-07-25 21:29:19
图 内核的划分
二、在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
#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》