好好学习,天天向上
分类: LINUX
2011-04-08 12:08:55
基本的字符驱动
1.获取设备号
设备编号是一个32长度的整型:
linux/types.h
typedef u_long dev_t;
其中高22位为主设备号,低8位为次设备号,使用下面宏可以得到设备号
linux/kdev_t.h
#define MAJOR(dev) ((dev)>>8)
#define MINOR(dev) ((dev) & 0xff)
#define MKDEV(ma,mi) ((ma)<<8 | (mi))
设备号有什么用?
LDD3上说主设备号标识驱动,次设备号标识设备。
我自己的理解:应用程序通过访问/dev目录下的设备文件来访问驱动,比如对一个设备文件进行read,设备文件通过自己的主设备号,在内核的驱动链表中查找到驱动入口,然后再调用驱动的read。
所以,字符驱动首先要得到一个设备号,这样才能被应用程序找到。
得到设备号的办法,一个是自己手动设定,比如看到26这个主设备号没人用,那么就可以使用这个做为我们的驱动主设备号了,另一个方法是,不知道哪个号码有人用,懒得去看,那么可以请求内核分配一个。这两种方法各有优缺点,第一个方法创建设备文件时简单,但不能保证在其他系统中这个设备号也没驱动在用,会使用系统的设备号比较混乱,注册时可能导致失败,第二个方法,创建设备文件时复杂。 推荐使用第二个方法。
动态分配设备号:
linux/fs.h
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, char *name);
dev用来返回分配的设备号
baseminor是次设备号的起始值,通常为0
count 是你请求的连续设备编号的总数.
name 是应当连接到这个编号范围的设备的名字,它会出现在 /proc/devices 和 sysfs 中.
函数成功返回0,失败返回负的错误代码
当你的驱动从内核卸载时,当然得把设备号还回去,使用下面的函数,一般在模块退出时使用:
void unregister_chrdev_region(dev_t first, unsigned int count);
测试:
base_char.c
#include
#include
#include
#include
dev_t dev_major;
static int __init base_char_init(void)
{
dev_t dev;
if(alloc_chrdev_region(&dev,0,1,”base_char”)<0)
{
printk(“base_char alloc dev id failed!\n”);
dev_major=0;
goto error0;
}
dev_major=MAJOR(dev);
printk(“base_char devmajor is %d\n”,dev_major);
return 0;
error0:
return -1;
}
static void __exit base_char_exit(void)
{
if(dev_major!=0)
unregister_chrdev_region(MKDEV(dev_major,0),1);
}
module_init(base_char_init);
module_exit(base_char_exit);
MODULE_AUTHOR(“crazycode”);
Makefile:
obj-m := base_char.o
KDIR := /home/linux-2.6.9
PWD := $(shell pwd)
default:
$(MAKE) –C $(KDIR) M=$(PWD) modules
.PHONY:clean
clean:
-rm *.o *.ko *.mod.c
make
insmod base_char.ko
cat /proc/devices
254 base_char
可以看到主设备号为254
2.创建设备文件/设备节点
得到设备号了,接下来就可以创建设备文件了,创建设备文件的方法是调用mknod工具:
mknod /dev/base_char –m 666 –c 254 0
-m 666 表示文件的权限位为666
-c 254 0 表示主设备号为254,次设备号为0
使用awk脚本读取/proc/devices中的设备号可以自动创建设备文件。
3.字符驱动的注册
应用程序和驱动的交互,内核提供的接口是设备文件,应用程序通过open,read,write等基本的文件操作来访问设备文件。最终这些操作是调用了驱动中我们提供的函数,内核怎么知道驱动的?这需要我们来注册它,这个步骤就是字符驱动的注册。
字符驱动的注册,简单地说,就是告诉内核一些驱动的信息,然后内核保存这些信息以便后面使用,内核用一个结构体来记录这些信息:
linux/cdev.h
struct cdev {
struct kobject kobj;
struct module *owner;
struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
owner模块的所有者,ops一组自定义的文件操作函数,dev起始设备号,count关联到设备的设备号数目,里面的list告诉我们,这个结构体会被放在一个循环链表中,事实也正是如此。当一个驱动拥有多个设备(cout>1)的时候,注册的时候,内核会增加多个cdev结构体,也就是说一个设备一个结构体。
这个结构体代表着内核中的一个字符设备,每个字符设备都对应着这样一个结构体。
内核提供了一些函数用来初始化cdev结构体,以及添加cdev到内核,它们是:
linux/cdev.h
struct cdev *cdev_alloc(void);//动态申请,也可以自己静态定义的
void cdev_init(struct cdev *cdev, struct file_operations *fops);//初始化
int cdev_add(struct cdev *p, dev_t dev, unsigned count);//注册
void cdev_del(struct cdev *p);//从内核中删除
下面是初始化要的文件操作函数结构体:
linux/fs.h
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_write) (struct kiocb *, const char __user *, size_t, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*dir_notify)(struct file *filp, unsigned long arg);
int (*flock) (struct file *, int, struct file_lock *);
};
测试:
base_char.c
#include
#include
#include
#include
#include
dev_t dev;
struct cdev cdev;
struct file_operations fop;
static int __init base_char_init(void)
{
if(alloc_chrdev_region(&dev,0,1,"base_char")<0)
{
printk("base_char alloc dev id failed!\n");
goto error0;
}
fop.owner=THIS_MODULE;
cdev_init(&cdev,&fop);
cdev.owner=THIS_MODULE;
if(cdev_add(&cdev,dev,1)<0)
{
printk("base_char cdev add failed!\n");
goto error1;
}
printk("base char register successful!\n");
return 0;
error1:
unregister_chrdev_region(dev,1);
error0:
return -1;
}
static void __exit base_char_exit(void)
{
cdev_del(&cdev);
unregister_chrdev_region(dev,1);
}
module_init(base_char_init);
module_exit(base_char_exit);
MODULE_AUTHOR("crazycode");
MODULE_LICENSE("GPL");
上面的例子并没有添加文件操作函数,下面我们给它加上open,release,read.write,构成一个基本的字符驱动:
在base_char.c中增加四个函数:
ssize_t base_char_read(struct file *fp, char __user *buf, size_t size, loff_t *ff)
{
printk("base char read!\n");
return 0;
}
ssize_t base_char_write(struct file *fp, const char __user *buf, size_t size, loff_t *ff)
{
printk("base char write!\n");
return 0;
}
int base_char_open(struct inode *inode, struct file *fp)
{
printk("base char open!\n");
return 0;
}
int base_char_release(struct inode *inode, struct file *fp)
{
printk("base char close!\n");
return 0;
}
struct file_operations fop=
{
.owner=THIS_MODULE,
.open=base_char_open,
.write=base_char_write,
.read=base_char_read,
.release=base_char_release
};
接下来写个测试的应用程序:test.c
#include
#include
#include
#include
#include
int main()
{
int fd=open("/dev/base_char",O_RDONLY);
if(fd<0)
{
perror("");
return -1;
}
char buf[10];
read(fd,buf,10);
write(fd,buf,10);
close(fd);
return 0;
}
编译:
$ gcc -o test test.c
加载模块
$ sudo insmod base_char.ko
查看设备号:
$ cat /proc/devices
250 base_char
创建设备文件:
sudo mknod /dev/base_char -m 666 c 250 0
测试:./test
可以看到系统日志里面显示了下面信息,说明应用程序已经成功调用了驱动。
Apr 6 21:48:21 ubuntu kernel: [112431.976741] base char register successful!
Apr 6 21:50:43 ubuntu kernel: [112573.379065] base char open!
Apr 6 21:50:43 ubuntu kernel: [112573.379106] base char read!
Apr 6 21:50:43 ubuntu kernel: [112573.379142] base char close!
回过头来,看看open等函数的参数,发现两个新的结构体,struct inode和struct file,inode结构体定义如下:
linux/fs.h
struct inode {
struct hlist_node i_hash;
struct list_head i_list;
struct list_head i_dentry;
unsigned long i_ino;
atomic_t i_count;
umode_t i_mode;
unsigned int i_nlink;
uid_t i_uid;
gid_t i_gid;
dev_t i_rdev;
loff_t i_size;
struct timespec i_atime;
struct timespec i_mtime;
struct timespec i_ctime;
unsigned int i_blkbits;
unsigned long i_blksize;
unsigned long i_version;
unsigned long i_blocks;
unsigned short i_bytes;
unsigned char i_sock;
spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
struct semaphore i_sem;
struct rw_semaphore i_alloc_sem;
struct inode_operations *i_op;
struct file_operations *i_fop; /* former ->i_op->default_file_ops */
struct super_block *i_sb;
struct file_lock *i_flock;
struct address_space *i_mapping;
struct address_space i_data;
#ifdef CONFIG_QUOTA
struct dquot *i_dquot[MAXQUOTAS];
#endif
/* These three should probably be a union */
struct list_head i_devices;
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev;
int i_cindex;
__u32 i_generation;
unsigned long i_dnotify_mask; /* Directory notify events */
struct dnotify_struct *i_dnotify; /* for directory notifications */
unsigned long i_state;
unsigned long dirtied_when; /* jiffies of first dirtying */
unsigned int i_flags;
atomic_t i_writecount;
void *i_security;
union {
void *generic_ip;
} u;
#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount;
#endif
};
这个结构体,是文件在内核中的体现,它对于每个文件来说是唯一的,当系统要打开一个文件,如果该文件在内核中已经拥有了inode节点,那么仅创建struct file和设置一些参数,如果还没有该文件的inode节点,那么就新建一个inode节点。这个结构只有 2 个成员对于编写驱动代码有用:
dev_t i_rdev; 对于代表设备文件的节点, 这个成员包含实际的设备编号.
struct cdev *i_cdev; cdev结构体代表字符设备
从inode节点中获取主次设备号:
static inline unsigned iminor(struct inode *inode);
static inline unsigned imajor(struct inode *inode);
再看struct file
struct file {
struct list_head f_list;
struct dentry *f_dentry;
struct vfsmount *f_vfsmnt;
struct file_operations *f_op;
atomic_t f_count;
unsigned int f_flags;
mode_t f_mode;
int f_error;
loff_t f_pos;
struct fown_struct f_owner;
unsigned int f_uid, f_gid;
struct file_ra_state f_ra;
unsigned long f_version;
void *f_security;
/* needed for tty driver, and maybe others */
void *private_data;
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
spinlock_t f_ep_lock;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
};
file结构体代表着一个打开的文件 (它不特定给设备驱动; 系统中每个打开的文件有一个关联的 struct file 在内核空间). 它由内核在 open 时创建, 并传递给在文件上操作的任何函数, 直到最后的关闭.
这两个传入到open的参数,对于驱动开发来讲有什么用呢?首先应该明确我们在open函数中应当做的事情:
要检查设备信息,就要知道打开了哪个设备,这个设备结构体cdev就可以从inode中拿到了。可能有人会问,为什么自己定义的cdev结构体,注册到内核中去,最终又要从内核中拿回来呢,这不多此一举吗?上面说了,如果驱动有多个设备,那么内核中就会有多个此驱动的cdev,所以要重新从内核中拿到正确的cdev。
在open中初始化设备信息,好一点的做法是定义一个结构体来保存设备信息,并把cdev放进去,然后将设备信息结构体的指针放到file结构体的private_data中,让后面的read,write等方法使用。
拿到cdev后怎么得到设备信息结构体指针呢,内核提供了宏:
linux/kernel.h
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
代码可以这样写:
struct dev_info_struct
{
struct cdev cdev;
}dev_info;
。。。
int base_char_open(struct inode *inode, struct file *fp)
{
struct dev_info_struct *dev_info;
dev_info=container_of(inode->i_cdev,struct dev_info_struct,cdev);
fp->private_data=dev_info;
printk("base char open!\n");
return 0;
}
。。。
我们的字符驱动到现在什么也没做,现在有必要增加一个设备给它,LDD3上的scull我个人认为其内存模型有些复杂,不适合做些简单的实验,我决定把我的设备定义成这样:一块长度为50字节的内存。驱动加载时,向系统申请一块50字节的内存做为设备,驱动卸载时,释放这些空间。
怎么分配内存,使用malloc?别忘了内核中没有C库的,正解是使用内核提供的函数:
linux/slab.h
static inline void *kmalloc(size_t size, int flags);
void kfree (const void *objp);
其中flags是内存的类型,这里使用GFP_KERNEL,失败返回NULL,记得检查。
修改设备信息结构体:
struct dev_info_struct
{
char* data;
int size;
struct cdev cdev;
}dev_info;
在模块init中分配内存:
dev_info.size=50;
dev_info.data=kmalloc(dev_info.size,GFP_KERNEL);
在模块exit中回收内存:
kfree(dev_info.data);
分配了内存之后,来看下write函数:
ssize_t base_char_write(struct file *fp, const char __user *buf, size_t size, loff_t *ff);
__user表示的是后面的这个指针来自用户空间,内核中不能直接使用
buf是数据指针,size是数据大小,ff是偏移(暂时不管)
用户程序将数据放到buf指向的缓冲区中,然后调用write,驱动只要将buf中的数据复制到设备(内存)中就可以了,但是buf是不能直接使用的,内核提供了这样的函数来使用它:
asm/uaccess.h
unsigned long copy_to_user(void __user *to,const void *from,unsigned long count);
unsigned long copy_from_user(void *to,const void __user *from,unsigned long count);
这两个函数如果成功返回0
加入read和write后的驱动:
base_char.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
dev_t dev;
struct dev_info_struct
{
char* data;
int size;
struct cdev cdev;
}dev_info;
ssize_t base_char_read(struct file *fp, char __user *buf, size_t size, loff_t *ff)
{
struct dev_info_struct *dev_info_ptr=(struct dev_info_struct*)fp->private_data;
printk("base char read!\n");
if(size<0)
return -1;
if(size>dev_info_ptr->size)
size=dev_info_ptr->size;
return copy_to_user(buf,dev_info_ptr->data,size);
}
ssize_t base_char_write(struct file *fp, const char __user *buf, size_t size, loff_t *ff)
{
struct dev_info_struct *dev_info_ptr=(struct dev_info_struct*)fp->private_data;
printk("base char write !\n");
if(size<0)
return -1;
if(size>dev_info_ptr->size)
size=dev_info_ptr->size;
return copy_from_user(dev_info_ptr->data,buf,size);
}
int base_char_open(struct inode *inode, struct file *fp)
{
struct dev_info_struct *dev_info_ptr;
printk("base char open!\n");
dev_info_ptr=container_of(inode->i_cdev,struct dev_info_struct,cdev);
fp->private_data=dev_info_ptr;
return 0;
}
int base_char_release(struct inode *inode, struct file *fp)
{
printk("base char close!\n");
return 0;
}
struct file_operations fop=
{
.owner=THIS_MODULE,
.open=base_char_open,
.write=base_char_write,
.read=base_char_read,
.release=base_char_release
};
static int __init base_char_init(void)
{
if(alloc_chrdev_region(&dev,0,1,"base_char")<0)
{
printk("base_char alloc dev id failed!\n");
goto error0;
}
dev_info.size=50;
dev_info.data=kmalloc(dev_info.size,GFP_KERNEL);
if(dev_info.data==NULL)
{
printk("kmalloc failed!\n");
goto error1;
}
memset(dev_info.data,0,dev_info.size);
cdev_init(&dev_info.cdev,&fop);
dev_info.cdev.owner=THIS_MODULE;
if(cdev_add(&dev_info.cdev,dev,1)<0)
{
printk("base_char cdev add failed!\n");
goto error2;
}
printk("base char register successful!\n");
return 0;
error2:
kfree(dev_info.data);
error1:
unregister_chrdev_region(dev,1);
error0:
return -1;
}
static void __exit base_char_exit(void)
{
kfree(dev_info.data);
cdev_del(&dev_info.cdev);
unregister_chrdev_region(dev,1);
}
module_init(base_char_init);
module_exit(base_char_exit);
MODULE_AUTHOR("crazycode");
MODULE_LICENSE("GPL");
测试代码
test.c
#include
#include
#include
#include
#include
int main()
{
int fd=open("/dev/base_char",O_RDWR);
int count=0;
if(fd<0)
{
perror("");
return -1;
}
char buf[50];
strcpy(buf,"hello world!\n");
write(fd,buf,50);
strcpy(buf,"xxxxxxxxxxxxxxx!\n");
read(fd,buf,50);
printf("read test: %s\n",buf);
close(fd);
return 0;
}