简单字符设备驱动程序

linux驱动程序开发步骤

1)查看原理图、数据手册,了解设备的操作方法。

2)在内核中找到相近的驱动程序,以它为模板进行开发,有时候需要从零开始。

3)实现驱动程序的初始化:比如向内核注册驱动程序,这样应用程序传入文件名时,内核才能找到相应的驱动程序。

4)设计所要实现的操作,比如open,read,write,close。

5)实现中断服务(不是必需的)

6)编译该驱动程序到中,或者用insmod命令加载。

7)测试驱动程序。

 驱动框架

应用程序API接口read,open,write 是对应驱动程序上的led_read,led_open,led_write的

1)写出驱动函数led_read,led_open,led_write

每个系统调用,驱动程序怎样和它对应起来??

2)定义一个file_operations结构,和应用层API对接,

Linux系统怎么知道去调用哪个驱动程序的file_operations??

3)根据设备类型和主设备号

例如应用层程序open("dev/led");根据打开的文件属性中的根据设备类型和主设备号,找到内核中注册了的file_operations结构来判断是哪个驱动程序。

所以,要将注册函数将主设备号和file_operations一起注册到内核

4)最后把驱动程序加载到内核

如module_init(memdev_init);

加载时,就调用驱动初始化函数,向内核注册

|--------------------------------------------------------------------|

|           APP:open("dev/xxx");write();read()                 |

|              文件属性:设备类型   c  主设备号  11           |

|--------------------------------------------------------------------|

|                                       C库                                             |

|--------------------------------------------------------------------|

|                  VFS                                  内核                       |

|                   chrdev数组 0,1,2...11...                              |

|                   找到对应的 file_operations                      |

|--------------------------------------------------------------------|

|                    1)led_read();led_write();led_open();      |

|驱动程序:2)定义了file_operations  关联APP函数  |

|                    3)chardev_region()注册到内核                |

|--------------------------------------------------------------------|

注意:  chardev_region()是2.4的接口, struct cdev是2.6的接口

一、重要知识点

1. 主次设备号

dev_t

dev_t是内核中用来表示设备编号的数据类型;

int MAJOR(dev_t dev)

int MINOR(dev_t dev)

这两个宏抽取主次设备号。

dev­_t MKDEV(unsigned int major, unsigned int minor)

这个宏由主/次设备号构造一个dev_t结构。

2. 分配和释放设备号

静态申请设备号

int register_chardev_region(dev_t first,unsigned int count, char *name)  //分配成功时,返回0,错误时,返回一个负的错误码

参数:

first 要注册的第一个设备号

count 要注册的设备号个数

name 设备名

Int alloc_chardev_region(dev_t *dev,unsigned int firstminor, unsigned int count, char *name)

//分配成功时,返回0,错误时,返回一个负的错误码

//alloc_chardev_region函数调用成功后,会把得到的设备号放入dev中

参数:

dev 分配到的设备号

firstminor 起始次设备号

count、name和静态一样

动态申请设备号,注意第一个参数是传地址,而静态则是传值。

     3. 几种重要的数据结构

struct file

file结构代表一个打开的文件,它由内核在open时创建,并传递给该文件上进行操作的所有函数,直到最后的close函数。

file结构private_data是跨系统调用时保存状态信息非常有用的资源。

file结构的f_ops 保存了文件的当前读写位置。

struct inode

内核用inode代表一个磁盘上的文件,它和file结构不同,后者表示打开的文件描述符。对于单个文件,可能会有许多个表示打开文件的文件描述符file结构,但他们都指单个inode结构。inode的dev_t i_rdev成员包含了真正的设备编号,struct cdev *i_cdev包含了指向struct cdev结构的指针。

struct file_operations

file_operations结构保存了字符设备驱动程序的方法。它是一个在 <linux/fs.h> 中定义的 struct file_operations 结构,这是一个内核结构,不会出现在用户空间的程序中,它定义了常见文件 I/O 函数的入口。系统调用函数通过内核,最终调用对应的 struct file_operations 结构的接口函数(例如,open() 文件操作是通过调用对应文件的file_operations 结构的 open 函数接口而被实现)。当然,每个设备的驱动程序不一定要实现其中所有的函数操作,若不需要定义实现时,则只需将其设为 NULL 即可。

   4. 字符设备的注册和注销

