linux驱动程序编程学习[转]
分类: linux相关
原创 无双
发表于loveunix.net
转载请注明作者与出处
应用程序与驱动程序通过设备文件进行通信
每个设备文件都有主设备号与次设备号 主设备号表示设备的类型 次设备号表示具体的设备
在内核中 就是根据主设备号来调用相应的驱动程序 驱动根据次设备号分辩具体设备以区分操作
主设备号由linux统一分配 但是也可以使用临时设备 在注册驱动时把主设备号输入为0则由内核自动分配
设备的类型:
主要类型有下面三种
字符设备:它的保存是以字符流的形式保存
块设备:它的保存是以块为单位保存,提供和字符驱动一样的接口,当然有自己块操作的接口,如mount,同时支持像字符设备那样的访问方式,
上面两个都是映射到一个设备文件,设备文件通常保存在/dev/目录下,当然你可以保存在任意地方
网络接口:用于进行网络通信,可能针对某个硬件,如网卡,或是纯软件,如loopback,网络接口只是面向数据包而不是数据流,所以内核的处理也不同,没有映射成任何设备文件,而是按unix标准给它们分配一个唯一的名字
驱动开发时注意点:
1 只提供机制,不限制策略,调用者可以按照自己的要求自己订制策略
2 权限的判断, 应该尽量让系统管理员决定而不是在驱动中指定,除非这个设备对整个系统有影响
3 安全编程,从用户进程得到的输入必须检查,返回给用户进程的内存必须初始化(如果这块内存刚好保存口令信息 或是其它就会破坏系统安全性)
4 内核不连接libc,所以不可以使用libc里面的函数 如printf等,必须使用内核自己提供的函数,如果你不知道有哪些函数,那看看内核头文件是不错的选择
5 避免名字空间污染,不需要输出的符号就不输出 如果你的符号被其它模块引用,那使用EXPORT_SYMBLE声明被输出,要输出的模块最好加上模块名前缀 避免冲突 .不对外开放的使用static限制在本地使用
6 注意可重入性问题,必须考虑到执行到某步时任务切换或是中断处理时应该怎样处理.
在2.6下开发简单的驱动
2.6驱动的开发相对以前各版本来说有了很大的改变 先是初始化与退出函数
以前使用init_module函数来声明模块初始化过程 使用cleanup_module来定义模块退出时的清除过程
而在2.6中 必须使用 module_init 标志来说明初始化函数 使用module_exit来说明清理函数
在2.4中,可以单独编译一个模块 而在2.6中,一个模块的编译,必须编译全部所有模块
下面是一个2.6内核的模板
#define MODULE
#include linux/module.h>
#include linux/config.h>
#include linux/init.h>
static int __init name_of_initialization_routine(void) {
/*
* code here
*/
}
static void __exit name_of_cleanup_routine(void) {
/*
* code here
*/
}
module_init(name_of_initialization_routine);
module_exit(name_of_cleanup_routine);
MODULE宏也不再是必须的了,在编译时可不定义
编译过程的区别
2.4的编译过程如下
gcc -D__KERNEL__ -DMODULE -I/usr/src/linux-2.4.21/include -O2 -c testmod.c
要定义__KERNEL__是因为在内核头文件中,某些符号只在定义这个宏后才会公开
2.6的编译过程
写一个简单的Makefile只有一行
obj-m := testmod.o
然后使用如下命令编译
make -C /usr/src/linux-2.6.1 SUBDIRS=$WD modules
$WD是你的内核模块所在的目录 使用如上命令时会自动重编译系统的内核模块与你的模块
所以 保存内核源目录中的版本号与配置文件与你当前运行内核相同是必要的
一个简单的例子:
testmod.c
#define MODULE
#include <linux/module.h>
#include <linux/config.h>
#include <linux/init.h>
static int __init testmod_init(void)
{
printk("<1>hello world\n");
return 0;
}
static void __exit testmod_cleanup(void)
{
printk("<1>exit module\n");
}
module_init(testmod_init);
module_exit(testmod_cleanup);
Makefile 编译方法与执行结果
wushuang:~/work/kernel# cat Makefile
obj-m :=testmod.o
wushuang:~/work/kernel# cat test
testmod: module license 'unspecified' taints kernel.
hello world
wushuang:~/work/kernel# rmmod testmod
exit module
wushuang:~/work/kernel#
wushuang:~/work/kernel# make -C /usr/src/linux SUBDIRS=~/work/kernel/ modules
第一个可用的例子
下面是一个简单的字符设备驱动程序
* chardev.c: Creates a read-only char device that says how many
* times
* * you've read from the dev file
* */
#include <linux/module.h>
#include <linux/config.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/moduleparam.h>
static int __init testmod_init(void);
static void __exit testmod_exit(void);
static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char *, size_t, loff_t *);
static ssize_t device_write(struct file *, const char *, size_t, loff_t *);
#define SUCCESS 0
#define DEVICE_NAME "chardev" /* Dev name as it appears in /proc/devices */
#define BUF_LEN 80 /* Max length of the message from the device */
/* Global variables are declared as
* static, so are global within the file.
* */
static int Major; /* Major number assigned to our device driver */
static int Device_Open = 0; /* Is device open? Used to prevent multiple */
static char msg[BUF_LEN]; /* The msg the device will give when asked */
static char *msg_Ptr;
static struct file_operations fops = {
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release
};//注意这里需要是静态变量, 不然会出现内核访问错误情况
//.open = device_read,对fops里面的open变量赋值为device_read,
//,这是c99;里面定义的新标准,表示定义结构变量时同时对它进行子元素的赋值
// gcc扩展使用的是open:device_read,
/* Functions
* */
static int __init testmod_init(void)
{
Major = register_chrdev(240, DEVICE_NAME, &fops);
//注册字符驱动 第一个参数是主设备号 如果第一个参数是0 表示由内核自动分配
//第二个是设备名 可以在/proc/devices里面看到
//第三个是对此设备的操作函数 具体操作可以看file_operations结构定义 在linux/fs.h里面
if (Major < 0) {
printk ("Registering the character device failed with %d\n", Major);
return Major;
}
Major = 240;
printk("<1>I was assigned major number %d. To talk to\n", Major);
printk("<1>the driver, create a dev file with\n");
printk("'mknod /dev/hello c %d 0'.\n", Major);
printk("<1>Try various minor numbers. Try to cat and echo to\n");
printk("the device file.\n");
printk("<1>Remove the device file and module when done.\n");
return 0;
}
static void __exit testmod_exit(void)
{
/* Unregister the device */
int ret = unregister_chrdev(Major, DEVICE_NAME);//取消注册 取消了后模块可以unload
if (ret < 0) printk("Error in unregister_chrdev: %d\n", ret);
}
/* Methods
* */
/* Called when a process tries to open the device file, like
* * "cat /dev/mycharfile"
* */
static int device_open(struct inode *inode, struct file *file)
{
static int counter = 0;
if (Device_Open) return -EBUSY;
Device_Open++;
sprintf(msg,"I already told you %d times Hello world!\n", counter++);
msg_Ptr = msg;
//MOD_INC_USE_COUNT;在2.6中,会自动维护引用计数,所以不再需要这个宏,但是如果要考虑兼容性那最好保存这个宏
printk("<1>device_open call\n");
return SUCCESS;
}
/* Called when a process closes the device file.
* */
static int device_release(struct inode *inode, struct file *file)
{
Device_Open --; /* We're now ready for our next caller */
printk("<1>device_release call\n");
/* Decrement the usage count, or else once you
* opened the file, you'll
* never get get rid of the module.
* */
//MOD_DEC_USE_COUNT;
return 0;
}
/* Called when a process, which already opened the dev file,
* attempts to
* read from it.
* */
static ssize_t device_read(struct file *filp,
char *buffer, /* The buffer to fill with data */
size_t length, /* The length of the buffer */
loff_t *offset) /* Our offset in the file */
{
/* Number of bytes actually written to the buffer
* */
int bytes_read = 0;
/* If we're at the end of the message, return 0
* signifying end of file */
if (*msg_Ptr == 0) return 0;
/* Actually put the data into the buffer */
while (length && *msg_Ptr) {
//内核空间与用户空间内存是不同的,所以需要使用内存操作函数 而不可以直接赋值
/* The buffer is in the user data
* segment, not the kernel
* segment;
* * assignment won't work.
* We have to use put_user
* which copies data from
* * the kernel data
* segment to the
* user data
* segment. */
put_user(*(msg_Ptr++), buffer++);
length--;
bytes_read++;
}
/* Most read functions return the
* number of bytes put into the buffer
* */
return bytes_read;
}
/* Called when a process writes to dev file: echo "hi" >
* /dev/hello */
static ssize_t device_write(struct file *filp,
const char *buff,
size_t len,
loff_t *off)
{
printk ("<1>Sorry, this operation isn't supported.\n");
return -EINVAL;
}
module_init(testmod_init);
module_exit(testmod_exit);
上面是一个简单的字符设备驱动例子
使用的makefile如下:
subdir='/root/work/kernel/'
obj-m :=testmod.o
all:
make -C /usr/src/linux SUBDIRS=$(subdir) modules
clean:
-rm testmod.[^c]*
reset:clean all
-rmmod testmod
-rm *wushuang*
insmod testmod.ko
mknod testmod_wushuang0 c 240 0
使用时make reset就OK了
简单的测试办法
echo"hello">testmod_mushuang0
第一次自己写时没有把fops定义成静态的 结果每次都报错 很是郁闷 后面从网上拿到了这个程序改了改
旧的内核需要自己维护模块引用计数 所以使用MOD_INC_USE_COUNT和MOD_DEC_USE_COUNT
新内核中自动维护 这两个已经不再使用
file_operations定义了字符设备的接口 你可以根据自己的需要添加自己要处理的函数 不需要处理的函数将为NULL 由内核自动处理
如果需要传参数 那使用module_param宏
如下:
subdir='/root/work/kernel/'
obj-m :=testmod.o
all:
make -C /usr/src/linux SUBDIRS=$(subdir) modules
clean:
-rm testmod.[^c]*
reset:clean all
-rmmod testmod
-rm *wushuang*
insmod testmod.ko
mknod testmod_wushuang0 c 240 0
内核内可使用符号的查找
第一个办法是看/proc/kallsyms
第二个是看内核文档或是代码
http://www.kernelnewbies.org/documents/ 这里列出了很多连接 包括内核api文档
第三个是看邮件列表 文档总不可能及时更新 这时而相同的问题可能别人已经碰到过并处理完 这时就没有必要再重复一次以体现自己的伟大了
另外需要注意的是 开发时必须include正确的头文件 不然会报错 虽然为些错有些莫名奇妙(可能因为使用的宏类型比较多吧 )
头文件在linux/目录下 asm/目录下也有
如果你在linux下工作 那建议使用cscope 来帮助查找
使用方法
cd /usr/include/linux
cscope -R
然后就出现了cscope的界面
CODE
Global definition: get_user
File Line
0 uaccess.h 171 #define get_user(x,ptr) \
Find this C symbol: 查找这个符号
Find this global definition: 查找这个定义
Find functions called by this function:
Find functions calling this function:
Find this text string:
Change this text string:
Find this egrep pattern:
Find this file:
Find files #including this file:
使用tab来跳转 想退出时按ctrl-d就可以 或是ctrl-c
它可以反向查找和正向查找 很是方便 查找到后按数字或是在选中项上按回车就使用默认编辑器打开到指定页
另外已经集成在了vim中 只是vim默认编译时不打开cscope支持 如果想在vim中使用还需要自己另外编译
在内核中有一个全局变量current
它是一个task_strut的指针 定义在asm/current.h
task_struct定义在linux/scked.h
这个变量指向了当前调用者信息
如果你想取调用者信息的话 可以取出它的值
下面是一个简单的例子
static ssize_t device_read(struct file *filp,
char *buffer, /* The buffer to fill with data */
size_t length, /* The length of the buffer */
loff_t *offset) /* Our offset in the file */
{
char outbuf[2048];
int len;
if(!buffer || length <1)
return 0;
len = dump_processinfo(outbuf,2047);
outbuf[2047] = 0;
if(len >length)
len = length;
copy_to_user(buffer,outbuf,len);
return len;
}
static size_t dump_processinfo(char *buf,int len)
{
if(!buf || len <1 )
return 0;
return snprintf(buf,len,"stat:%d actvated:%d pid:%d cmd:%s\n",
current->state,current->activated,current->pid,current->comm );
}
把上面的device_read代替上一贴中的device_read然后重编译
简单测试方法就是
cat testmod_mushuang0
使用ctrl-c停止
注意到里面使用了copy_to_user 内核空间中的内存与用户空间的不一样 所以需要使用内存操作函数把内核中信息复制到用户空间去 同理还有上面的put_user 也是一样的功能
对应的,内核空间需要读用户空间数据时,使用copy_from_user ,get_user
get_user和put_user只是指定了源和目的 没有使用长度 那它是如何复制的呢 是不是只复制一个字节或是指定的字节 这个开始时容易让人感到困惑
它们复制的长度是根据第二个数的类型来判断的
也就是 put_user(x,y) = copy_to_user(x,y,sizeof(*y)) 只是性能更快 注意y 应该是内建类型 也就是如char short int类型
内存具体信息可以在网上查找
KernelHacking101.pdf
模块在内核中的实例个数
在应用开发时 每个进程的地址空间都是独立的 改变一个进程的值 不会影响其它进程
但是在内核中是不是也会这样呢
写一个简单的驱动程序
如下
/* chardev.c: Creates a read-only char device that says how many
* times
* * you've read from the dev file
* */
#include <linux/module.h>
#include <linux/config.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/moduleparam.h>
static int __init testmod_init(void);
static void __exit testmod_exit(void);
static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char *, size_t, loff_t *);
static ssize_t device_write(struct file *, const char *, size_t, loff_t *);
static size_t dump_processinfo(char *buf,int len);
#define SUCCESS 0
#define DEVICE_NAME "chardev" /* Dev name as it appears in /proc/devices */
#define BUF_LEN 80 /* Max length of the message from the device */
/* Global variables are declared as
* static, so are global within the file.
* */
static int Major; /* Major number assigned to our device driver */
static int Device_Open = 0; /* Is device open? Used to prevent multiple */
static char msg[BUF_LEN]; /* The msg the device will give when asked */
static char *msg_Ptr;
static struct file_operations fops = {
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release
};
/* Functions
* */
static int __init testmod_init(void)
{
Major = register_chrdev(240, DEVICE_NAME, &fops);
if (Major < 0) {
printk ("Registering the character device failed with %d\n", Major);
return Major;
}
Major = 240;
printk("<1>I was assigned major number %d. To talk to\n", Major);
printk("<1>the driver, create a dev file with\n");
printk("'mknod /dev/hello c %d 0'.\n", Major);
printk("<1>Try various minor numbers. Try to cat and echo to\n");
printk("the device file.\n");
printk("<1>Remove the device file and module when done.\n");
return 0;
}
static void __exit testmod_exit(void)
{
/* Unregister the device */
int ret = unregister_chrdev(Major, DEVICE_NAME);
if (ret < 0) printk("Error in unregister_chrdev: %d\n", ret);
}
/* Methods
* */
/* Called when a process tries to open the device file, like
* * "cat /dev/mycharfile"
* */
static int device_open(struct inode *inode, struct file *file)
{
static int counter = 0;
if (Device_Open) return -EBUSY;
Device_Open++;
sprintf(msg,"I already told you %d times Hello world!\n", counter++);
msg_Ptr = msg;
//MOD_INC_USE_COUNT;
printk("<1>device_open call\n");
return SUCCESS;
}
/* Called when a process closes the device file.
* */
static int device_release(struct inode *inode, struct file *file)
{
Device_Open --; /* We're now ready for our next caller */
printk("<1>device_release call\n");
/* Decrement the usage count, or else once you
* opened the file, you'll
* never get get rid of the module.
* */
//MOD_DEC_USE_COUNT;
return 0;
}
/* Called when a process, which already opened the dev file,
* attempts to
* read from it.
* */
static ssize_t device_read(struct file *filp,
char *buffer, /* The buffer to fill with data */
size_t length, /* The length of the buffer */
loff_t *offset) /* Our offset in the file */
{
char outbuf[2048];
int len = dump_processinfo(outbuf,2047);
outbuf[2047] = 0;
if(len >length)
len = length;
copy_to_user(buffer,outbuf,len);
return len;
/* Most read functions return the
* number of bytes put into the buffer
* */
//return bytes_read;
}
/* Called when a process writes to dev file: echo "hi" >
* /dev/hello */
static ssize_t device_write(struct file *filp,
const char *buff,
size_t len,
loff_t *off)
{
printk ("<1>Sorry, this operation isn't supported.\n");
return -EINVAL;
}
static size_t dump_processinfo(char *buf,int len)
{
static int i = 0;
if(!buf || len <1 )
return 0;
printk(KERN_INFO"%dth dump\n",i++);
return 0;
}
module_init(testmod_init);
module_exit(testmod_exit);
这只是在上面的例子基础上进行了一些修改 注意在dump_processinfo里面使用了一个静态变量
测试步骤如下:
编译上面的模块 然后使用insmod testmod.ko加载模块
可重入的进一步研究
即然模块在内存中只有一份 那想操作多个文件时什么办
这时考虑的就是看open操作会不会被调用了
对任何操作 都是先调用open 再进行读写操作 最后调用close(ioctl还没江过)
如果你的程序中只调用open而没有调用close时 那会怎样呢
答案是 你没有调用close时 在进程退出时操作系统也会调用close 所以不会引起资源泄漏
在这里可以补充一下c中进程三种退出方式的区别
第一个是普通的return 这种与exit相同
第二个是使用exit,刷新所有打开文件并close,调用使用atexit 注册的退出清理函数进行退出清理
第三个是_exit,只是关闭文件 ,不调用atexit注册的退出函数,也不刷新IO,这可能会引起数据的不同步 但是速度比exit快
mknod testmod_wushuang c 240 0
mknod testmod_wushuang1 c 240 1
cat testmod_wushuang
cat testmod_wushuang
cat testmod_wushuang1
cat testmod_wushuang1
看上面的输出结果 发现虽然是不同的进程 对不同的文件进行操作 但是序号还是在增加中
rmmod testmod
insmod testmod
重加载内核模块
然后重复上面的步骤
发现重加载后 计数器从头开始了
可见驱动模块在整个系统中是唯一的 另外 每次模块unload时 内核都会把它的内存空间翻译
所以 在模块开发中要慎用静态变量