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]#