1、主设备号和次设备号(二者一起为设备号):
  一个字符设备或块设备都有一个主设备号和一个次设备号。主设备号用来标识与设备文件相连的驱动程序,用来反  映设备类型。次设备号被驱动程序用来辨别操作的是哪个设备,用来区分同类型的设备。
  linux内核中,设备号用dev_t来描述,2.6.28中定义如下:
  typedef u_long dev_t;

      在32位机中是4个字节,高12位表示主设备号,低12位表示次设备号。

可以使用下列宏从dev_t中获得主次设备号:                   

也可以使用下列宏通过主次设备号生成dev_t:
MAJOR(dev_t dev);

MKDEV(int major,int minor);

MINOR(dev_t dev);

//宏定义:
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1) //1U后面的U表示1是unsigned int (如果是在VS编译器下的话,就是32位),把1U左移(<<,这是位运算符号,按位左移)20位就相当于1 * 2^20,然后-1,也就是mask替代了(2^20-1)。
//得到的结果就是高12位全为0,低20位全为1
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

2.分配设备号(两种方法)

(1静态申请

int register_chrdev_region(dev_t from,unsigned count,const char *name);

 (2)动态分配

int alloc_chardev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name);

注销设备号

void unregister_chrdev_region(dev_t from, unsigned count);

创建设备文件

利用cat /proc/devices查看申请到的设备名,设备号。

(1)使用mknod手工创建:mknod filename type  major minor

(2) 自动创建:

利用udev(mdev)来实现设备文件的自动创建,首先应保证支持udev(mdev),由busybox

配置,在驱动初始化代码里调用class_create为该设备创建一个class,再为每个设备调用device_create

创建对应的设备。

3、字符设备驱动程序重要的数据结构
(1)struct file:

代表一个打开的文件描述符,系统中每一个打开的文件在内核中都有一个关联的struct file。它由内核在open时创建,并传递给在文件上操作的任何函数,直到最后关闭。当文件的所有实例都关闭之后,内核释放这个数据结构。

//重要成员

const struct file_operations *f_op;  //该操作是定义文件关联操作的。内核在执行open时对这个指针赋值。

off_t f_pos;                  //该文件读写位置。

void  *private_data;          //该成员是系统调用时保存状态信息非常有用的资源。

  2)struct inode:

用来记录文件的物理信息。它和代表打开的file结构是不同的。一个文件可以对应多个file结构,但只有一个inode结构。inode一般作为file_operations结构中函数的参数传递过来。
  inode译成中文就是索引节点。每个存储设备或存储设备的分区(存储设备是硬盘、软盘、U盘 ... ... )被格式化为文件系统后,应该有两部份,一部份是inode,另一部份是Block,Block是用来存储数据用的。而inode呢,就是用来存储这些数 据的信息,这些信息包括文件大小、属主、归属的用户组、读写权限等。inode为每个文件进行信息索引,所以就有了inode的数值。操作系统根据指令, 能通过inode值最快的找到相对应的文件。

dev_t i_rdev;   //对表示设备文件的inode结构,该字段包含了真正的设备编号。

struct cdev *i_cdev;   //是表示字符设备的内核的内部结构,当inode指向一个字符设备文件时,该字段包含了指向struct_cdev结构的指针。

我们也可以使用下边两个宏从inode中获得主设备号和次设备号

unsigned int iminor(struct inode *inode);

unsigned int imajor(struct inode *inode);

(3)struct file_operations

struct file_operation ***_ops={

.owner=THIS_MODULE,

.llseek=***_llseek,

.read=***_read,

.write=***_write,

.ioctl=***_ioctl,

.open=***_open,

.release=***_release,

.....

};   //strude file_operations 是一个函数指针的集合

struct module *owner;

字符设备驱动程序注意事项

1)open()调用可能由于几个原因而失败。

(2)成功运行的read()和write()返回的字节数可能是1至请求的字节数之间的任意值,因此应用程序必须能处理这些情况。
(3)即使1字节的数据读或写就绪,select()也会返回成功。
(4)很多字符驱动程序方法是可选的,并不是所有的方法都提供。
另外,字符驱动程序不仅在drivers/char/目录下。下面是一些“超级”字符驱动程序:
(1)串行驱动程序,放在drivers/serial/目录下。
(2)输入驱动程序,放在drivers/input/目录下。
(3)帧缓存区(/dev/fb/*)提供对显存的访问,/dev/mem提供对系统内存的访问途径。
(4)一些设备类支持少量采用字符接口的硬件。
(5)一些子系统提供额外的字符接口,以向用户空间提供原始的设备模型。例如MTD子系统。
(6)一些内核层提供钩子,通过导出相应的字符接口实现用户空间的设备驱动程序。 例如ioctl。
在drivers/目录下的register_chrdev上运行grep-r可了解内核中字符驱动程序的大致情况。

 设备驱动程序是内核的一部分,它完成以下的功能

1、对设备初始化和释放;

(1)字符设备cdev结构体初始化:

***********不是每个字符设备驱动都需要,cdev是为了构建设备模型,便于设备文件的管理所产生的。如果你的字符设备比较简单或者你不需要构建设备模型,是可以不需要cdev.
file_operation结构是虚拟层上的东西,这样使得驱动程序可以操作设备。*******************

内核中每个字符设备都对应一个 cdev 结构的变量,下面是它的定义: linux-2.6.22/include/linux/cdev.h
struct cdev {
   struct kobject kobj;          // 每个 cdev 都是一个 kobject
   struct module *owner;       // 指向实现驱动的模块
   const struct file_operations *ops;   // 操纵这个字符设备文件的方法
   struct list_head list;       // 与 cdev 对应的字符设备文件的 inode->i_devices 的链表头
   dev_t dev;                   // 起始设备编号
   unsigned int count;       // 设备范围号大小
};

  静态内存定义初始化:
                        struct cdev my_cdev;
                        cdev_init(&my_cdev, &fops);
                        my_cdev.owner = THIS_MODULE;

void cdev_init(struct cdev *cdev, const struct file_operations *fops)
                        {
                           memset(cdev, 0, sizeof *cdev);
                           INIT_LIST_HEAD(&cdev->list);
                           kobject_init(&cdev->kobj, &ktype_cdev_default);
                           cdev->ops = fops;
}

        动态内存定义初始化:
                        struct cdev *my_cdev = cdev_alloc();
                        my_cdev->ops = &fops;
                        my_cdev->owner = THIS_MODULE;

struct cdev *cdev_alloc(void)
                        {
                         struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
                         if (p) {
                                 INIT_LIST_HEAD(&p->list);
                                 kobject_init(&p->kobj, &ktype_cdev_dynamic);
                                }
                         return p;
                        }

             由此可见,两个函数完成都功能基本一致,只是 cdev_init() 还多赋了一个 cdev->ops 的值。

初始化 cdev 后,需要把它添加到系统中去。为此可以调用 cdev_add() 函数。传入 cdev 结构的指针,起始设备编号,以及设备编号范围。
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
   p->dev = dev;
   p->count = count;
   return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}
关 于 kobj_map() 函数就不展开了,我只是大致讲一下它的原理。内核中所有都字符设备都会记录在一个 kobj_map 结构的 cdev_map 变量中。这个结构的变量中包含一个散列表用来快速存取所有的对象。kobj_map() 函数就是用来把字符设备编号和 cdev 结构变量一起保存到 cdev_map 这个散列表里。当后续要打开一个字符设备文件时,通过调用 kobj_lookup() 函数,根据设备编号就可以找到 cdev 结构变量,从而取出其中的 ops 字段。
当一个字符设备驱动不再需要的时候(比如模块卸载),就可以用 cdev_del() 函数来释放 cdev 占用的内存。
void cdev_del(struct cdev *p)
{
   cdev_unmap(p->dev, p->count);
   kobject_put(&p->kobj);
}
其中 cdev_unmap() 调用 kobj_unmap() 来释放 cdev_map 散列表中的对象。kobject_put() 释放 cdev 结构本身。

下面这个是必须要有的:

设备初始化

 static int first_drv_init(void)           
 {
   major=register_chrdev(0,"first_drv",&first_drv_fops); //注册,告诉内核。
   firstdrv_class=class_create(THIS_MODULE,"firstdrv");   //创建一个类
   if(IS_ERR(firstdrv_class))
   return PTR_ERR(firstdrv_class);
   
   firstdrv_class_dev=class_device_create(firstdrv_class,NULL,MKDEV(major,0),NULL,"xyz");//xyz为设备名字
   gpfcon=(volatile unsigned long *)ioremap(0x56000050,16);
   gpfdat=gpfcon+1;
   if(unlikely(IS_ERR(firstdrv_class_dev)))
   return PTR_ERR(firstdev_class_drv);
   return 0;
 }
 
 设备卸载
 static vodi first_drv_exit(void)
 {
   unregister_chrdev(major,"first_drv");//卸载
   class_device_unregister(firstdrv_class_dev);
   class_destroy(firstdrv_class);
   iounremap();
 }

  2、把数据从内核传送到硬件(copy_to_user)和从硬件读取数据(copy_form_user);

结构体file_operations在头文件 linux/fs.h中定义,用来存储驱动内核模块提供的对设备进行各种操作的函数的指针。该结构体的每个域都对应着驱动内核模块用来处理某个被请求的 事务的函数的地址。

fileoprations结构体上定义的函数进行具体的数据操作。

static struct  file_operations second_drv fops={
        .owner=THIS_MODULE;
        .write=second_drv_write;
        .open=second_drv_open;
        .read=second_drv_read;
        }    
        
      int major;

static int first_drv_open(struct inode *inode,struct file *file)
    {
     gpfcon &= ~((0x3<<(4*2))|(0x3<<(5*2))|(0x3<<(6*2)));    //GPF4,5,6为输出
     gpfcon |=  ((0x1<<(4*2))|(0x1<<(5*2))|(0x1<<(6*2)));    //
     
     return 0;
    }
 
 static ssize_t first_drv_read(struct inode *inode,struct file *file)
    {
      
      return 0;
    }
    
    static ssize_t first_drv_write(struct file *file,const char _user *buf,size_t count,loff_t *ppos)
    {
      int val;
      copy_from_user(&val,buf,count);
      if(val==1)
      {
         gpfdata|=((1<<4)|(1<<5)|(1<<6));          //点灯
      }
      else
      {
         gpfdata &=~ ((1<<4)|(1<<5)|(1<<6));                 //灭灯
      }
      return 0;
    }
 
 测试程序:
 int main()
     {
        int fd;
        int val=1;
        fd=open(("/dev/xyz",O_RDWR);
         if(fd<0)
         {
          printf("cant't open");
         }
         if(argc!=2)
         {
           printf("Usage:\n");
           printf("%s<on|off>\n",argv[0]);//<>表示参数不可以省略,|表示或者的意思/
         }
         if(strcmp(argc[1],on)==0)   //如果第二个参数等于on的话
         {
           val=1;
         }
         else
         {
         val=0;
         }
        write(fd,&val,4);
        return 0;
      }
      
      写驱动程序的时候要:1.写框架。2.看原理图 3.看手册 3.写代码(在驱动程序中,不能直接使用物理地址,要ioremap,使用虚拟地址)
   主设备号作用是找到fileoperations。次设备号是留给我们自己用的。
   int minor=MINOR(inode->i_rdev);次设备号代表的意思有我们自己决定。

  3、读取应用程序传送给设备文件的数据和回送应用程序请求的数据;

            应用程序、库、内核、驱动程序的关系

                1)应用程序调用一系列函数库,通过对文件的操作完成一系列功能:

                   应用程序以文件形式访问各种硬件设备(linux特有的抽象方式,把所有的硬件访问抽象为对文件的读写、设置)

               函数库:

                         部分函数无需内核的支持,由库函数内部通过代码实现,直接完成功能

                        部分函数涉及到硬件操作或内核的支持,由内核完成对应功能,我们称其为系统调用

               2)内核处理系统调用,根据设备文件类型、主设备号、从设备号(后面会讲解),调用设备驱动程序;

               3)设备驱动直接与硬件通信;

  4、检测和处理设备出现的错误。

ARM Linux字符设备驱动程序的更多相关文章

  1. 简单linux字符设备驱动程序

    本文代码参考<LINUX设备驱动程序>第三章 字符设备驱动程序 本文中的“字符设备”是一段大小为PAGE_SIZE的内存空间 功能:向字符设备写入字符串:从字符设备读出字符串 代码: 1. ...

  2. 浅析Linux字符设备驱动程序内核机制

    前段时间在学习linux设备驱动的时候,看了陈学松著的<深入Linux设备驱动程序内核机制>一书. 说实话.这是一本非常好的书,作者不但给出了在设备驱动程序开发过程中的所须要的知识点(如对 ...

  3. 一个简单的演示用的Linux字符设备驱动程序

    实现如下的功能:--字符设备驱动程序的结构及驱动程序需要实现的系统调用--可以使用cat命令或者自编的readtest命令读出"设备"里的内容--以8139网卡为例,演示了I/O端 ...

  4. linux字符设备驱动程序框架(老方法)

    #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #inclu ...

  5. Linux字符设备简单示例

    1. Linux字符设备是一种按字节来访问的设备,字符驱动则负责驱动字符设备,这样的驱动通常实现open.close.read和write系统调用.例如:串口.Led.按键等. 2. 通过字符设备文件 ...

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

    第零章:扯扯淡 特此总结一下写的一个简单字符设备驱动程序的过程,我要强调一下“自顶向下”这个介绍方法,因为我觉得这样更容易让没有接触过设备驱动程序的童鞋更容易理解,“自顶向下”最初从<计算机网络 ...

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

          <一>.主设备号和次设备号        对字符设备的訪问时通过文件系统内的设备名称进行的.那些设备名称简单称之为文件系统树的节点,它们通常位于/dev文件夹. 字符设备驱动程 ...

  8. Linux驱动实践:你知道【字符设备驱动程序】的两种写法吗?

    作 者:道哥,10+年嵌入式开发老兵,专注于:C/C++.嵌入式.Linux. 关注下方公众号,回复[书籍],获取 Linux.嵌入式领域经典书籍:回复[PDF],获取所有原创文章( PDF 格式). ...

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

    字符设备驱动程序 应用程序是调用C库中的open read write等函数.而为了操作硬件,所以引入了驱动模块. 构建一个简单的驱动,有一下步骤. 1. 创建file_operations 2. 申 ...

随机推荐

  1. Django基本介绍

    Django板块分类: 1.urls.py  网址的入口(关联到views.py中的一个函数) 2.views.py 处理用户发起的请求,从urls.py中对应过来,通过渲染templates中的网页 ...

  2. C语言自带的快速排序(qsort)函数使用方法

    感觉打快排太慢了,找到了c语言自带的函数.这函数用起来没c++的方便,不过也够了. 函数名称:qsort,在头文件:<stdlib.h>中 不多说,上代码: #include <st ...

  3. 软键盘android:windowSoftInputMode属性详解

    android:windowSoftInputModeactivity主窗口与软键盘的交互模式,可以用来避免输入法面板遮挡问题,Android1.5后的一个新特性.这个属性能影响两件事情:[一]当有焦 ...

  4. NGUI系列教程五(角色信息跟随)

    在一些网络游戏中,我们常常可以看到角色的上方显示着角色的名称,等级,血量等信息.它们可以跟随角色移动,并且可以显示和隐藏.今天我们就来学习一下这些功能的实现方法.1. 新建unity工 程,导入NGU ...

  5. nodejs基础安装

    安装Nodejs需要从官网上下载一个最新的安装包,运行.我这里是win764位系统. 下载版本6.5.0 由于去外国的镜像上下载东西比较慢,淘宝为我们准备了国内的镜像.我们需要安装国内镜像的使用工具. ...

  6. 解决npm安装模块时 npm err! registry error parsing json

    最近还真是点背,从yeoman生成一个react项目或是github上克隆一个项目,在npm install的时候, 一直报npm err! registry error parsing json(就 ...

  7. Apple 如何知道你使用了私有API

    大约有三种方式 otool -L这个工具可以清晰的列出你链接所有的库 像IO.Kit是不允许使用的 nm -u 这个工具可以清晰的列出你所有链接符号如 C方法 OC方法 检查所有Selecter的字符 ...

  8. js展开更多

    var introduces = { inIt : function(){ introduces.imgLoad(); introduces.showMore(0,'hioh',86); introd ...

  9. 用 Maven 做项目构建

    转自:http://www.ibm.com/developerworks/cn/java/j-lo-maven/index.html 本文将介绍基于 Apache Maven 3 的项目构建的基本概念 ...

  10. java修改远程服务器密码

    积累: 1. echo 用户名:新密码 | chpasswd