struct cdev *cdev_alloc(void);//分配

void cdev_init(struct cdev *dev, structfile_operations *fops);//初始化

int cdev_add(struct cdev *dev, dev_t num,unsigned int count);//添加

void cdev_del(struct cdev *dev);//移除

用来管理cdev结构的函数,内核中使用该结构表示字符设备。注意cdev_add函数的count参数为次设备的个数,要想拥有多个次设备,就必须将该参数设为次设备的个数。

     5. 并发处理

信号量和自旋锁的区别,使用信号量时当调用进程试图获得一个锁定了的锁时会导致进程睡眠,而自旋锁则是一直循法的等待一直到该锁解锁了为止。

1)信号量

DECLARE_MUTEX(name);

DECLARE_MUTEX_LOCKED(name);

声明和初始化用在互斥模式中的信号量的两个宏

void init_MUTEX(struct semaphore *sem)

void init_MUTEX_LOCKER(struct semaphore*sem);

这两个函数可以在运行时初始化信号量

void down(struct semaphore *sem);

int down_interruptible(struct semaphore*sem);

int down_trylock(struct semahpore *sem);

void up(struct semaphore *sem);

锁定和解锁信号量。如果必要,down会将调用进程置于不可中断的休眠状态;相反,down_interruptible可被信号中断。down_trylock不会休眠,并且会在信号量不可用时立即返回。锁定信号量的代码最后必须使用up解锁该信号量。

2)自旋锁

spionlock_t lock = SPIN_LOCK_UNLOCKED;

spin_lock_init(spinlock_t *lock);

初始化自旋锁的两种方式。

voidspin_lock(spinlock_t *lock);

锁定自旋锁

voidspin_unlock(spinlock_t *lock);

解锁自旋锁

6. 驱动程序的加载和卸载 

在2.6的内核中,模块的扩展名为.ko

使用insmod命令加载,当使用时,模块的初始化函数被调用,它用来向内核注册驱动程序

rmmod卸载

lsmod查看内核中已经加载了哪些模块

module_init(memdev_init);

module_exit(memdev_exit);

