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

Linux内核分析(五)

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

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

1.      字符设备驱动基础

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

3.      驱动测试

字符设备基础

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/可看到下图,其中左边红框为主设备号,右边为次设备号

2.        何为主设备号:用来对应该设备为何种类型设备。(比如串口我们用一个数字识别,而串口有好几个)

3.        何为次设备号:用来对应同一类型设备下的具体设备。(用次设备号来具体区分是哪个串口)

4.        设备号相关操作:

1.        通过主设备号和次设备号获取devdev = MKDEV(主,次);

2.        通过dev获取主设备号:主 = MAJOR(dev);

3.        通过dev获取次设备号:dev = MINOR(dev);

5.        设备号分配:设备号的分配有两种方式,一种是静态的,另一种是动态的,下面一一分析

1.        静态分配:也就是程序员自己指定设备号,通过register_chrdev_region();函数向内核申请,可能会导致和内核已有的冲突,从而失败。

2.        动态分配:通过 alloc_chrdev_region(); 函数向内核申请设备号。

3.        释放设备号:通过 unregister_chrdev_region(); 释放申请到的设备号。

3.       file_operations操作函数集

file_operations结构体中的成员函数在我们驱动开发过程中极为重要,其中的内容相当庞大,下面我们看看其定义:

 struct file_operations {
struct module *owner;/*拥有该结构的模块的指针,一般为THIS_MODULES*/
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 (*readdir) (struct file *, void *, filldir_t); /*只用于读取目录,对于设备文件该字段为NULL*/
unsigned int (*poll) (struct file *, struct poll_table_struct *);/*轮询函数,判断目前是否可以进行非阻塞的读取或写入*/
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); /* 不用BLK的文件系统,将使用此函数代替ioctl*/
long (*compat_ioctl) (struct file *, unsigned int, unsigned long); /* 代替ioctl*/
int (*mmap) (struct file *, struct vm_area_struct *);/*用于请求将设备内存映射到进程地址空间*/
int (*open) (struct inode *, struct file *);/*打开*/
int (*flush) (struct file *, fl_owner_t id); /*在进程关闭它的设备文件描述符的拷贝时调用; 它应当执行(并且等待)设备的任何未完成的操作. */
int (*release) (struct inode *, struct file *);/*关闭*/
int (*fsync) (struct file *, int datasync); /*刷新待处理数据*/
int (*aio_fsync) (struct kiocb *, int datasync); /*异步fsync*/
int (*fasync) (int, struct file *, int); /*通知设备FASYNC标志发生变化*/
int (*lock) (struct file *, int, struct file_lock *);/* 实现文件加锁*/
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); /*通常为NULL*/
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); /*在当前的进程地址空间找的一个未映射的内存段*/
int (*check_flags)(int); /*法允许模块检查传递给 fnctl(F_SETFL...) 调用的标志*/
int (*flock) (struct file *, int, struct file_lock *);/**/
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); /*由VFS调用,将管道数据粘贴到文件*/
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); /*由VFS调用,将文件数据粘贴到管道*/
int (*setlease)(struct file *, long, struct file_lock **);/**/
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len); /**/
};

上面结构体中的函数指针所指向的函数,在我们在进行open、write、read等系统调用的时候最终会被调用到,所以我们的驱动中想为应用层实现那种调用就要在此实现。

4.       字符设备驱动初始化

我们通过上面的分析对设备号和操作函数集有了一定的了解下面我们来看字符设备驱动初始化,其主要步骤如下。

1.        分配cdev结构:有静态(直接定义)动态(cdev_alloc();)两种方式

2.        初始化cdev结构:使用 cdev_init(struct cdev *cdev, const struct file_operations *fops) 初始化

3.        驱动注册:使用 int cdev_add(struct cdev *p, dev_t dev, unsigned count)//count为该种类型的设备个数注册

4.        硬件初始化:阅读芯片手册进行硬件设备的初始化

5.        完成操作函数集:实现要用的操作(设备方法)

6.        驱动注销:使用 void cdev_del(struct cdev *p) 注销

5.       字符设备驱动模型及调用关系

下面我通过一张图将字符设备的驱动结构、以及字符设备驱动与用户空间的调用关系进行展示:

6.       遗漏知识

我们内核空间和用户空间的数据交互要用到下面两个函数:

 copy_from_user();//从用户空间读
copy_to_user();//写入用户空间 

简单字符设备驱动实现

经过上面的分析我们对字符设备有一定了解,下面我们来完成一个最简单的字符设备驱动。我只展示最主要的代码,整个项目工程在https://github.com/wrjvszq/myblongs.git欢迎大家关注。

1.       字符设备驱动编写

因为驱动本身就是一个内核模块,下面的字符设备驱动只实现了部分方法,在后面的博客中我们会基于此驱动慢慢修改,希望大家掌握。

 #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 == ){/*判断为那个设备*/
