- Linux设备驱动开发学习笔记
- 内核版本:2.6.x
- Major and Minor Numbers
- 内核通过major号来识别设备,下面的命令列出的是系统上所连接的设备及其major number,第一列就是设备的major number.
- $ cat /proc/devices
- Character devices:
- 1 mem
- 4 /dev/vc/0
- 4 tty
- 5 /dev/tty
- 5 /dev/console
- 5 /dev/ptmx
- 7 vcs
- 10 misc
- 13 input
- 14 sound
- 116 alsa
- 128 ptm
- 136 pts
- 180 usb
- 195 nvidia
- 226 drm
- 254 devfs
- Block devices:
- 3 ide0
- 22 ide1
- $ls -l /dev
- ..............
- crw------- 1 root root 119, 0 Apr 24 11:34 vmnet0
- crw------- 1 root root 119, 1 Apr 24 11:34 vmnet1
- crw------- 1 root root 119, 2 Apr 24 11:34 vmnet2
- crw------- 1 root root 119, 3 Apr 24 11:34 vmnet3
- crw------- 1 root root 119, 4 Apr 24 11:34 vmnet4
- crw------- 1 root root 119, 5 Apr 24 11:34 vmnet5
- crw------- 1 root root 119, 6 Apr 24 11:34 vmnet6
- crw------- 1 root root 119, 7 Apr 24 11:34 vmnet7
- crw------- 1 root root 119, 8 Apr 24 11:34 vmnet8
- ..............
- 可以看到他们的major number 是119,但他们的minor number不同.分别是0~8.
- 内核只关心major number,而minor number 是由设备驱动来区别的.
- 内核内部,类型dev_t存储着设备号,且定义了一组宏来维护它.
- MKDEV(int major,int minor);//return dev_t
- MAJOR( dev_t dev);
- MINOR (dev_t dev);
- 比如,我们用mknod建立一个新的设备文件
- #mknod /dev/newchr c 50 0
- 建立/dev/newchr设备文件,类型是c(char,字符型),major number 是50,minor number 是0.mknod的用法可以用man来查看.
- 在内核内部,我们用上面的宏来维护:
- dev_t mydev;
- mydev=MKDEV(50,0);
- 我们也可以由mydev得到major 和minor number.
- int major,minor;
- major=MAJOR(mydev);
- minor=MINOR(mydev);
- 注册设备号
- 我们定义好major和minor number 后就可以在内核中注册一个设备了.注册一个字符设备需要用到下面几个函数:
- int register_chrdev_region(dev_t first,unsigned int count,
- char *name);
- first是你要注册的设备号范围的开始(其中minor号一般设置为0),count是你所申请的连续设备号的总数.name是设备的名称.它会在/proc/devices中出现.
- int alloc_chrdev_region(dev_t *dev,unsigned int firstminor,
- unsigned int count,char *name);
- 这个函数是用来动态分配设备号的.有余开发者不知道所要用的major号是多少,便让内核动态分配一个.参数dev是个output-only参数.
- void unregister_chrdev_region(dev_t first,unsigned int count);
- 一般在模块清除函数中调用.
- 重要的数据结构
- 正如你所想象的,注册只是第一步,后面还有更重要的部分,其中一个就是我们一开始提到的如何实现open/read/write/ioctl等用户接口.
- 首先看一下几个重要的数据结构:
- file_operations,file,inode
- <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);
- }
- file_operations结构定义了一组函数指针,每一个打开的文件(用struct file表示)和他自己的一组函数(包含在一个叫f_op的域中,它指向一个struct file_operations结构)相联系.这些操作都是来实现系统调用的,所以才被命名为open,read,等等.对于那些不需要的功能(比如你的设备不需要write功能,即不需要向设备写数据),可以给write指针付NULL.
- 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;
- };
- 每个打开的文件对应一个struct file.它在被打开时由内核创建,并传给它所有可以操作该文件的函数,到文件被关闭时才被删除.
- The inode Structure.
- Inode结构是用来在内核内部表示文件的.同一个文件可以被打开好多次,所以可以对应很多struct file,但是只对应一个struct inode.该结构里面包含了很多信息,但是,驱动开发者只关心里面两个重要的域:
- dev_t i_rdev;//含有真正的设备号
- struct cdev *i_cdev;//struct cdev是内核内部表示字符设备的结构.
- 注册字符设备
- <linux/cdev.h>
- 可以有两种方法注册
- struct cdev *my_cdev=cdev_alloc();
- my_cdev->ops=&my_fops;
- 或
- void cdev_init(struct cdev *cdev,struct file_operations *fops);
- 注册完后还要通知内核一声,通过调用
- int cdev_add(struct cdev *dev,dev_t num,unsigned int count);
- count 一般是 1.
- 删除字符设备,调用
- void cdev_del(struct cdev *dev);
- 说了那么多,现在可以来个例子了.
- #include <linux/kernel.h>
- #include <linux/fs.h>
- #include <linux/module.h>
- #include <asm/uaccess.h>
- #include <linux/cdev.h>
- #include <asm/uaccess.h>
- #define DP_MAJOR 50
- #define DP_MINOR 0
- static int char_read(struct file *filp,char __user *buffer,size_t,loff_t *);
- static int char_open(struct inode *,struct file *);
- static int char_write(struct file *filp,const char __user *buffer,size_t ,loff_t*);
- static int char_release(struct inode *,struct file *);
- static char *arr,*p;
- static int chropen;
- struct cdev *my_cdev;
- static int len;
- struct file_operations Fops = {
- .read = char_read,
- .write = char_write,
- .open = char_open,
- .release = char_release, /* a.k.a. close */
- };
- static int __init char_init(void)
- {
- printk(KERN_ALERT"Initing......\n");
- dev_t dev;
- dev=MKDEV(DP_MAJOR,DP_MINOR);
- my_cdev = cdev_alloc( );
- arr=kmalloc(1024,GFP_KERNEL);
- if(arr==NULL){
- printk(KERN_ALERT"kmalloc error\n");
- }
- sprintf(arr,"Hello,Pid=%d\n",current->pid);
- if(my_cdev==NULL){
- return -1;
- }
- if(register_chrdev_region(dev,10,"dpchr")<0){
- printk(KERN_ALERT"Register char dev error\n");
- return -1;
- }
- chropen=0;
- len=0;
- my_cdev->ops = &Fops;
- cdev_init(my_cdev,&Fops);
- cdev_add(my_cdev,dev,1);
- return 0;
- }
- static int char_open(struct inode *inode,struct file *file)
- {
- if(chropen==0)
- chropen++;
- else{
- printk(KERN_ALERT"Another process open the char device\n");
- return -1;
- }
- p=arr;
- try_module_get(THIS_MODULE);
- return 0;
- }
- static int char_release(struct inode *inode,struct file *file)
- {
- chropen--;
- module_put(THIS_MODULE);
- return 0;
- }
- static int char_read(struct file *filp,char __user *buffer,size_t length,loff_t *offset)
- {
- int i=0;
- if(*p=='\0')
- return 0;
- while(length&&*p){
- put_user(*(p++),buffer++);
- length--;
- i++;
- }
- return i;
- }
- static int char_write(struct file *filp,const char __user *buffer,size_t length,loff_t *offset)
- {
- int i;
- for(i=0;i<length&&i<1024;i++)
- get_user(p[i],buffer+i);
- p[i]=0;
- len=i;
- return i;
- }
- static void module_close()
- {
- len=0;
- printk(KERN_ALERT"Unloading..........\n");
- kfree(arr);
- unregister_chrdev_region(MKDEV(DP_MAJOR,DP_MINOR),10);
- cdev_del(my_cdev);
- }
- module_init(char_init);
- module_exit(module_close);
- 需要注意的是,用户调用read/write时返回的值便是我们实现的函数(char_read,char_write)返回的值,所以我们不能随便的返回一个值(比如,0,用户的read/write返回0,所以会认为出错了,然后并没有出错,只是我们返回了一个错误的值而已.
- 参考资料
- 内核模块的编程http://www.tldp.org/LDP/lkmpg/2.6/html/index.html
- Linux Device Driver 3rd Edition
- Linux Kernel Development 2nd Edition
linux驱动学习
最新推荐文章于 2024-01-01 16:24:31 发布