注意:模块有多种,例如文件系统也可以编译为模块,不一定只是驱动程序。

  1. 二、驱动代码
  2. #include <linux/module.h>
  3. #include <linux/types.h>
  4. #include <linux/fs.h>
  5. #include <linux/errno.h>
  6. #include <linux/mm.h>
  7. #include <linux/sched.h>
  8. #include <linux/init.h>
  9. #include <linux/cdev.h>
  10. #include <asm/io.h>
  11. #include <asm/system.h>
  12. #include <asm/uaccess.h>
  13. #define MEMDEV_MAJOR 251   /*预设的mem的主设备号*/
  14. #define MEMDEV_NUM 2  /*设备数*/
  15. #define MEMDEV_SIZE 1024  //分配的内存大小
  16. struct mem_dev
  17. {
  18. unsignedint size;
  19. char*data;
  20. structsemaphore sem;
  21. };
  22. static int mem_major = MEMDEV_MAJOR;  /*预设的mem的主设备号*/
  23. struct cdev mem_cdev;
  24. struct mem_dev *mem_devp; /*设备结构体指针*/
  25. /*文件打开函数*/
  26. static int mem_open(struct inode *inode,struct file *filp)
  27. {
  28. struct mem_dev *dev;
  29. unsignedint num;
  30. printk("mem_open.\n");
  31. num= MINOR(inode->i_rdev);//获得次设备号
  32. if(num> (MEMDEV_NUM -1))          //检查次设备号有效性
  33. return-ENODEV;
  34. dev= &mem_devp[num];
  35. filp->private_data= dev; //将设备结构保存为私有数据
  36. return0;
  37. }
  38. static int mem_release(struct inode *inode,struct file *filp)
  39. {
  40. printk("mem_release.\n");
  41. return0;
  42. }
  43. static ssize_t mem_read(struct file *filp,char __user *buf, size_t size, loff_t *ppos)
  44. {
  45. intret = 0;
  46. structmem_dev *dev;
  47. unsignedlong p;
  48. unsignedlong count;
  49. printk("mem_read.\n");
  50. dev= filp->private_data;//获得设备结构
  51. count= size;
  52. p= *ppos;
  53. //检查偏移量和数据大小的有效性
  54. if(p> MEMDEV_SIZE)
  55. return0;
  56. if(count> (MEMDEV_SIZE-p))
  57. count= MEMDEV_SIZE - p;
  58. if(down_interruptible(&dev->sem))//锁定互斥信号量
  59. return -ERESTARTSYS;
  60. //读取数据到用户空间
  61. if(copy_to_user(buf,dev->data+p, count)){
  62. ret= -EFAULT;
  63. printk("copyfrom user failed\n");
  64. }
  65. else{
  66. *ppos+= count;
  67. ret= count;
  68. printk("read%d bytes from dev\n", count);
  69. }
  70. up(&dev->sem);//解锁互斥信号量
  71. returnret;
  72. }
  73. static ssize_t mem_write(struct file *filp,const char __user *buf, size_t size, loff_t *ppos)//注意:第二个参数和read方法不同
  74. {
  75. intret = 0;
  76. structmem_dev *dev;
  77. unsignedlong p;
  78. unsignedlong count;
  79. printk("mem_write.\n");
  80. dev= filp->private_data;
  81. count= size;
  82. p= *ppos;
  83. if(p> MEMDEV_SIZE)
  84. return0;
  85. if(count> (MEMDEV_SIZE-p))
  86. count= MEMDEV_SIZE - p;
  87. if(down_interruptible(&dev->sem))//锁定互斥信号量
  88. return-ERESTARTSYS;
  89. if(copy_from_user(dev->data+p,buf, count)){
  90. ret= -EFAULT;
  91. printk("copyfrom user failed\n");
  92. }
  93. else{
  94. *ppos+= count;
  95. ret= count;
  96. printk("write%d bytes to dev\n", count);
  97. }
  98. up(&dev->sem);//解锁互斥信号量
  99. returnret;
  100. }
  101. static loff_t mem_llseek(struct file *filp,loff_t offset, int whence)
  102. {
  103. intnewpos;
  104. printk("mem_llseek.\n");
  105. switch(whence)
  106. {
  107. case0:
  108. newpos= offset;
  109. break;
  110. case1:
  111. newpos= filp->f_pos + offset;
  112. break;
  113. case2:
  114. newpos= MEMDEV_SIZE - 1 + offset;
  115. break;
  116. default:
  117. return-EINVAL;
  118. }
  119. if((newpos<0)|| (newpos>(MEMDEV_SIZE - 1)))
  120. return-EINVAL;
  121. filp->f_pos= newpos;
  122. returnnewpos;
  123. }
  124. /*文件操作结构体*/
  125. static const struct file_operations mem_fops = {
  126. .owner= THIS_MODULE,
  127. .open= mem_open,
  128. .write= mem_write,
  129. .read= mem_read,
  130. .release= mem_release,
  131. .llseek= mem_llseek,
  132. };
  133. /*设备驱动模块加载函数*/
  134. static int __init memdev_init(void)
  135. {
  136. int result;
  137. int err;
  138. int i;
  139. //申请设备号
  140. dev_t devno = MKDEV(mem_major, 0);
  141. if(mem_major)
  142. result= register_chrdev_region(devno, MEMDEV_NUM, "memdev");//注意静态申请的dev_t参数和动态dev_t参数的区别
  143. else{        //静态直接传变量,动态传变量指针
  144. result= alloc_chrdev_region(&devno, 0, MEMDEV_NUM, "memdev");
  145. mem_major= MAJOR(devno);
  146. }
  147. if(result< 0){
  148. printk("can‘tget major devno:%d\n", mem_major);
  149. returnresult;
  150. }
  151. //注册设备驱动
  152. cdev_init(&mem_cdev,&mem_fops);  /*初始化cdev结构*/
  153. mem_cdev.owner= THIS_MODULE;
  154. /* 注册字符设备 */
  155. err= cdev_add(&mem_cdev, MKDEV(mem_major, 0), MEMDEV_NUM);//如果有N个设备就要添加N个设备号
  156. if(err)
  157. printk("addcdev faild,err is %d\n", err);
  158. //分配设备内存
  159. mem_devp= kmalloc(MEMDEV_NUM*(sizeof(struct mem_dev)), GFP_KERNEL);
  160. if(!mem_devp){
  161. result = - ENOMEM;
  162. goto fail_malloc;
  163. }
  164. memset(mem_devp,0, MEMDEV_NUM*(sizeof(struct mem_dev)));
  165. for(i=0;i<MEMDEV_NUM; i++){
  166. mem_devp[i].size= MEMDEV_SIZE;
  167. mem_devp[i].data= kmalloc(MEMDEV_SIZE, GFP_KERNEL);
  168. memset(mem_devp[i].data,0, MEMDEV_SIZE);
  169. init_MUTEX(&mem_devp[i].sem);//初始化互斥锁
  170. }
  171. returnresult;
  172. fail_malloc:
  173. unregister_chrdev_region(MKDEV(mem_major,0), MEMDEV_NUM);
  174. returnresult;
  175. }
  176. static void memdev_exit(void)
  177. {
  178. cdev_del(&mem_cdev);
  179. unregister_chrdev_region(MKDEV(mem_major,0), MEMDEV_NUM);//注意释放的设备号个数一定要和申请的设备号个数保存一致
  180. //否则会导致设备号资源流失
  181. printk("memdev_exit\n");
  182. }
  183. module_init(memdev_init);
  184. module_exit(memdev_exit);
  185. MODULE_AUTHOR("Y-Kee");
  186. MODULE_LICENSE("GPL");

三、疑点难点

 

1.

__init

__initdata

__exit

__exitdata

仅用于模块初始化或清除阶段的函数(__init和__exit)和数据(__initdata和__exitdata)标记。标记为初始化项目会在初始化结束后丢弃;而退出在内核未被配置为可卸载模块的情况下被简单的丢弃。被标记为__exit的函数只能在模块卸载或者系统关闭时被调用,其他任何用法都是错误的。内核通过对应的目标对象放置在可执行文件的特殊ELF段中而让这些标记起作用。

2.static

初始化函数应该被声明为static,因为这种函数在特定文件之外没有其他意义。因为一个模块函数要对内核其他部分课件,则必须显示导出,因此这并不是什么强制性规则。

3. struct module *owner

内核使用这个字段以避免在模块操作正在使用时卸载该模块。几乎在所有的情况下,该成员都会被初始化为THIS_MODULE。它是定义在<linux/module.h>中的一个宏。

4 __user

我们会注意到许多参数包括含有__user字串,它其实是一种形式的文档而已,表面指针是一个用户指针,因此不能被直接用。对通常的编译来讲,__user没有任何效果,但是可由外部检查软件使用,用来寻找对用户空间地址错误使用。

from:http://blog.csdn.net/jianchi88/article/details/6797057

时间: 2024-10-02 11:25:30

简单字符设备驱动程序的相关文章

Linux 简单字符设备驱动程序 (自顶向下)

第零章:扯扯淡 特此总结一下写的一个简单字符设备驱动程序的过程,我要强调一下“自顶向下”这个介绍方法,因为我觉得这样更容易让没有接触过设备驱动程序的童鞋更容易理解,“自顶向下”最初从<计算机网络 自顶向下方法>这本书学到的,我觉得有时候这是一种很好的方式. 第一章:测试程序 咦?你怎么跟别人的思路不一样???自顶向下嘛,我就直接从测试程序来说啦,这样那个不是更熟悉吗?看看下面的测试程序的代码,是不是很熟悉? 1 #include <stdio.h> 2 #include <u