filp -> private_data = my_dev.mem;/*将设备结构体指针复制给文件私有数据指针*/
}
return ;
}
/*文件关闭函数*/
int mem_release(struct inode *inode, struct file *filp){
return ;
} 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 = ; if(p >= MEM_SIZE)/*合法性判断*/
return ;
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 = ;
int *pbase = filp -> private_data; if(p >= MEM_SIZE)
return ;
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)- + offset;/*从文件尾开始定位*/
break;
default:
return -EINVAL;
} if ((newpos<) || (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 = -; /*动态分配设备号*/
ret = alloc_chrdev_region(&my_dev.devno,,,"memdev");
if (ret >= ){
cdev_init(&my_dev.cdev,&mem_ops);/*初始化字符设备*/
cdev_add(&my_dev.cdev,my_dev.devno,);/*添加字符设备*/
} return ret;
} static void memdev_exit(void){
cdev_del(&my_dev.cdev);
unregister_chrdev_region(my_dev.devno,); } module_init(memdev_init);
module_exit(memdev_exit);

驱动测试

经过上面的代码我们已经实现了一个简单的字符设备驱动,我们下面进行测试。(应用程序在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

4.       编译应用程序并测试

使用gcc对应用程序进行编译,然后先使用write对设备进行写入,在使用read对设备读取,完成测试。

Linux内核分析(五)----字符设备驱动实现的更多相关文章

  1. Linux应用程序访问字符设备驱动详细过程【转】

    本文转载自:http://blog.csdn.net/coding__madman/article/details/51346532 下面先通过一个编写好的内核驱动模块来体验以下字符设备驱动 可以暂时 ...

  2. LCD驱动分析(一)字符设备驱动框架分析

    参考:S3C2440 LCD驱动(FrameBuffer)实例开发<一>   S3C2440 LCD驱动(FrameBuffer)实例开发<二> LCD驱动也是字符设备驱动,也 ...

  3. linux字符设备驱动--基本知识介绍

    一.设备驱动的分类 1.字符设备 字符设备是指那些能一个字节一个字节读取数据的设备,如LED灯.键盘.鼠标等.字符设备一般需要在驱动层实现open().close().read().write().i ...

  4. Linux内核分析(六)----字符设备控制方法实现|揭秘系统调用本质

    原文:Linux内核分析(六)----字符设备控制方法实现|揭秘系统调用本质 Linux内核分析(六) 昨天我们对字符设备进行了初步的了解,并且实现了简单的字符设备驱动,今天我们继续对字符设备的某些方 ...

  5. linux字符设备驱动中内核如何调用驱动入口函数 一点记录

    /* 内核如何调用驱动入口函数 ? *//* 答: 使用module_init()函数,module_init()函数定义一个结构体,这个结构体里面有一个函数指针,指向first_drv_init() ...

  6. 深入理解Linux字符设备驱动

    文章从上层应用访问字符设备驱动开始,一步步地深入分析Linux字符设备的软件层次.组成框架和交互.如何编写驱动.设备文件的创建和mdev原理,对Linux字符设备驱动有全面的讲解.本文整合之前发表的& ...

  7. 【转】linux设备驱动程序之简单字符设备驱动

    原文网址:http://www.cnblogs.com/geneil/archive/2011/12/03/2272869.html 一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用 ...

  8. 【转】深入浅出:Linux设备驱动之字符设备驱动

    深入浅出:Linux设备驱动之字符设备驱动 一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据 ...

  9. 【Linux驱动】字符设备驱动

    一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 1.字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据.字符设备是面 ...

随机推荐

  1. Ubuntu 如何重新安裝 Unity ?

    阿舍在刪除 Qt 的時候下錯指令,結果,就把 Unity 給移除掉了,雖然,阿舍從此就知道 Unity 和 Qt 有不分離的關係,不過,就沒有Unity可以用了,這樣阿舍要試東西就不是很方便了哩 ! ...

  2. win7 64bit+vs2010 操作注册表

    注册表五个根键 HKEY_CLASSES_ROOT--管理文件系统  HKEY_LOCAL_MACHINE--管理当前系统硬件配置  HKEY_LOCAL_USER--管理系统当前用户配置  HKEY ...

  3. 清除Android工程中没用到的资源(转)

    项目需求一改再改,UI一调再调,结果就是项目中一堆已经用不到但却没有清理的垃圾资源,不说工程大小问题,对新进入项目的人或看其他模块的代码的人来说,这些没清理的资源可能也可能会带来困扰,所以最好还是清理 ...

  4. 怎么样Eclipse IDE for C/C++ Developers正确编译GTK规划?(解决)

    <span style="color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 25.99 ...

  5. C#读书

    C#读书雷达   大家都知道,ThoughtWorks的技术雷达每年都会发布两到三次,它不但是业界技术趋势的标杆,更提供了一种卓有成效的方法论,即打造自己的技术雷达.在这种思想的驱动下,我们诞生了自己 ...

  6. 栈实现java

    栈是一种“先去后出”的抽象的数据结构.例如:我们在洗盘子的时候,洗完一个盘子,将其放在一摞盘子的最上面,但我们全部洗完后,要是有盘子时,我们会先从最上面的盘子开始使用,这种例子就像栈的数据结构一样,先 ...

  7. [转载] RaspberryPi B+ WiringPi 引脚对应图

    Pin Numbering - Raspberry Pi Model B+ Numbering Scheme Expansion Header J8 Pinout (40-pin Header) Ad ...

  8. Dom操作高级应用

    table tBodies,tHead,tFoot,rows,cells 一个table有多个tbody oTab.tBodies[0].rows[i].style.background = &quo ...

  9. C# WinForm多线程(三)Control.Invoke

    下面我们就把在Windows Form软件中使用Invoke时的多线程要注意的问题给大家做一个介绍. 首先,什么样的操作需要考虑使用多线程?总的一条就是,负责与用户交互的线程(以下简称为UI线程)应该 ...

  10. java ClassLoader static

    package init; class Person { private static Person person = new Person(); public static int count2 = ...