【Linux驱动】字符设备驱动
2015-12-04 12:41
411 查看
,本文假定读者具备一定linux基础以及对linux驱动基础有所了解。
globalmem.c
关键词:register_chrdev_region()、alloc_chrdev_region()、unregister_chrdev_region()、cdev_init()、cdev_add()、cdev_del()
下面是Linux kernel 3.14的源码中的file_operations结构体(include/linux/fs.h),由于有时候内核版本的升级会给接口带来一定的变化,所以在写驱动时,应该同时具有该版本的源码,以编写出对应内核接口的函数。所以对于上面的ioctl函数,自行参考修改一下即可编译通过。
Makefile
用户态程序test.c
运行测试程序
上面一个简单的case,只是大致的概述下linux字符设备驱动的编写流程,但这也是字符驱动的一个最初雏形,类似于单片机的最小系统之类。
在linux内核中,使用cdev结构体描述一个字符设备
关于设备号请具体参考任何一本linux字符驱动书籍。
cdev结构体中一个重要成员file_operations定义了字符设备驱动提供给虚拟文件系统的接口函数(file_operations请参考前面的博文文件系统系列)。而下层的具体实现,则根据用户及驱动编写。
linux内核提供了一组函数用于操作cdev结构体:
分配和释放设备号
在调用cdev_add()函数向系统注册字符设备之前,应首先调用 register_chrdev_region() 或 alloc_chrdev_region()函数向系统申请设备号
其中register_chrdev_region函数用于已知起始设备的设备号的情况,而alloc_chrdev_region函数用于设备号未知,向系统动态申请未被占用的设备号的情况,函数调用成功以后,会把得到的设备放入第一个参数dev中。
相反的,在调用cdev_del函数从系统注销字符设备之后,unregiter_chrdev_region()应该用以释放原先申请的设备号:
我们可以使用下列宏从dev_t获得主设备号和次设备号:
使用下列宏(也是我们通常使用的)则可以通过主设备号和次设备号生成dev_t
➜ ~ cat /proc/version Linux version 3.13.0-43-generic (buildd@akateko) (gcc version 4.8.2 (Ubuntu 4.8.2-19ubuntu1) ) #72-Ubuntu SMP Mon Dec 8 19:35:44 UTC 2014
globalmem.c
关键词:register_chrdev_region()、alloc_chrdev_region()、unregister_chrdev_region()、cdev_init()、cdev_add()、cdev_del()
#include <linux/module.h> #include <linux/types.h> #include <linux/fs.h> #include <linux/errno.h> #include <linux/mm.h> #include <linux/sched.h> #include <linux/init.h> #include <linux/cdev.h> #include <linux/slab.h> MODULE_LICENSE("Dual BSD/GPL"); #define GLOBALMEM_SIZE 0X1000 #define MEM_CLEAR 0x1 #define GLOBALMEM_MAJOR 240 static int globalmem_major = GLOBALMEM_MAJOR; //globalmem设备结构体 struct globalmem_dev{ struct cdev cdev; unsigned char mem[GLOBALMEM_SIZE];//全局内存 }; struct globalmem_dev *globalmem_devp;//设备结构指针 //文件打开函数 int globalmem_open(struct inode *inode, struct file *filp) { //将设备结构体指针赋值给文件私有数据指针 filp->private_data = globalmem_devp; printk(KERN_ALERT "globalmem open\n"); return 0; } //文件释放函数 int globalmem_release(struct inode *inode, struct file *filp) { printk(KERN_ALERT "globalmem release\n"); return 0; } //ioctl设备控制函数,linux kernel高版本中ioctl已经发生了改变,用了其余两个替换 /*static int globalmem_ioctl(struct inode *inodep, struct file *filp, unsigned int cmd, unsigned long arg) { struct globalmem_dev *dev = filp->private_data;//获得设备结构体指针 switch(cmd) { case MEM_CLEAR://清空全局内存 memset(dev->mem, 0, GLOBALMEM_SIZE); printk(KERN_INFO "globalmem is set to zero\n"); break; default: return -EINVAL; } return 0; }*/ //读函数 static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { unsigned long p = *ppos; int ret = 0; struct globalmem_dev *dev = filp->private_data; if(p >= GLOBALMEM_SIZE) return 0; if(count > GLOBALMEM_SIZE - p) count = GLOBALMEM_SIZE -p; if(copy_to_user(buf, (void*)(dev->mem +p), count)) ret = -EFAULT; else { *ppos += count; ret = count; // printk(KERN_INFO "read %d byte(s) from %d", count, p); } return ret; } //写函数 static ssize_t globalmem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos) { unsigned long p = *ppos; unsigned int count = size; int ret = 0; struct globalmem_dev *dev = filp->private_data; if(p >= GLOBALMEM_SIZE) return 0; if(count > GLOBALMEM_SIZE - p) count = GLOBALMEM_SIZE - p; if(copy_from_user(dev->mem+p,buf,count)) ret = -EFAULT; else { *ppos += count; ret = count; // printk(KERN_INFO "written %d byte(s) form %d\n", count, p); } return ret; } //seek文件定位函数 static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig) { loff_t ret = 0; switch(orig) { case 0: if(offset < 0) { ret = -EINVAL; break; } if((unsigned int)offset > GLOBALMEM_SIZE) { ret = -EINVAL; break; } filp->f_pos = (unsigned int)offset; ret = filp->f_pos; break; case 1: if((filp->f_pos + offset) > GLOBALMEM_SIZE) { ret = -EINVAL;; break; } if((filp->f_pos + offset) < 0) { ret = -EINVAL; break; } filp->f_pos += offset; ret = filp->f_pos; break; default: ret = -EINVAL; break; } return ret; } //文件操作结构体 static const struct file_operations globalmem_fops = { .owner = THIS_MODULE, .llseek = globalmem_llseek, .read = globalmem_read, .write = globalmem_write, // .ioctl = globalmem_ioctl,//Linux kernel高版本中ioctl已被两个XX_ioctl替代,参见下面注释 .open = globalmem_open, .release = globalmem_release, }; //初始化并注册cdev static void globalmem_setup_cdev(struct globalmem_dev *dev, int index) { int err,devno = MKDEV(globalmem_major, index); cdev_init(&dev->cdev, &globalmem_fops); dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &globalmem_fops; err = cdev_add(&dev->cdev, devno, 1); if(err) printk(KERN_NOTICE "Error %d adding globalmem %d", err, index); } //设备驱动模块加载函数 int globalmem_init(void) { int result; dev_t devno = MKDEV(globalmem_major, 0); //申请设备号 if(globalmem_major) result = register_chrdev_region(devno,1,"globalmem"); else//动态申请设备号 { result = alloc_chrdev_region(&devno,0,1,"globalmem"); globalmem_major = MAJOR(devno); } if(result < 0) return result; printk(KERN_ALERT "T1\n"); //动态申请设备结构体的内存 globalmem_devp = (struct globalmem_dev *)kmalloc(sizeof(struct globalmem_dev),GFP_KERNEL); printk(KERN_ALERT "T0\n"); if(!globalmem_devp) { result = -ENOMEM; goto fail_malloc; } memset(globalmem_devp, 0, sizeof(struct globalmem_dev)); globalmem_setup_cdev(globalmem_devp,0); return 0; fail_malloc: unregister_chrdev_region(devno,1); return result; } //模块卸载函数 void globalmem_exit(void) { cdev_del(&globalmem_devp->cdev); kfree(globalmem_devp); unregister_chrdev_region(MKDEV(globalmem_major,0),1); } module_param(globalmem_major, int,S_IRUGO); module_init(globalmem_init); module_exit(globalmem_exit);
下面是Linux kernel 3.14的源码中的file_operations结构体(include/linux/fs.h),由于有时候内核版本的升级会给接口带来一定的变化,所以在写驱动时,应该同时具有该版本的源码,以编写出对应内核接口的函数。所以对于上面的ioctl函数,自行参考修改一下即可编译通过。
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 (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); int (*iterate) (struct file *, struct dir_context *); unsigned int (*poll) (struct file *, struct poll_table_struct *); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); ...... }
Makefile
ifneq ($(KERNELRELEASE),) obj-m := globalmem.o else KERNELDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) all: $(MAKE) -C $(KERNELDIR) SUBDIRS=$(PWD) modules endif clean: rm -f *.o *.ko *.mod.c .globalmem*
用户态程序test.c
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main(int argc, char *argv[]) { int fd; fd = open("/dev/globalmem", O_RDWR); if(fd < 0) { perror("open"); } return EXIT_SUCCESS; }
运行测试程序
root@selfimpr-pc:/home/selfimpr/wenqian/lkp/globalmem#insmod globalmem.ko root@selfimpr-pc:/home/selfimpr/wenqian/lkp/globalmem# cat /proc/devices | grep "globalmem" 240 globalmem root@selfimpr-pc:/home/selfimpr/wenqian/lkp/globalmem# mknod /dev/globalmem c 240 0 root@selfimpr-pc:/home/selfimpr/wenqian/lkp/globalmem# ll /dev/globalmem crw-r--r-- 1 root root 240, 0 12月 2 19:47 /dev/globalmem root@selfimpr-pc:/home/selfimpr/wenqian/lkp/globalmem#./test root@selfimpr-pc:/home/selfimpr/wenqian/lkp/globalmem#dmesg [210299.896657] T1 [210299.896665] T0 [210393.405124] globalmem open [210393.405201] globalmem release
上面一个简单的case,只是大致的概述下linux字符设备驱动的编写流程,但这也是字符驱动的一个最初雏形,类似于单片机的最小系统之类。
在linux内核中,使用cdev结构体描述一个字符设备
struct cdev { struct kobject kobj;//内嵌的kobject对象 struct module *owner;//所属模块 struct file_operations *ops;//文件操作结构体 struct list_head list; dev_t dev;//设备号,长度为32位,其中高12为主设备号,低20位为此设备号 unsigned int count; };
关于设备号请具体参考任何一本linux字符驱动书籍。
cdev结构体中一个重要成员file_operations定义了字符设备驱动提供给虚拟文件系统的接口函数(file_operations请参考前面的博文文件系统系列)。而下层的具体实现,则根据用户及驱动编写。
linux内核提供了一组函数用于操作cdev结构体:
void cdev_init(struct cdev *, struct file_operations *);//初始化cdev成员 struct cdev *cdev_alloc(void);//动态申请一个cdev内存 int cdev_add(struct cdev *, dev_t, unsigned);//向系统添加一个cdev void cdev_del(struct cdev *);//向系统删除一个cdev
分配和释放设备号
在调用cdev_add()函数向系统注册字符设备之前,应首先调用 register_chrdev_region() 或 alloc_chrdev_region()函数向系统申请设备号
int register_chrdev_region(dev_t first, unsigned count, const char *name); int alloc_chrdev_region(dev_t *dev, unsigned firstminor, unsigned int count, const char *name);
其中register_chrdev_region函数用于已知起始设备的设备号的情况,而alloc_chrdev_region函数用于设备号未知,向系统动态申请未被占用的设备号的情况,函数调用成功以后,会把得到的设备放入第一个参数dev中。
相反的,在调用cdev_del函数从系统注销字符设备之后,unregiter_chrdev_region()应该用以释放原先申请的设备号:
void unregister_chrdev_region(dev_t first, unsigned count);
我们可以使用下列宏从dev_t获得主设备号和次设备号:
MAJOR(dev_t dev) MINOR(dev_t dev)
使用下列宏(也是我们通常使用的)则可以通过主设备号和次设备号生成dev_t
MKDEV(int major, int minor)
相关文章推荐
- OMAP3630 Linux I2C总线驱动分析
- Linux设备驱动开发环境的搭建
- 迅为4412开发板Linux驱动教程/硬件知识及原理图的使用
- 设备控制接口(ioctl 函数) 主要是在驱动中
- Broadcom NetXtreme II BCM5706/5708/5709/5716 Driver 驱动问题处理办法
- NAPI
- 嵌入式linux和嵌入式android系统有什么区别和联系?
- struct file结构体
- Linux设备驱动之简单字符设备驱动开开发
- Fedora 20 上安装基于dell 1420的无线网卡驱动
- 记得感激我 评论我 nvidia显卡驱动linux系统地安装 完全驱动方法 世界第一人
- pwm驱动程序及其注释
- pwm驱动程序及其注释
- 在使用SIS M672+SIS 968芯片组,SIS Mirage 3+集成显卡的电脑上安装Debian 7.7的驱动
- Bus--device--driver驱动模型源码分析
- linux驱动---DMA操作---驱动编写
- linux驱动---DMA操作---寄存器分析
- linux驱动
- Linux驱动程序开发 - 字符设备驱动