0915-----Linux设备驱动 学习笔记----------一个简单的字符设备驱动程序

0.前言 研究生生活一切都在步入正轨,我也开始了新的学习,因为实在不想搞存储,所以就决定跟师兄学习设备驱动,看了两星期书,终于有点头绪了,开始记录吧! 1.准备工作 a)查看内核版本 uname -r b)安装内核源码树(http://www.cnblogs.com/Jezze/archive/2011/12/23/2299871.html) 在www.linux.org上下载源码编译,这里是.xz格式,需要安装解压工具,xz-utils: 解压方法示例:xz -d linux-3.1-rc4.

20150216简单的Linux字符设备驱动程序

20150216简单的Linux字符设备驱动程序 2015-02-16 李海沿 关于字符设备驱动程序详细的知识点,本文就不再介绍了,很多同志,看了知识点,还是一头雾水,写不出来,所以,本文从实战出发,带领各位同胞们来实现一个字符设备驱动程序,改程序可作为字符设备的通用模板. 好了废话不多说,先上驱动程序,在驱动程序中加入详细注释: 1 /****************************** 2 linux 字符设备驱动程序 3 *****************************/

linux 驱动学习(一)简单的字符设备驱动程序

linux 系统将设备分为三种类型:字符设备.块设备和网络接口设备. 文章将先给出字符设备驱动程序,参照程序记录知识点,可能会不全,以后会慢慢加 .知识点记录完成后,会贴出字符设备驱动程序的测试程序并记录测试过程. 注释版 1 #include "linux/kernel.h" //内核头文件,含有一些内核常用函数的原形定义 2 #include "linux/module.h" //包含大量加载模块需要的函数和符号的定义 3 #include "linu

led driver 0--一个简单但完整的字符设备驱动程序

#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <asm/uaccess.h> #include <asm/irq.h> #include <asm/io.h> #include <asm/a

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

原文网址:http://www.cnblogs.com/geneil/archive/2011/12/03/2272869.html 一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 1.字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据.字符设备是面向流的设备,常见的字符设备有鼠标.键盘.串口.控制台和LED设备等.2.块设备:是指可以从设备的任意位置读取一定长度数据的设备.块设备包括硬盘.磁盘.U盘和SD卡等.

LINUX设备驱动程序笔记(三)字符设备驱动程序

      <一>.主设备号和次设备号        对字符设备的访问时通过文件系统内的设备名称进行的.那些设备名称简单称之为文件系统树的节点,它们通常位于/dev目录.字符设备驱动程序的设备文件可通过ls -l命令输出的第一列中的'c'来识别.块设备同样位于/dev下,由字符'b'标识 crw-rw----  1 root root    253,   0 2013-09-11 20:33 usbmon0 crw-rw----  1 root root    253,   1 2013-09

Linux嵌入式驱动学习之路(二十一)字符设备驱动程序总结和块设备驱动程序的引入

字符设备驱动程序 应用程序是调用C库中的open read write等函数.而为了操作硬件,所以引入了驱动模块. 构建一个简单的驱动,有一下步骤. 1. 创建file_operations 2. 申请设备号 3. 注册字符设备驱动, 4. 驱动入口 5. 驱动出口 检查数据是否到来的方式: 1. 查询方式 2. 休眠唤醒方式 如果设备出现异常而无法唤醒时,则将永远处于休眠状态. 3. poll机制 如果没有被唤醒,则在一定时间内可自己唤醒. 4. 异步通知(信号) 而以上的几种方式通用性不高,

【驱动】——字符设备驱动程序

字符设备不得不说的那些事: 一: 设备号:主设备号,次设备号: 数据类型 dev_t(unsigned int) 定义设备号  高12位主设备号 低20位次设备号: 二: 设备号的作用: 应用程序通过主设备号找到驱动程序: 三:如何分配设备号: ①:静态分配: 1: cat /proc/devices 查看linux系统哪个设备号没有被占用: 2: dev_t dev_id = MKDEV(主设备号,次设备号)  根据你的设备个数分配次设备号 如果设备个数只有一个,一般此设备号从0开始: 3: