linux2.6标准字符设备驱动模型(手动注册)

linux2.6标准字符设备核心结构

//虽然linux26有核心结构体,但在代码中没有具体操作,他们的调用在相关注册函数中对其有操作
struct cdev {
    struct kobject kobj;
    struct module *owner;
    const struct file_operations *ops;
    struct list_head list;
    dev_t dev;
    unsigned int count;
};

关键成员介绍

  • ops
    文件操作集合
  • dev
    32位设备号,包含主设备号和次设备号
  • count
    占用连续的次设备号数量,从dev中的次设备号开始
  • 其余成员与系统调用相关,用户不用配置

设备号

linux2.6以后用dev_t 32位整数表示设备号,实质是u32类型,其中高12位是主设备号,低20位是次设备号。
主设备号:dev_t高12位,2^12=4K,(10是给杂项设备使用的)
早期字符设备范围:0~255 Linux2.6是0~4095
次设备号:dev_t低20位,2^20=1M
早期字符设备范围:0~255(主设备号一旦注册,下属次设备号全部被注册), Linux2.6是0~1M-1
合成设备号:
MKDEV(ma,mi);ma:主设备号;mi:次设备号
分解设备号:
MAJOR(dev)
MINOR(dev)
内核中定义

#define MINORBITS   20
#define MINORMASK   ((1U << MINORBITS) - 1)
#define MAJOR(dev)  ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)  ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))

特征

  • 安装后不会在/dev目录下创建设备文件节点,需要手动mknod创建
  • 调用一个cdev_add注册后,指定数量的次设备号将被占用,数量自己制定,一个主设备号可以被cdev_add注册多次。
  • 设备号使用前需要提前申请,register_chrdev_region静态分配,或alloc_chrdev_region动态分配设备号函数申请。

设备号申请/注销函数

静态设备号申请函数

//静态申请设备号
int register_chrdev_region(dev_t from, unsigned count, const char *name)
from :指定的设备号   250  1
count :设备个数   5         250 1   250 2  250 3  250 4   250 5  
name: 设备名  
返回值:成功返回0,失败返回负数错误码
  • 头文件
    #include<linux/fs.h>
  • 功能
    注册一个设备号范围
  • 参数
  • [ ] from起始设备号(主、次)
  • [ ] count连续的次设备号数量
  • [ ] name设备名,不需要和/dev的设备名相同
  • [ ] 其余参数由内核使用,用户不直接操作
  • 返回值
    成功:返回0, 失败:返回负数

动态设备号注册函数

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

dev: 接收动态分配的一系列设备号的第一个设备号
baseminor:指定一系列设备号的第一个设备号的次设备号
count :设备个数
name :设备名

返回值:成功0,失败为负数
  • 头文件
    #include<linux/fs.h>
  • 功能
    注册一个设备号的范围
  • 参数
  • [ ] *dev:存放分配到的第一个设备(包含主次设备号)
  • [ ] baseminor:要分配的起始次设备号
  • [ ] count连续次设备号的数量
  • [ ] name设备名,不需要和/dev的设备名相同
  • 返回值
    成功:返回0 失败:返回负数

设备号释放函数

//释放设备号
void unregister_chrdev_region(dev_t from, unsigned count)
from: 第一个设备号
count:设备个数
  • 头文件
    #include<linux/fs.h>
  • 功能
    注销一个设备号范围
  • 参数
  • [ ] from:起始设备号(主、次)(包含主次设备号)
  • [ ] count:连续的次设备号数量
  • 返回值

核心结构分配函数

struct cdev *cdev_alloc(void); //分配存储区
功能:在堆空间中分配一个核心结构体,注意不使用时用kfree函数释放,
参数:
返回值:返回分配到struct cdev的结构首地址

说明:用完记得释放,否则会造成内存泄漏

第一种方式:
struct cdev k;//定义变量,定以后已经有了struct cdev解构内存空间
第二种方式:
struct cdev *p://这种写法只是定义了指针,但是没有分配struct cdev内存空间,p=struct_cdev();//写在模块加载函数中


核心结构struct cdev初始化函数

初始化函数

void cdev_init(struct cdev *xxx, const struct file_operations *fops); //初始化
- 头文件
#include<linux/cdev.h>
- 功能
初始化核心结构,具体的做法是清零核心结构,初始化核心结构体的list,kobj,ops成员
- 参数
- [ ] cdev:需要初始化的核心结构体
- [ ] fops:文件操作集合函数结构体
- 返回值

说明:写这种驱动模型的时候,不需要定义struct cdev结构体初始化,因为在调用cdev_init结构体会初始化把它请0,定义时候的初始无效


真正的设备注册/注销函数

//注册驱动
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
p: 核心结构体
dev:设备号
count:设备个数

返回值:成功返回0,失败返回负数错误码
  • 头文件
    #include<linux/cdev.h>
  • 功能
    注册一个cdev结构
  • 参数
  • [ ] p:已经初始化的核心结构体指针
  • [ ] dev:起始设备号(包含主次设备号在内)
  • [ ] count:连续的次设备号数量
  • 返回值
    成功:返回0 失败:返回负数
//注销函数
void cdev_del(struct cdev *p);
p:核心结构体
  • 头文件
    #include<linux/cdev.h>
  • 功能
    注销一个cdev结构
  • 参数
    p 前面注册的struct cdev结构指针
  • 返回

linux2.6标准字符设备驱动编程步骤

回顾杂项设备
定义杂项设备核心结构体,填充核心结构体,注册核心结构。这个过程中,使用倒推法则,实现要什么给什么。
linux2.6模型方法相同。

模块的初始化函数

  • 使用cdev_alloc(void)分配cdev空间
  • 申请设备号:静态或者动态申请
//静态申请
int register_chrdev_region(dev_t from, unsigned count, const char *name);
//动态申请
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
                                                    const char *name)
  • 初始化cdev结构
void cdev_init(struct cdev *xxx, const struct file_operations *fops); //初始化
  • 注册已初始化好的cdev结构
//注册驱动
int cdev_add(struct cdev *p, dev_t dev, unsigned count);

模块卸载函数做的事情和加载的函数相反,顺序也相反。

  • 注销cdev结构
void cdev_del(struct cdev *p);
  • 释放设备号
void unregister_chrdev_region(dev_t from, unsigned count);
  • 释放cdev解构空间
kfree(void *p);

驱动核心

  • 实现struct file_operations结构

示例

代码

#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<linux/kdev_t.h>
#include<linux/slab.h>

#define MY_DEVICE_NAME "linux26"
static struct cdev *pcdev;
static dev_t dev_no;   //第一个设备号(包含主次)
static int major=0;

ssize_t my_device_open(struct inode *node, struct file *file)
{
   printk("my device is open\n");
   return 0;
}
ssize_t my_device_close(struct inode *node, struct file *file)
{
  printk("my device is close\n");
  return 0;
}

ssize_t my_device_read(struct file *fp, char __user *buf, size_t size, loff_t *loff)
{
  printk("my device is read\n");
  return 0;
}

ssize_t my_device_write(struct file *file, const char __user *buf, size_t size, loff_t *loff)
{
  printk("my device is write\n");
  return 0;
}


//文件结构体
struct file_operations my_fops={
    .owner=THIS_MODULE,
    .open=my_device_open,
    .read=my_device_read,
    .write=my_device_write,
    .release=my_device_close,
};


static int __init linux26_cdev_init(void)
{
  //分配cdev空间
  pcdev=cdev_alloc();
  //动态分配设备号 次设备号0开始 数量为2个 名称为宏
  alloc_chrdev_region(&dev_no,0,2,MY_DEVICE_NAME);
  //初始化结构体
  cdev_init(pcdev,&my_fops);
  //注册驱动
  cdev_add(pcdev,dev_no,2);
  //再去查注册文件很麻烦,这里打印主设备号方便创建节点
  major=MAJOR(dev_no);
  printk("linux26 major is %d\n",major);
  return 0;
}
static void __exit linux26_cdev_exit(void)
{
  cdev_del(pcdev);
  unregister_chrdev_region(dev_no,2);
  kfree(pcdev);
  printk("cdev module is exit\n");
}

module_init(linux26_cdev_init);
module_exit(linux26_cdev_exit);
MODULE_LICENSE("GPL");

app代码

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

int main(int argc,char *argv[])
{
  char buf[20];
  int fd = open(argv[1],O_RDWR);
    if(fd == -1)
    {
        printf("打开失败\n");
        return 0;
    }
    read(fd,buf,0);
    write(fd,NULL,0);
    close(fd);
    return 0;
}

Makefile

KERN_DIR = /zhangchao/linux3.5/linux-3.5
all:
    make -C $(KERN_DIR) M=`pwd` modules
clean:
    rm -f *.mod.o *.order *.symvers *.o *.mod.c
obj-m += cdev.o

模块编译

[root@CentOS zhangchao]# make
make -C /zhangchao/linux3.5/linux-3.5 M=`pwd` modules
make[1]: 进入目录“/zhangchao/linux3.5/linux-3.5”
  CC [M]  /zhangchao/rootfs/zhangchao/cdev.o
  Building modules, stage 2.
  MODPOST 1 modules
  LD [M]  /zhangchao/rootfs/zhangchao/cdev.ko
make[1]: 离开目录“/zhangchao/linux3.5/linux-3.5”
[root@CentOS zhangchao]# ls
cdev_app.c  cdev.ko     cdev.mod.o  Makefile       Module.symvers
cdev.c      cdev.mod.c  cdev.o      modules.order
[root@CentOS zhangchao]# 

app 编译

[root@CentOS zhangchao]# arm-linux-gcc cdev_app.c -o cdevapp
[root@CentOS zhangchao]# ls
cdevapp     cdev.c   cdev.mod.c  cdev.o    modules.order
cdev_app.c  cdev.ko  cdev.mod.o  Makefile  Module.symvers

开发板装载模块

没有采取自动创建设备节点,所以/dev没有该模块的设备文件节点

[root@ZC/zhangchao]#insmod cdev.ko 
[  742.520000] linux26 major is 250
[root@ZC/zhangchao]#lsmod
Module                  Size  Used by    Tainted: G  
cdev                    1518  0 
[root@ZC/zhangchao]#

通过/proc/devices设备注册文件表查看是否安装成功

可以看到主设备号为250的linux26设备识别名出现在列表中,证明注册没有问题。

[root@ZC/zhangchao]#cat /proc/devices 
Character devices:
  1 mem
  2 pty
  3 ttyp
  ···
189 usb_device
204 ttySAC
216 rfcomm
250 linux26
251 ttyGS
252 watchdog
253 media
254 rtc

Block devices:
  1 ramdisk
···
179 mmc
254 device-mapper

创建文件节点

[root@ZC/zhangchao]#mknod /dev/linux26 c 250 1
[root@ZC/zhangchao]#ls /dev/linux26 -l
crw-r--r--    1 root     root      250,   1 Mar 29 16:37 /dev/linux26
[root@ZC/zhangchao]#

运行app

app调用驱动模块成功

[root@ZC/zhangchao]#./cdevapp /dev/linux26 
[ 1345.240000] my device is open
[ 1345.240000] my device is read
[ 1345.240000] my device is write
[ 1345.240000] my device is close
[root@ZC/zhangchao]#

思考下面的问题

[root@ZC/zhangchao]#rm /dev/linux26 
[root@ZC/zhangchao]#mknod /dev/linux26 c 250 2
[root@ZC/zhangchao]#./cdevapp /dev/linux26 
打开失败
[root@ZC/zhangchao]#rm /dev/linux26 
[root@ZC/zhangchao]#mknod /dev/linux26 c 250 1
[root@ZC/zhangchao]#./cdevapp /dev/linux26 
[ 1564.045000] my device is open
[ 1564.045000] my device is read
[ 1564.045000] my device is write
[ 1564.045000] my device is close
[root@ZC/zhangchao]#

如果将次设备号定义为2,运行app将不能运行驱动模块,这是为什么呢?因为我们注册时,申请的次设备号数量为两个,在主设备号下其他次设备号没有被占用的情况下,所以只有0和1可以被注册,设备号为2时,他将是次设备号的第三个

卸载驱动模块

[root@ZC/zhangchao]#rmmod cdev.ko 
[ 1977.955000] cdev module is exit
[root@ZC/zhangchao]#
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值