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

Linux内核分析(五)----字符设备驱动实现

2015-02-04 14:09 567 查看
Linux内核分析(五)

昨天我们对linux内核的子系统进行简单的认识,今天我们正式进入驱动的开发,我们今后的学习为了避免大家没有硬件的缺陷,我们都会以虚拟的设备为例进行学习,所以大家不必害怕没有硬件的问题。

今天我们会分析到以下内容:

1. 字符设备驱动基础

2. 简单字符设备驱动实现

3. 驱动测试

l 字符设备基础

1. 字符设备描述结构

在linux2.6内核中,使用cdev结构体描述一个字符设备,其定义如下:

struct cdev {
struct kobject kobj;/*基于kobject*/
struct module *owner; /*所属模块*/
const struct file_operations *ops; /*设备文件操作函数集*/
struct list_head list;
dev_t dev; /*设备号*/
unsigned int count; /*该种类型设备数目*/
};


上面结构中需要我们进行初始化的有ops和dev,下面我们会对这两个成员进行分析。

注:kobject结构是驱动中很重要的一个结构,由于其复杂性,我们现在不进行介绍,后面会详细介绍。

2. 设备号

1. 何为设备号:cdev结构体中dev成员定义了设备号,而dev_t则为U32类型的也就是32位,其中12位为主设备号,20位为次设备号。我们执行ls –l /dev/可看到下图,其中左边红框为主设备号,右边为次设备号

#include<linux/module.h>
#include<linux/init.h>
#include<linux/cdev.h>
#include<linux/fs.h>
#include<asm/uaccess.h>

#define MEM_SIZE 1024

MODULE_LICENSE("GPL");

struct mem_dev{
struct cdev cdev;
int mem[MEM_SIZE];//全局内存4k
dev_t devno;
};

struct mem_dev my_dev;

/*打开设备*/
int mem_open(struct inode *inode, struct file *filp){
int num = MINOR(inode->i_rdev);/*获取次设备号*/

if(num == 0){/*判断为那个设备*/
filp -> private_data = my_dev.mem;/*将设备结构体指针复制给文件私有数据指针*/
}
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){
int * pbase = filp -> private_data;/*获取数据地址*/
unsigned long p = *ppos;/*读的偏移*/
unsigned int count = size;/*读数据的大小*/
int ret = 0;

if(p >= MEM_SIZE)/*合法性判断*/
return 0;
if(count > MEM_SIZE - p)/*读取大小修正*/
count = MEM_SIZE - p;

if(copy_to_user(buf,pbase + p,size)){
ret = - EFAULT;
}else{
*ppos += count;
ret = count;
}

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;
int *pbase = filp -> private_data;

if(p >= MEM_SIZE)
return 0;
if(count > MEM_SIZE - p)
count = MEM_SIZE - p;

if(copy_from_user(pbase + p,buf,count)){
ret = - EFAULT;
}else{
*ppos += count;
ret = count;
}
return ret;
}

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

loff_t newpos;

switch(whence) {
case SEEK_SET:/*从文件头开始定位*/
newpos = offset;
break;
case SEEK_CUR:/*从当前位置开始定位*/
newpos = filp->f_pos + offset;
break;
case SEEK_END:
newpos = MEM_SIZE * sizeof(int)-1 + offset;/*从文件尾开始定位*/
break;
default:
return -EINVAL;
}

if ((newpos<0) || (newpos>MEM_SIZE * sizeof(int)))/*检查文件指针移动后位置是否正确*/
return -EINVAL;

filp->f_pos = newpos;
return newpos;

}

const struct file_operations mem_ops = {
.llseek = mem_llseek,
.open = mem_open,
.read = mem_read,
.write = mem_write,
.release = mem_release,
};

static int memdev_init(void){
int ret = -1;

/*动态分配设备号*/
ret = alloc_chrdev_region(&my_dev.devno,0,1,"memdev");
if (ret >= 0){
cdev_init(&my_dev.cdev,&mem_ops);/*初始化字符设备*/
cdev_add(&my_dev.cdev,my_dev.devno,1);/*添加字符设备*/
}

return ret;
}

static void memdev_exit(void){
cdev_del(&my_dev.cdev);
unregister_chrdev_region(my_dev.devno,1);

}

module_init(memdev_init);
module_exit(memdev_exit);


View Code
l 驱动测试

经过上面的代码我们已经实现了一个简单的字符设备驱动,我们下面进行测试。(应用程序在https://github.com/wrjvszq/myblongs.git 上)

1. 加载内核模块

我们使用 insmod memdev.ko 命令加载内核模块

2. 获取设备号

我们的设备号是动态申请到的,所以我们要通过下面的命令查看设备号

cat /proc/devices

找到我们的设备memdev的设备号



3. 建立设备文件

使用如下命令建立设备文件

mknod /dev/文件名 c 主设备号次设备号

上面命令中文件名为我们在应用程序中打开的文件名

c代表字符设备

主设备号为上一步找到的,我的位249

次设备号非负即可,但不能超过自己所创建的设备数。

比如我的就是 mknod /dev/memdev0 c 249 0

4. 编译应用程序并测试

使用gcc对应用程序进行编译,然后先使用write对设备进行写入,在使用read对设备读取,完成测试。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: