linux驱动程序函数之字符驱动程序的设计

文章目录

    • linux驱动程序的分类
    • 驱动程序的安装
    • linux驱动程序的使用及设备号等
    • 设备号的分配
    • 创建设备文件(2种方法)
    • 3种重要的数据结构
      • 关于struct file_operations 中函数的一些常用方法操作:
    • 设备注册
    • 设备注销
    • 简单字符设备的代码实现(上边的函数都有涉及)
      • (1)头文件memdev.h
      • (2)项目文件memdev.c
      • Makefile
    • 应用程序的编写(用于测试)
    • 设备实现方法(基于mini6410)
    • 结尾

linux驱动程序的分类

  1. 字符设备驱动
  2. 网络接口驱动
  3. 块设备驱动

Linux系统将设备分为三种类型,每个模块通常实现为其中的某一类:字符模块块模块网络模块,这三种类型如下:

  1. 字符设备
    字符(char)设备是个能够像字节流(类似文件)一样被访问的设备,由字符设备驱动程序来实现这种特性。字符设备驱动程序至少要实现open、close、read和write系统调用。字符终端(/dev/console)和串口(/dev/ttys0以及类似设备)就是两个字符设备,因为它们很好地展现了流的抽象。字符设备通过文件系统结点来存取, 例如 /dev/tty1 和 /dev/lp0. 在一个字符设备和一个普通文件之间唯一有关的不同就是, 你经常可以在普通文件中移来移去, 但是大部分字符设备仅仅是数据通道, 你只能顺序存取.然而, 存在看起来象数据区的字符设备, 你可以在里面移来移去. 例如, frame grabber 经常这样, 应用程序可以使用 mmap 或者 lseek 存取整个要求的图像.
  2. **块设备如同字符设备, 块设备通过位于 /dev 目录的文件系统结点来存取. 一个块设备(例如一个磁盘)应该是可以驻有一个文件系统的. 在大部分的 Unix 系统, 一个块设备只能处理这样的 I/O 操作, 传送一个或多个长度经常是 512 字节( 或一个更大的 2 的幂的数 )的整块. Linux, 相反, 允许应用程序读写一个块设备象一个字符设备一样 – 它允许一次传送任意数目的字节. 结果就是, 块和字符设备的区别仅仅在内核在内部管理数据的方式上, 并且因此在内核/驱动的软件接口上不同.如同一个字符设备, 每个块设备都通过一个文件系统结点被存取的, 它们之间的区别对用户是透明的. 块驱动和字符驱动相比, 与内核的接口完全不同.
  3. 网络接口
    任何网络事务都通过一个接口来进行, 就是说, 一个能够与其他主机交换数据的设备. 通常, 一个接口是一个硬件设备, 但是它也可能是一个纯粹的软件设备, 比如环回接口. 一个网络接口负责发送和接收数据报文, 在内核网络子系统的驱动下,不必知道单个事务是如何映射到实际的被发送的报文上的. 很多网络连接( 特别那些使用 TCP 的)是面向流的, 但是网络设备却常常设计成处理报文的发送和接收.一个网络驱动对单个连接一无所知; 它只处理报文.

字符设备块设备区别

  • 字符设备:以字节为最小单位来访问的设备
  • 块设备:传送整块数据(512字节或2的n次幂字节)
    区别:linux中允许块设备传送任意数目字节,区别仅仅是驱动与内核接口函数不同

驱动程序的安装

两个方法:

  1. 模块方式(module)
    insmod 直接安装到正在运行的内核中
  2. 直接编译进内核:修改Kconfig 和 Makefile
    先修改Kconfig,修改方式(以hello.c为例):
    (1)进入linux内核所在目录,进入 /drivers/char 中,用vim打开Kconfig(配置菜单的起源):
		zhangbin@zhangbin-virtual-machine:~/mini6410/linux-2.6.38/$: cd drivers/char/
		zhangbin@zhangbin-virtual-machine:~/mini6410/linux-2.6.38/drivers/char$ :vi Kconfig

进行如下配置(红圈处)
linux驱动程序函数之字符驱动程序的设计_第1张图片
(2)进行内核配置的选择

     zhangbin@zhangbin-virtual-machine:~/mini6410/linux-2.6.38/$  make menuconfig ARCH=arm

配置如下(光标处)
linux驱动程序函数之字符驱动程序的设计_第2张图片
(3)在 drivers/char/ 中 的Makefile 进行如下配置(红线处)
linux驱动程序函数之字符驱动程序的设计_第3张图片
(4)最后进行内核的编译和运行

		zhangbin@zhangbin-virtual-machine:~/mini6410/linux-2.6.38/$  make uImage ARCH=arm CROSS_COMPILE=arm-linux-

linux驱动程序的使用及设备号等

驱动程序的使用 :linux 用户程序通过设备文件来使用驱动程序操作字符设备和块设备。

在linux内核中描述设备号dev_t 结构 为unsigned int 32位整数,高12位为主设备号,低20位为次设备号

  • 主设备号:连接设备文件个设备驱动
    从 dev_t 中分解出主设备号:MAJOR(dev_t dev)
  • 次设备号:区分操作的是哪一个设备
    从 dev_t 中分解出次设备号:MINOR(dev_t dev)

设备号的分配

linux内核给设备分配主设备号,可以采用 静态申请和动态分配

  1. 静态申请(优点:简单,缺点:容易冲突)
    (1)根据linux内核的 /documentation/dervices.txt 确定一个没有使用的主设备号
    (2)使用 register_chrdev_region 函数注册设备号
    *int register_chrdev_region(dev_t from,unsigned count,const char name)
    功能:申请使用从from 开始的count个设备号(主设备号不变,次设备号增加)
    参数:
    from :希望申请使用的设备号
    count:希望申请的设备号数目
    name:设备名(体现在/proc/devices)
  2. 动态分配(优点:简单、易推广,缺点:无法安装驱动前创建设备文件),解决方法:安装驱动后,从/proc/devices中查询设备号
    方法:使用alloc_chrdev_region分配设备号
    int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name)
    功能:请求内核动态分配count个设备号,且次设备号从baseminor开始
    参数:
    dev:分配到的设备号
    baseminor:起始次设备号
    count:需要分配的设备号数目
    name:设备名

**注销设备号:**void unregister_chrdev_region(dev_t from,unsigned count)
功能:释放从from 开始的count个设备号

创建设备文件(2种方法)

  1. 手动创建(mknod)
    用法:mknod filename type major minor
    参数
    filename:设备文件名
    type:设备文件类型
    major:主设备号
    minor:次设备号
    举例:mknod seriaI0 c 100 0
  2. 自动创建(mdev)
    相对于devfs,udev(mdev)存在于应用层。利用udev(mdev)来实现设备文件的自动创建很简单。在驱动初始化代码中调用 class_create 为设备创建一个 class ,再为每个设备调用device_create()创建对应的设备。
    举例:
   struct class *myclass = class_create(THIS_MODULE,"my_device_driver");
   device_create(myclass,NULL,MKDEV(major_num,0),NULL,"my_device");

当驱动被加载时,udev(mdev)就会自动在/dev/下创建 my_device 设备文件。

前提:要配置busybox支持 种(udev)。

3种重要的数据结构

  1. struct file
    代表一个打开的文件。系统中每个打开的文件在内核空间都有一个关联的 struct file。它由内核在打开文件时创建,在文件关闭后释放。
    重要成员:
    loff_t f_pos //文件读写位置
    *struct file_operations f_op
  2. struct inode
    用来记录文件的物理上的信息。因此它和代表打开文件的file结构是不同的。一个文件可以对应多个file结构,但只有一个inode结构。
    重要成员:
    dev_t i_rdev:设备号
  3. struct file_operations
    一个函数指针的集合,定义能在设备上进行的操作。结构中的成员指向驱动中的函数,这些函数实现一个特别的操作,对于不支持的操作保留NULL。
    举例:
    struct file_operations mem_fops=
    {
			.owner = THIS_MODULE,
			.llseek = mem_seek,
			.read = mem_read,
			.write = mem_write,
			.unlocked_ioctl = mem_ioctl,  //2.6.10内核后把.ioctl改为.unlocked_ioctl了
			.open = mem_open,
			.release = mem_release,
	};

关于struct file_operations 中函数的一些常用方法操作:

  1. int (*open) (struct inode *,struct file *) 对应系统调用的 open 函数
    设备文件上第一个操作,并不要求驱动程序一定要实现这个方法,如果该项为NULL,设备打开操作永远成功
  2. void (*release) (struct inode *,struct file *) 对应系统调用 close 函数相仿
    当设备文件被关闭时,调用这个操作,与 open 相仿,release也可无
  3. ssize_t (*read) (struct file *,char __user *,size_t,loff_t *) 对应系统调用 read 函数
    从设备中读取数据
  4. ssize_t (*write) (struct file *,const char __user *,size_t,loff_t *) 对应系统调用 write 函数
    从设备中发送数据
  5. unsigned int (*poll) (struct file *,struct poll_table_struct *) 对应系统调用的 **select
    **函数
  6. int (*ioctl)(struct inode *,struct file *,unsigned int,unsigned long) 对应系统调用的ioctl
    控制设备
  7. off_t (*llseek) (struct file *,loff_t,int)
    修改文件当前读写位置,并将新位置作为返回值
  8. int (*mmap) (struct file *,struct vm_area_struct *)
    将设备映射到读写位置,并将新位置作为返回值

内核提供了专门的函数用于访问用户空间指针如:

  1. int copy_from_user(void to,const void __user from,int n) 对应write**
  2. int copy_to_user(void to,const void __user from,int n) 对应read**

设备注册

字符设备使用 struct cdev 来描述
字符设备注册分为三个步骤:

  1. 分配 cdev:使用 cdev_alloc() 函数来完成
    struct cdev *cdev_alloc(void)
  2. 初始化 cdev : cdev_init 函数
    *void cdev_init(struct cdev,const struct file_operations fops);
    参数:
    cdev:待初始化的cdev结构
    fops:设备对应的操作函数集
  3. 添加 cdev:cdev_add函数
    *int cdev_add(struct cdev p,dev_t dev,unsigned count)
    参数:
    p:待添加到内核的字符设备结构
    dev:设备号
    count:添加的设备个数

设备注销

注销函数:int cdev_del(struct cdev *p)
参数:
p:要注销的字符设备结构

简单字符设备的代码实现(上边的函数都有涉及)

工程名为 memdev ,分为三部分构成:

  1. 头文件(memdev.h)
  2. 项目文件(memdev.c)
  3. Makefile

(1)头文件memdev.h

		#ifndef _MEMDEV_H_
		#define _MEMDEV_H_
		
		#ifndef MEMDEV_MAJOR
		#define MEMDEV_MAJOR 99   //预设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;
		};
		
		#endif

(2)项目文件memdev.c

		#include 
		#include 
		#include 
		#include 
		#include 
		#include 
		#include 
		#include 
		#include 
		#include 
		#include 
		#include 
		#include 
		
		#include "memdev.h"
		
		static mem_major = MEMDEV_MAJOR;
		
		module_param(mem_major,int,S_IRUGO);
		
		struct mem_dev *mem_devp;   //设备结构体指针
		
		struct cdev 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 = &mem_devp[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 from %d\n",count,p);
		        }
		
		        return ret;
		}

		//写函数
		static ssize_t mem_write(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_from_user(dev->data+p,buf,count))
		                ret = -EFAULT;
		        else
		        {
		                *ppos += count;
		                ret = count;
		
		                printk(KERN_INFO "write %d bytes 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);
		        //静态申请设备号
		        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结构
		        cdev_init(&cdev,&mem_fops);
		        cdev.owner = THIS_MODULE;
		        cdev.ops = &mem_fops;
		
		        //注册字符设备
		        cdev_add(&cdev,MKDEV(mem_major,0),MEMDEV_NR_DEVS);
		
		        //为设备描述结构分配内存
		        mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev),GFP_KERNEL);
		        if(!mem_devp)
		        {
		                result = -ENOMEM;
		                goto fail_malloc;
		        }
		        memset(mem_devp,0,sizeof(struct mem_dev));
				
				//为设备分配内存
		        for(i= 0; i < MEMDEV_NR_DEVS;i++)
		        {
		                mem_devp[i].size = MEMDEV_SIZE;
		                mem_devp[i].data = kmalloc(MEMDEV_SIZE,GFP_KERNEL);
		                memset(mem_devp[i].data,0,MEMDEV_SIZE);
		        }
		
		        return 0;
		
		fail_malloc:
		        unregister_chrdev_region(devno,1);
		
		        return result;
		}

		//模块卸载函数
		static void memdev_exit(void)
		{
		        cdev_del(&cdev);  //注销设备
		        kfree(mem_devp);  //释放设备结构体内存
		        unregister_chrdev_region(MKDEV(mem_major,0),2);   //释放设备号
		}
		
		MODULE_AUTHOR("zhangbin");
		MODULE_LICENSE("GPL");
		
		module_init(memdev_init);
		module_exit(memdev_exit);

Makefile

			ifneq ($(KERNELRELEASE),)

		obj-m := memdev.o 
		
		else
		
		KDIR := /home/zhangbin/mini6410/linux-2.6.38 
		all:
		        make -C $(KDIR) M=$(PWD) 
		clean:
		        rm -f *.ko *.o *.mod.c *.symvers *.order
		
		endif

应用程序的编写(用于测试)

		#include 
		#include 
		
		int main()
		{
		        FILE *fp0 = NULL;
		        char Buf[4096];
		    
		        //初始化Buf
		        strcpy(Buf,"Mem is char dev!");
		        printf("BUF:%s\n",Buf);
		
		        //打开设备文件
		        fp0 = fopen("/dev/memdev0","r+");
		        if(fp0 == NULL)
		        {
		                printf("Open Memdev0 Error!\n");
		                return -1; 
		        }
		
		        //写入设备
		        fwrite(Buf,sizeof(Buf),1,fp0);
		
		 		//重新定位文件位置
		        fseek(fp0,0,SEEK_SET);
		
		        //清除Buf
		        strcpy(Buf,"Buf is NULL!");
		        printf("BUF :%s\n",Buf);
		
		        fread(Buf,sizeof(Buf),1,fp0);
		
		        //检测结果
		        printf("BUF: %s\n",Buf);
		
		        return 0;
		}

设备实现方法(基于mini6410)

执行 make 命令后,把生成的 memdev.ko 文件下载到开发板系统中
之后手动创建 memdev 设备(创建方法前边有讲)

测试方法如下:
linux驱动程序函数之字符驱动程序的设计_第4张图片

结尾

简单的一个字符驱动程序就算实现了,功能是实现设备的读写,更多控制功能在以后的博客中会更新出来,最近有点忙,更新可能较慢!!!

你可能感兴趣的:(嵌入式,linux)