3. Linux - 字符设备驱动模型

      在上一节(Linux 设备驱动 — 概念)中,我们对Linux设备驱动有了大致的了解;接下来的几个章节主要对字符设备进行学习。

1、设备描述结构

在任何一种驱动模型中,设备都会用内核中的一种结构来描述。我们的字符设备在内核中使用 struct cdev 来描述。

struct cdev {
    struct kobject kobj;
    struct module *owner;
    const struct file_operations *ops;   //设备操作集
    struct list_head list;
    dev_t dev;  //设备号
    unsigned int count; //设备数
};

1.1、 设备号

主设备号
次设备号

      Linux内核中使用 dev_t 类型来定义设备号,dev_t这种类型其实质为32位的unsigned int,其中高12位为主设备号,低20位为次设备号.

问1:如果知道主设备号,次设备号,怎么组合成dev_t类型?
答: dev_t dev = MKDEV(主设备号,次设备号)
问2: 如何从dev_t中分解出主设备号?
答: 主设备号 = MAJOR(dev_t dev)
问3: 如何从dev_t中分解出次设备号?
答: 次设备号 = MINOR(dev_t dev)

  • 设备号的申请

    • 静态申请:

开发者自己选择一个数字作为主设备号,然后通过函数 register_chrdev_region 向内核申请使用。缺点:如果申请使用的设备号已经被内核中的其他驱动使用了,则申请失败。

  • 动态分配

使用 alloc_chrdev_region 由内核分配一个可用的主设备号。优点:因为内核已经知道哪些设备号被使用了,所以不会导致分配到已经被使用到的设备号。

  • 设备号的注销

不论使用何种方法分配设备号,都应该在驱动退出时,使用 unregister_chrdev_region 函数释放这些设备号。

2、编写字符设备驱动

写出一个字符设备驱动一般需要以下几步:

  • (1)确定major;
  • (2)分配一个file_operation结构体;
  • (3)设置file_operation结构体;
  • (4)注册 cdev
  • (5)入口(模块加载);
  • (6) 出口(卸载函数);

说明:
     file_operation 定义了字符设备驱动提供给虚拟文件系统的接口函数;
     Linux 内核提供了一组函数用以操作 cdev 结构体:

void cdev_init(struct cdev *,struct file_operation *);
struct cdev *cdev_alloc(void);
void cdev_put(struct cdev *p);
void cdev_add(struct cdev *,dev_t,unsigned);
void cdev_del(struct cdev *);

cdev_init: 用于初始化cdev成员,并建立cdev和file_operation之间的连接;
cdev_alloc:用于动态申请一个cdev内存;
cdev_add/cdev_del:分别向系统添加和删除一个cdev,完成字符设备的注册和注销;

创建first_drv.c文件,代码如下:

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/slab.h>

struct cdev cdev; 
dev_t devno;


/* inode:结构表示具体的文件;
   file:结构体用来追踪文件在运行时的状态信息。*/
static int first_drv_open(struct inode *inode, struct file *filp)
{
    printk("@debug -----> first_drv_open! \n");      //打印
    return 0;
}

/*file:为目标文件结构体指针;
  buffer:为要写入文件的信息缓冲区,
  count:为要写入信息的长度;
  ppos:为当前的偏移位置,这个值通常是用来判断写文件是否越界*/
static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
   printk("@debug -----> first_drv_write!\n");      //打印
   return 0;
}

 static struct file_operations first_drv_fops = {
    .owner  =   THIS_MODULE,     //被使用时阻止模块被卸载
    .open   =   first_drv_open,      
    .write   =   first_drv_write,   
};

/*4写first_drv_init入口函数来调用这个register_chrdev()注册函数*/
int first_drv_init(void)
{
    /*初始化cdev结构*/
    cdev_init(&cdev, &first_drv_fops);
    
    /* 注册字符设备 */
    alloc_chrdev_region(&devno, 0, 2, "first_drv");
    cdev_add(&cdev, devno, 2);
    return 0;
}

void first_drv_exit(void)
{
    cdev_del(&cdev);   /*注销设备*/
    unregister_chrdev_region(devno, 2); /*释放设备号*/
}

/*module_init修饰入口函数*/
module_init(first_drv_init);
/*module_exit修饰出口函数*/
module_exit(first_drv_exit);

/*许可证声明, 描述内核模块的许可权限,如果不声明LICENSE,
模块被加载时,将收到内核被污染 (kernel tainted)的警告。*/
MODULE_LICENSE( "GPL v2" );

写Makefile编译脚本:

KDIR := /home/work/project/cbox/02-source/iMX-Linux

all:
    make -C $(KDIR) M=$(PWD) modules 
clean:
    rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.bak *.order
 
 
obj-m := first_drv.o

make,编译生成frist_drv.ko文件


拷贝到开发板后,通过 insmod first_drv.ko来挂载, 通过 cat /proc/devices就能看到first_drv已挂载好:

编写测试程序 first_drv_test.c 代码如下:

#include <sys/types.h>    //调用sys目录下types.h文件
#include <sys/stat.h>      //stat.h获取文件属性
#include <fcntl.h>
#include <stdio.h>

/*输入”./first_drv_test”,     agc就等于1, argv[0]= first_drv_test  */
/*输入”./first_drv_test on”,   agc就等于2, argv[0]= first_drv_test,argv[1]=on;  */

int main(int argc,char **argv) 
{
    int fd1, fd2;
    int val=1;
    fd1 = open("/dev/xxx",O_RDWR);  //打开/dev/xxx设备节点
    if(fd1<0)                   //无法打开,返回-1
      printf("@debug ----- > can't open%d! \n", fd1);
    else
       printf("@debug -----> can open%d! \n", fd1);    //打开,返回文件描述符

    write(fd1, &val, 4);          

    return 0;
}
执行生成可执行文件:arm-linux-gnueabihf-gcc first_drv_test.c -o first_drv_test

拷贝到开发板后执行报错,发现如果open()打不开,会返回-1:


是因为我们没有创建dev/xxx这个设备节点,然后我们来创建,使它等于刚刚挂载好的first_drv模块。设备节点的手动创建,可参考:linux中在/dev/下手动创建设备节点

mknod -m 660 /dev/xxx c 246 0 // first_drv模块的主设备号=246

./first_drv_test

OK。

2.1、自动创建设备节点

对于每个设备,如果都需要这样手动创建的话,那也太麻烦了。
可以使用自动创建设备节点,Linux有udev、mdev的机制,而我们的ARM开发板上移植的有udev机制,然后udev机制会通过class类来找到相应类的驱动设备来自动创建设备节点 (前提需要有udev)。
具体udev相关知识这里不详细阐述,可以移步Linux 文件系统与设备文件系统 —— udev 设备文件系统,这里主要讲使用方法。
在驱动用加入对udev 的支持主要做的就是:在驱动初始化的代码里调用class_create(...)为该设备创建一个class,再为每个设备调用device_create(...)创建对应的设备。

(1)首先创建一个class设备类,然后在class类下,创建一个class_device,即类下面创建类的设备;

static struct class *firstdrv_class;               //创建一个class类
static struct class_device   *firstdrv_class_devs; //创建类的设备

(2)在first_drv_init入口函数中添加:

//创建类,它会在sys/class目录下创建firstdrv_class这个类
firstdrv_class= class_create(THIS_MODULE,"firstdrv");  
 
//创建类设备,会在sys/class/firstdrv_class类下创建xyz设备,
//然后udev通过这个自动创建/dev/first_drv这个设备节点,     
firstdrv_class_devs=device_create(firstdrv_class,NULL,MKDEV(major,0),NULL,"first_drv");

(3)在first_drv_exit出口函数中添加:

 device_unregister(firstdrv_class_devs);      //注销类设备,与class_device_create对应
 class_destroy(firstdrv_class);  //注销类,与class_create对应

这样,就不需要再mknod了。
驱动程序first_drv_open first_drv_write中只是打印数据,接下来下一节便开始来实际硬件操作。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容

  • 大学的时候,帮朋友写的操作系统调研的作业,最近整理过去的文档时候偶然发现,遂作为博客发出来。 从串口驱动到Linu...
    free_will阅读 7,375评论 7 59
  • 转自,格式做了调整。 如果你使用Linux比较长时间了,那你就知道,在对待设备文件这块,Linux改变了几次策略。...
    mikeliuy阅读 7,777评论 0 1
  • 提到了关于Linux的设备驱动,那么在Linux中I/O设备可以分为两类:块设备和字符设备。这两种设备并没有什么硬...
    故事狗阅读 23,672评论 0 46
  • feisky云计算、虚拟化与Linux技术笔记posts - 1014, comments - 298, trac...
    不排版阅读 3,833评论 0 5
  • 尝闻经语,以鞋图壁,白众元无开悟。彼佛心要几经秋,似举花,同开一处。 流传法眼,衰微正道,游戏神通如故。如来教化百...
    宗定法师阅读 549评论 2 9