内存模拟设备进行驱动编程

设备驱动
在Linux的设备驱动编程中,都需要遵从一种内核编程的模式,这也可以理解为一种设计模式(Design pattern)【1】,这种模式让硬件programmer脱离硬件平台,开始和操作系统打交道,就像业务和逻辑分离一样,提高了代码的可复用性。

但是这样依然没有说清楚到底Linux用户是如何使用驱动程序的,Linux系统上有三类设备:

字符驱动设备(character device)
块设备(block device)
网络设备(network device)
它们(网络设备除外)的访问都是用户通过设备文件(或者叫设备节点)来进行访问而使用的。

内存如何模拟设备
下面来分析一个简单的字符设备驱动,实现在一段内存中模拟字符设备的读和写操作
首先定义一个结构体来描述一段内存的具体信息。因为外设的访问是操作系统通过物理地址进行访问的,因而我们可以把这段内存看作是一个字符设备。

view sourceprint?1 struct mem_dev                                      

2 {                                                         

3 char *data;                  

4 unsigned long size;   

5 };

data是指向内存起始地址的指针,size是内存的大小。因此把这样一个封装可以看作一个设备。

字符设备的统一描述
在Linux2.6的内核中,字符设备使用结构体struct cdev进行描述。它定义在/include/linux/cdev.h中:

 

在这个结构体中会发现另外一个嵌套的结构体struct file_operations,它是字符设备把驱动的操作和设备联系在一起的纽带。是一个函数指针的集合,每个被打开的文件都对应于一系列的操作。它定义在/include/linux/fs.h中:

 

设备的注册
字符设备经过描述之后,相当于规定了它的资源,逻辑和实现。下一步就是在内核中进行注册,相当于在体制中给一个固定的编制。设备的注册分为三部分:

分配cdev
初始化cdev
添加cdev
设备可以通过静态和动态分配两种方式进行分配。下面是实现代码:

view sourceprint?1 dev_t devno = MKDEV(mem_major, 0);//MKDEV是将主设备号和次设备号转换为dev_t类型数据 

2 /* 静态申请设备号*/

3 if (mem_major) 

4 result = register_chrdev_region(devno, 2, "memdev");//devno为主设备号,共申请两个连续的设备 

5 else  /* 动态分配设备号 */

6 { 

7 result = alloc_chrdev_region(&devno, 0, 2, "memdev");//&devno作为一个输出参数 

8 mem_major = MAJOR(devno);//获取动态分配到的主设备号。 

9 }

mem_major是在宏定义中自己选的一个设备号,定为254,所以下一步else不会执行。但当自己选定的设备号已经被使用时就会产生冲突。这时就可以使用动态分配的方式获得设备号。

设备号分配之后就是对cdev进行初始化:

view sourceprint?1 cdev_init(&cdev, &mem_fops); 

2  cdev.owner = THIS_MODULE;//驱动引用计数,作用是这个驱动正在使用的时候,你再次用inmod命令时,出现警告提示 

3  cdev.ops = &mem_fops;

cdev_init(&cdev, &mem_fops)是初始化cdev结构,将结构体cdev和mem_fops绑定起来。cdev.ops = &mem_fops是对cdev结构体成员进行赋值。其中mem_fops是用file_operations定义描述的文件操作结构体:

view sourceprint?1 static const struct file_operations mem_fops = 

2 { 

3 .owner = THIS_MODULE, 

4 .llseek = mem_llseek, 

5 .read = mem_read, 

6 .write = mem_write, 

7 .open = mem_open, 

8 .release = mem_release, 

9 };

最后是注册字符设备:

view sourceprint?1 cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);//MEMDEV_NR_DEVS=2,分配2个设备

这样三步就完成了字符设备的注册。

再看一下mem_read和mem_write的实现过程:

view sourceprint?01 /*读函数*/

02 static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)//buf缓存区 

03 { 

04 unsigned long p =  *ppos;//p为当前读写位置 

05 unsigned int count = size;//一次读取的大小 

06 int ret = 0; 

07 struct mem_dev *dev = filp->private_data; /*获得设备结构体指针*/

08 /*判断读位置是否有效*/

09 if (p >= MEMDEV_SIZE)//是否超出读取获围 

10 return 0; 

11 if (count > MEMDEV_SIZE - p) 

12 count = MEMDEV_SIZE - p;//count大于可读取的范围,则缩小读取范围。 

13 /*读数据到用户空间*/

14 if (copy_to_user(buf, (void*)(dev->data + p), count))//返回buf,读取位置,读取数量 

15 { 

16 ret =  - EFAULT; 

17 } 

18 else

19 { 

20 *ppos += count;//将文件当前位置向后移 

21  ret = count;//返回实际读取字节数 

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

23  } 

24  return ret;//返回实际读取字节数,判断读取是否成功 

25  } 

26   

27   

28  /*写函数*/

29  static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)//write和read类似,直接参考read 

30 { 

31 unsigned long p =  *ppos; 

32 unsigned int count = size; 

33 int ret = 0; 

34 struct mem_dev *dev = filp->private_data; /*获得设备结构体指针*/

35 /*分析和获取有效的写长度*/

36 if (p >= MEMDEV_SIZE) 

37 return 0; 

38 if (count > MEMDEV_SIZE - p) 

39 count = MEMDEV_SIZE - p; 

40 /*从用户空间写入数据*/

41 if (copy_from_user(dev->data + p, buf, count)) 

42 ret =  - EFAULT; 

43 else

44 { 

45 *ppos += count; 

46 ret = count; 

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

48 } 

49 return ret; 

50 }

下面是字符设备的测试程序:

view sourceprint?01 #include <stdio.h> 

02 int main() 

03 { 

04  FILE *fp0 = NULL; 

05  char Buf[4096]; 

06   /*初始化内核空间的Buf*/

07  strcpy(Buf,"Mem is char dev!"); 

08  printf("BUF: %s\n",Buf); 

09   

10  /*打开设备文件*/

11  fp0 = fopen("/dev/memdev0","r+"); 

12  if (fp0 == NULL) 

13  { 

14   printf("Open Memdev0 Error!\n"); 

15   return -1; 

16  } 

17   

18  /*写入设备:用户空间——>字符设备(即那一段内存中)*/

19  fwrite(Buf, sizeof(Buf), 1, fp0);//这个fwrite和.read = mem_read的关系? 

20   

21  /*重新定位文件位置(思考没有该指令,会有何后果)  【2】*/

22  fseek(fp0,0,SEEK_SET);//调用mem_llseek()定位 

23   

24  /*清除Buf*/

25  strcpy(Buf,"Buf is NULL!"); 

26  printf("BUF: %s\n",Buf); 

27   

28   /*读出设备字符设备(即那一段内存中)——>用户空间*/

29  fread(Buf, sizeof(Buf), 1, fp0); 

30   /*检测结果*/

31  printf("BUF: %s\n",Buf); 

32   return 0;  

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值