您的位置:首页 > 运维架构 > Linux

Linux设备驱动——字符设备驱动

2015-09-29 10:46 676 查看

介绍

字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED设备等

每一个字符设备或块设备都在/dev目录下对应一个设备文件。linux用户程序通过设备文件(或称设备节点)来使用驱动程序操作字符设备和块设备。

字符设备驱动模型





1、cdev结构体

struct cdev {
struct kobject kobj;   /*内嵌的kobject结构,用于内核设备驱动模型的管理*/

struct module *owner;  /*指向包含该结构的模块的指针,用于引用计数*/

const struct file_operations *ops;   /*指向字符设备操作函数集的指针*/
struct list_head list; /*该结构将使用该驱动的字符设备连接成一个链表*/
/*与cdev对应的字符设备文件的inode->i_devices的链表头*/

dev_t dev;             /*该字符设备的起始设备号,一个设备可能有多个设备号*/
unsigned int count;    /*使用该字符设备驱动的设备数量*/
};


cdev 结构体的dev_t 成员定义了设备号,为32位,其中12位是主设备号,20位是次设备号

我们只需使用二个简单的宏就可以从dev_t 中获取主设备号和次设备号:

MAJOR(dev_t dev)

MINOR(dev_t dev)

相反地,可以通过主次设备号来生成dev_t:

MKDEV(int major,int minor)

2、分配cdev

struct cdev btn_cdev;
/*申请设备号,如果申请失败采用动态申请方式*/
if(major)
{
//静态
dev_id = MKDEV(major, 0);
register_chrdev_region(dev_id, 1, "button");
}
else
{
//动态
alloc_chardev_region(&dev_id, 0, 1, "button");
major = MAJOR(dev_id);
}


静态申请:

int register_chrdev_region(dev_t from, unsigned count, const char *name);
/*功能:申请使用从from开始的count个设备号(主设备号不变,次设备号增加)*/


from :要分配的设备编号范围的初始值(次设备号常设为0);

Count:连续编号范围.

name:编号相关联的设备名称. (/proc/devices);

静态申请相对较简单,但是一旦驱动被广泛使用,这个随机选定的主设备号可能会导致设备号冲突,而使驱动程序无法注册。

动态申请:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);
/*功能:请求内核动态分配count个设备号,且次设备号从baseminor开始。*/
//返回值小于0表示分配失败,系统自动返回没有占用的设备号


baseminor:是请求的最小的次编号;

count:是请求的连续设备编号的总数;

name:为设备名

然后通过major=MMOR(dev)获取主设备号

动态申请简单,易于驱动推广,但是无法在安装驱动前创建设备文件(因为安装前还没有分配到主设备号)。

销毁设备号

void unregister_chrdev_region(dev_t first, unsigned int count);


first为第一个设备号,count为申请的设备数量

3、初始化c_dev

内核在内部使用类型 struct cdev 的结构来代表字符设备.有 2 种方法来分配和初始化一个这些结构.

①如果你想在运行时获得一个独立的 cdev 结构, 你可以为此使用这样的代码:

struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &my_fops;


②另一种是把cdv结构嵌入到你自己封装的设备结构中,这时需要使用下面的方法来分配和初始化:

void cdev_init(struct cdev *cdev, struct file_operations *fops);


cdev_ init()函数用于初始化 cdev 的成员,并建立 cdev 和 file_operations 之间的连接。

cdev_init()操作源代码:

void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj, &ktype_cdev_default);
cdev->ops = fops;
}


4、注册cdev

int cdev_add(struct cdev *dev, dev_t num, unsigned int count)
//向系统添加一个cdev,完成字符设备的注册。


dev 是 cdev 结构, num 是这个设备响应的第一个设备号, count 是应当关联到设备的设备号的数目. 常常 count 是 1。

5、硬件初始化

6、设备操作

file_operations结构

struct file_operations {
struct module *owner;  //THIS_MODULE, <linux/module.h> 中定义的宏.

/*用于修改文件当前的读写位置*/
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 __user *);
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);
};


参考:file_operations中各项解析

file结构,文件结构代表一个打开的文件

struct file{
mode_t fmode; //文件模式,如FMODE_READ,FMODE_WRITE*/

......
loff_t f_pos; //当前读写位置.off_t是一个64位的数,驱动可以读这个值,如果它需要知道文件中的当前位置,但是正常地不应该改变它。

unsigned int f_flags; //文件标志,例如O_RDONLY,O_NONBLOCK,和O_SYNC.驱动应当检查 O_NONBLOCK标志来看是否是请求非阻塞操作

struct file_operations *f_op;

void *private_data; //非常重要,用于存放转换后的设备描述结构指针*/
.......
};


inode 结构

内核用inode 结构在内部表示文件,它是实实在在的表示物理硬件上的某一个文件,且一个文件仅有一个inode与之对应,同样它有二个比较重要的成员:

struct inode{
dev_t i_rdev;   //设备编号

struct cdev *i_cdev;
};
//从inode中获取主次设备号
unsigned int imajor(struct inode *inode);
unsigned int iminor(struct inode *inode);


7、字符设备驱动模块加载与卸载函数

在字符设备驱动模块加载函数中应该实现设备号的申请cdev 结构的注册

卸载函数中应该实现设备号的释放cdev结构的注销

我们一般习惯将cdev内嵌到另外一个设备相关的结构体里面,该设备包含所涉及的cdev、私有数据及信号量等等信息。常见的设备结构体、模块加载函数、模块卸载函数形式如下:

/*设备结构体*/
struct xxx_dev{
struct cdev cdev;

char *data;

struct semaphore sem;
......
};

/*模块加载函数*/
static int __init xxx_init(void)
{
.......

初始化cdev结构;

申请设备号;
注册设备号;
申请分配设备结构体的内存;
/*非必须*/
}

/*模块卸载函数*/
static void __exit xxx_exit(void)
{
.......
释放原先申请的设备号;
释放原先申请的内存;
注销cdev设备;
}


参考:深入浅出:Linux设备驱动之字符设备驱动

实例1

#ifndef MEMDEV_MAJOR
#define MEMDEV_MAJOR 251 /*预设的mem的主设备号*/
#endif

#ifndef MEMDEV_NR_DEVS
#define MEMDEV_NR_DEVS 2 /*设备数*/
#endif

#ifndef MEMDEV_SIZE
#define MEMDEV_SIZE 4096
#endif

/*mem设备描述结构体*/
struct mem_dev
{
char *data;
unsigned long size;
};

static mem_major = MEMDEV_MAJOR;

module_param(mem_major, int, S_IRUGO);

struct mem_dev *gp_mem_dev; /*设备结构体指针*/

struct cdev g_cdev;

/*文件打开函数*/
int mem_open(struct inode *inode, struct file *filp)
{
struct mem_dev *dev;

/*获取次设备号*/
int num = MINOR(inode->i_rdev);

if (num >= MEMDEV_NR_DEVS)
return -ENODEV;
dev = &gp_mem_dev[num];

/*将设备描述结构指针赋值给文件私有数据指针*/
filp->private_data = dev;

return 0;
}

/*文件释放函数*/
int mem_release(struct inode *inode, struct file *filp)
{
return 0;
}

/*读函数*/
static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
unsigned long p = *ppos; /*记录文件指针偏移位置*/
unsigned int count = size; /*记录需要读取的字节数*/
int ret = 0; /*返回值*/
struct mem_dev *dev = filp->private_data; /*获得设备结构体指针*/

/*判断读位置是否有效*/
if (p >= MEMDEV_SIZE) /*要读取的偏移大于设备的内存空间*/
return 0;
if (count > MEMDEV_SIZE - p) /*要读取的字节大于设备的内存空间*/
count = MEMDEV_SIZE - p;

/*读数据到用户空间:内核空间->用户空间交换数据*/
if (copy_to_user(buf, (void*)(dev->data + p), count))
{
ret = - EFAULT;
}
else
{
*ppos += count;
ret = count;

printk(KERN_INFO "read %d bytes(s) from %d\n", count, p);
}

return ret;
}

/*写函数*/
static ssize_t mem_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 mem_dev *dev = filp->private_data; /*获得设备结构体指针*/

/*分析和获取有效的写长度*/
if (p >= MEMDEV_SIZE)
return 0;
if (count > MEMDEV_SIZE - p) /*要写入的字节大于设备的内存空间*/
count = MEMDEV_SIZE - p;

/*从用户空间写入数据*/
if (copy_from_user(dev->data + p, buf, count))
ret = - EFAULT;
else
{
*ppos += count; /*增加偏移位置*/
ret = count; /*返回实际的写入字节数*/

printk(KERN_INFO "written %d bytes(s) from %d\n", count, p);
}

return ret;
}

/* seek文件定位函数 */
static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
{
loff_t newpos;

switch(whence) {
case 0: /* SEEK_SET */ /*相对文件开始位置偏移*/
newpos = offset; /*更新文件指针位置*/
break;

case 1: /* SEEK_CUR */
newpos = filp->f_pos + offset;
break;

case 2: /* SEEK_END */
newpos = MEMDEV_SIZE -1 + offset;
break;

default: /* can't happen */
return -EINVAL;
}
if ((newpos<0) || (newpos>MEMDEV_SIZE))
return -EINVAL;

filp->f_pos = newpos;
return newpos;

}

/*文件操作结构体*/
static const struct file_operations mem_fops =
{
.owner = THIS_MODULE,
.llseek = mem_llseek,
.read = mem_read,
.write = mem_write,
.open = mem_open,
.release = mem_release,
};

/*设备驱动模块加载函数*/
static int memdev_init(void)
{
int result;
int i;

dev_t devno = MKDEV(mem_major, 0);

/* 申请设备号,当xxx_major不为0时,表示静态指定;当为0时,表示动态申请*/
/* 静态申请设备号*/
if(mem_major)
result = register_chrdev_region(devno, 2, "memdev");
else /* 动态分配设备号 */
{
result = alloc_chrdev_region(&devno, 0, 2, "memdev");
mem_major = MAJOR(devno); /*获得申请的主设备号*/
}

if (result < 0)
return result;

/*初始化cdev结构,并传递file_operations结构指针*/
cdev_init(&g_cdev, &mem_fops);
g_cdev.owner = THIS_MODULE; /*指定所属模块*/
g_cdev.ops = &mem_fops;

/*注册字符设备*/
cdev_add(&g_cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);

/*为设备描述结构分配内存*/
gp_mem_dev = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);
if (!gp_mem_dev) /*申请失败*/
{
result = - ENOMEM;
goto fail_malloc;
}
memset(gp_mem_dev, 0, sizeof(struct mem_dev));

/*为设备分配内存*/
for (i=0; i < MEMDEV_NR_DEVS; i++)
{
gp_mem_dev[i].size = MEMDEV_SIZE;
gp_mem_dev[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);
memset(gp_mem_dev[i].data, 0, MEMDEV_SIZE);
}

return 0;

fail_malloc:
unregister_chrdev_region(devno, 1);

return result;
}

/*模块卸载函数*/
static void memdev_exit(void)
{
cdev_del(&g_cdev); /*注销设备*/
kfree(gp_mem_dev); /*释放设备结构体内存*/
unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*释放设备号*/
}

MODULE_AUTHOR("David Xie");
MODULE_LICENSE("GPL");

module_init(memdev_init);
module_exit(memdev_exit);


参考:Linux 内核开发 - 第一个字符驱动程序

Makefile文件如下:

ifneq ($(KERNELRELEASE),)
obj-m := cdevdemo.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif

clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules.order  Module.symvers


实例2

#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 <asm/io.h>
#include <asm/uaccess.h>
#include <linux/timer.h>
#include <asm/atomic.h>
#include <linux/slab.h>
#include <linux/device.h>

#define CDEVDEMO_MAJOR 255  /*预设cdevdemo的主设备号*/

static int cdevdemo_major = CDEVDEMO_MAJOR;

/*设备结构体,此结构体可以封装设备相关的一些信息等
信号量等也可以封装在此结构中,后续的设备模块一般都
应该封装一个这样的结构体,但此结构体中必须包含某些
成员,对于字符设备来说,我们必须包含struct cdev cdev*/
struct cdevdemo_dev
{
struct cdev cdev;
};

struct cdevdemo_dev *cdevdemo_devp; /*设备结构体指针*/

/*文件打开函数,上层对此设备调用open时会执行*/
int cdevdemo_open(struct inode *inode, struct file *filp)
{
printk(KERN_NOTICE "======== cdevdemo_open ");
return 0;
}

/*文件释放,上层对此设备调用close时会执行*/
int cdevdemo_release(struct inode *inode, struct file *filp)
{
printk(KERN_NOTICE "======== cdevdemo_release ");
return 0;
}

/*文件的读操作,上层对此设备调用read时会执行*/
static ssize_t cdevdemo_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
printk(KERN_NOTICE "======== cdevdemo_read ");
}

/* 文件操作结构体,文中已经讲过这个结构*/
static const struct file_operations cdevdemo_fops =
{
.owner = THIS_MODULE,
.open = cdevdemo_open,
.release = cdevdemo_release,
.read = cdevdemo_read,
};

/*初始化并注册cdev*/
static void cdevdemo_setup_cdev(struct cdevdemo_dev *dev, int index)
{
printk(KERN_NOTICE "======== cdevdemo_setup_cdev 1");
int err, devno = MKDEV(cdevdemo_major, index);
printk(KERN_NOTICE "======== cdevdemo_setup_cdev 2");

/*初始化一个字符设备,设备所支持的操作在cdevdemo_fops中*/
cdev_init(&dev->cdev, &cdevdemo_fops);
printk(KERN_NOTICE "======== cdevdemo_setup_cdev 3");
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &cdevdemo_fops;
printk(KERN_NOTICE "======== cdevdemo_setup_cdev 4");
err = cdev_add(&dev->cdev, devno, 1);
printk(KERN_NOTICE "======== cdevdemo_setup_cdev 5");
if(err)
{
printk(KERN_NOTICE "Error %d add cdevdemo %d", err, index);
}
}

int cdevdemo_init(void)
{
printk(KERN_NOTICE "======== cdevdemo_init ");
int ret;
dev_t devno = MKDEV(cdevdemo_major, 0);

struct class *cdevdemo_class;
/*申请设备号,如果申请失败采用动态申请方式*/
if(cdevdemo_major)
{
printk(KERN_NOTICE "======== cdevdemo_init 1");
ret = register_chrdev_region(devno, 1, "cdevdemo");
}else
{
printk(KERN_NOTICE "======== cdevdemo_init 2");
ret = alloc_chrdev_region(&devno,0,1,"cdevdemo");
cdevdemo_major = MAJOR(devno);
}
if(ret < 0)
{
printk(KERN_NOTICE "======== cdevdemo_init 3");
return ret;
}
/*动态申请设备结构体内存*/
cdevdemo_devp = kmalloc(sizeof(struct cdevdemo_dev), GFP_KERNEL);
if(!cdevdemo_devp)  /*申请失败*/
{
ret = -ENOMEM;
printk(KERN_NOTICE "Error add cdevdemo");
goto fail_malloc;
}

memset(cdevdemo_devp,0,sizeof(struct cdevdemo_dev));
printk(KERN_NOTICE "======== cdevdemo_init 3");
cdevdemo_setup_cdev(cdevdemo_devp, 0);

/*下面两行是创建了一个总线类型,会在/sys/class下生成cdevdemo目录
这里的还有一个主要作用是执行device_create后会在/dev/下自动生成
cdevdemo设备节点。而如果不调用此函数,如果想通过设备节点访问设备
需要手动mknod来创建设备节点后再访问。*/
cdevdemo_class = class_create(THIS_MODULE, "cdevdemo");
device_create(cdevdemo_class, NULL, MKDEV(cdevdemo_major, 0), NULL, "cdevdemo");

printk(KERN_NOTICE "======== cdevdemo_init 4");
return 0;

fail_malloc:
unregister_chrdev_region(devno,1);
}

void cdevdemo_exit(void)    /*模块卸载*/
{
printk(KERN_NOTICE "End cdevdemo");
cdev_del(&cdevdemo_devp->cdev);  /*注销cdev*/
kfree(cdevdemo_devp);       /*释放设备结构体内存*/
unregister_chrdev_region(MKDEV(cdevdemo_major,0),1);    //释放设备号
}

MODULE_LICENSE("Dual BSD/GPL");
module_param(cdevdemo_major, int, S_IRUGO);
module_init(cdevdemo_init);
module_exit(cdevdemo_exit);


参考:linux设备驱动–字符设备模型
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: