1. 字符设备的基本的框架:
1.1 组合主设备号和次设备号:
设备号是一个 dev_t 的一个数,其实是一个32位的数
int hello_major = 250;
int hello_minor = 0;
dev_t dev = 0;
dev = MKDEV(hello_major, hello_minor);
1.2 注册设备号:
注册上设备号后能用 lsmod 查看模块,一般是 xxx.ko 的 xxx
int result;
int number_of_devices = 1;
result = register_chrdev_region(dev, number_of_devices, "hello_dev");
if(result < 0){
printk(KERN_WARING "hello: can't get major number %d\n", hello_major);
return result;
}
1.3 取消注册的设备号:
dev_t devono = MKDEV(hello_major, hello_minor);
unregister_chrdev_region(devno, number_of_devices);
2. 函数说明:
MKDEV: --组合主设备号和次设备号,设备号的释放要晚于释放 cdev
包含的库:
linux/fs.h
函数原型:(所在文件include/linux/kdev_t.h)
#define MINORBITS 20
#define MKDEV(ma.mi) (((ma) << MINORBITS) | (mi))
参数:
ma,主设备号前 12 位
mi,次设备号后 20 位
返回值:
设备号,类型是 dev_t ,一般的变量时 dev
register_chrdev_region: --静态注册设备号
包含的库:
linux/fs.h
函数原型:
int register_chrdev_region(dev_t from, unsigned count, const char *name)
参数:
from,设备号,240-254 是留给本地和实验用
count,数量
name,设备的名字,如果数量是多个,那名字应该放在一个数组里,这里就是是实现的(com1,com2,com3...)
会显示在/proc/devices 中
返回值:
0,成功
<0,出错
unregister_chrdev_region: --取消注册的设备号
包含的库:
linux/fs.h
函数原型:
void unregister_chrdev_region(dev_t from, unsigned count)
参数:
from,设备号
count,数量
3. 两个结构体:
file_operations: --字符设备操作的集合,包括很多函数的索引
定义:
struct file_operations
hello_fops ={
.owner=THIS_MODULE,
...
}
cdev: --
字符设备结构体
包含的库:
linux/cdev.h
cdev结构体是这样被定义的:
也就是这种结构:
dev_t 本质是一个32位的整数
cdev结构体的使用:
①定义
struct cdev *cdev;
②初始化
cdev_init: --初始化一个 cdev 结构体,将file_operations 里的函数放到 cdev 中去
函数原型:
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
参数:
cdev,要初始化的字符设备结构体
fops,字符设备的操作集合
例子:
cdev_init(&cdev,hello_fops);
cdev.owner =THIS_MODULE;
③注册cdev
cdev_add: --添加一个字符设备到系统
函数原型:
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
参数:
p,设备的cdev结构体
dev,传进来的cdev结构体的设备号
count,相应设备的连续的次设备号的个数,
返回值:
<0,失败
其他,成功
例子:加载函数
int ret = 0;
ret =cdev_add(&cdev,devno,1); //devno 是设备号,250那个
if(ret <0 )
{
printk("cdev_add error\n");
return ret;
}
④释放 cdev 结构体
cdev_del
:从系统中移除一个cdev
函数原型:
void cdev_del(struct cdev *p)
参数:
p,要移除的 cdev 结构体
例子:卸载函数
cdev_del(&cdev)
4. 动态分配设备号和 cdev 结构体:
alloc_chrdev_region: --动态分配设备号
函数原型:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
参数:
dev,存放申请好后的设备号
baseminor,次设备号从哪开始
count,申请的数量
name,要申请分配的名字,是设备号的助记符,在
/proc/devices
中能查看到的
返回值:
0,成功
<0,失败
例子:
int error;
dev_t devno ;
error = alloc_chrdev_region(&devno,0,1,"char_dev");
if(error<0 )
{
printK("alloc_chrdev_region error\n");
return error;
}
cdev_alloc: --动态分配设备结构体 cdev
函数原型:
struct cdev *cdev_alloc(void)
返回值:
返回一个cdev的结构体
例子:
struct cdev *cdevp = NULL;
在加载函数内部:
cdevp = cdev_alloc();
5. 代码示例:
char_open.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
MODULE_LICENSE ("GPL");
int hello_major = 250;
int hello_minor = 0;
int number_of_devices = 1;
struct cdev *cdev;
static int hello_open (struct inode *inode, struct file *file)
{
printk (KERN_INFO "Hey! device opened\n");
return 0;
}
static int hello_release (struct inode *inode, struct file *file)
{
printk (KERN_INFO "Hmmm! device closed\n");
return 0;
}
struct file_operations hello_fops = {
.owner = THIS_MODULE,
.open = hello_open,
.release = hello_release
};
static int __init hello_2_init (void)
{
int result;
dev_t devno = MKDEV (hello_major, hello_minor); /* 生成设备号 */
/* 静态注册设备号,名字显示在 /proc/devices 中 */
result = register_chrdev_region (devno, number_of_devices, "xxhello");
if (result < 0) {
printk (KERN_WARNING "hello: can't get major number %d\n", hello_major);
return result;
}
/* 动态分配设备机构体cdev,得到一个设备机构体cdev */
cdev = cdev_alloc();
/* 将cdev中的file_operation的指针指向用户提供的结构体 */
cdev_init (cdev, &hello_fops);
cdev->owner = THIS_MODULE;
/* 将cdev中的设备号赋值,并将cdev添加到系统中去,即能够用lsmod查看到 */
result = cdev_add (cdev, devno, 1);
if (result)
printk (KERN_INFO "Error %d adding char_device", result);
printk (KERN_INFO "Char device registered ...\n");
return 0;
}
static void __exit hello_2_exit (void)
{
dev_t devno = MKDEV (hello_major, hello_minor);
if (cdev)
cdev_del (cdev);
unregister_chrdev_region (devno, number_of_devices);
printk (KERN_INFO "char device cleaned up :)\n");
}
module_init (hello_2_init);
module_exit (hello_2_exit);
【1】对字符设备的注册,即hello_2_init做的事情其实很简单,就做了两步:1. 将设备号注册到系统中,2. 将结构体跟设备号关联并添加到系统中去
Makefile
ifeq ($(KERNELRELEASE),)
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
#KERNELDIR ?= ~/wor_lip/linux-3.4.112
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules* Module*
.PHONY: modules modules_install clean
else
obj-m := char_open.o
endif
文件的测试方法是两个文件放到一个文件夹下,make modules 或者直接 make ,然后 sudo insmod char_open.ko ,之后lsmod | grep char_open 即能看到这个模块成功的加载到了内核。
当我们要查看 printk 打印的信息时,需要用 dmesg 命令,还要说的一点是sudo dmesg -c是清除之前的显示。
6. 补充:
上边有将主设备号和次设备号组合的函数 MKDEV(major,minor),下边是从设备号中提取主设备号和次设备号:
MAJOR函数: --提取主设备号
例子:MAJOR(dev_t)
MINOR函数: --提取次设备号
例子:MINOR(dev_t)