linux系统将设备分为3类:字符设备、块设备、网络设备。
Linux系统框架如下图:
字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据。字符设备按照字符流的方式被有序访问。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED设备等。
块设备:是指可以从设备的任意位置读取一定长度数据的设备。块设备包括硬盘、磁盘、U盘和SD卡等。
这两种类型的设备的根本区别在于它们是否可以被随机访问——换句话说就是,能否在访问设备时随意地从一个位置跳转到另一个位置。举个例子,键盘这种设备提供的就是一个数据流,当你敲入"fox" 这个字符串时,键盘驱动程序会按照和输入完全相同的顺序返回这个由三个字符组成的数据流。硬盘设备的驱动可能要求读取磁盘上任意块的内容,然后又转去读取别的块的内容,而被读取的块在磁盘上位置不一定要连续,所以说硬盘可以被随机访问,而不是以流的方式被访问,显然它是一个块设备。
每一个字符设备或块设备都在/dev目录下对应一个设备文件。linux用户程序通过设备文件(或称设备节点)来使用驱动程序操作字符设备和块设备。
字符设备、字符设备驱动与用户空间访问该设备的程序三者之间的关系
如图,在Linux内核中:
a – 使用cdev结构体来描述字符设备;
b – 通过其成员dev_t来定义设备号(分为主、次设备号)以确定字符设备的唯一性;
c – 通过其成员file_operations来定义字符设备驱动提供给VFS的接口函数,如常见的open()、read()、write()等;
在Linux字符设备驱动中:
a – 模块加载函数通过 register_chrdev_region( ) 或 alloc_chrdev_region( )来静态或者动态获取设备号;
b – 通过 cdev_init( ) 建立cdev与 file_operations之间的连接,通过 cdev_add( ) 向系统添加一个cdev以完成注册;
c – 模块卸载函数通过cdev_del( )来注销cdev,通过 unregister_chrdev_region( )来释放设备号;
用户空间访问该设备的程序:
a – 通过Linux系统调用,如open( )、read( )、write( ),来“调用”file_operations来定义字符设备驱动提供给VFS的接口函数;
设备号
内核用dev_t类型(
在实际使用中,是通过
(dev_t)–>主设备号、次设备号 | MAJOR(dev_t dev) MINOR(dev_t dev) |
---|---|
主设备号、次设备号–>(dev_t) | MKDEV(int major,int minor) |
注册设备号的函数:
int register_chrdev_region(dev_t from, unsigned int count, char *name); //指定设备编号
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name); //动态生成设备编号
void unregister_chrdev_region(dev_t from, unsigned int count); //释放设备编号
指定设备号 | 自动分配设备号1 | 自动分配设备号2 |
---|---|---|
devno = MKDEV(major,minor); ret = register_chrdev_region(devno, 1, “hello”); cdev_init(&cdev,&hello_ops); ret = cdev_add(&cdev,devno,1); |
alloc_chrdev_region(&devno, minor, 1, “hello”); major = MAJOR(devno); cdev_init(&cdev,&hello_ops); ret = cdev_add(&cdev,devno,1); |
register_chrdev(major,“hello”,&hello_ops); |
创建设备文件:
使用mknod手工创建:
sudo mknod /dev/hello c major minor
chrdev01.c
#include
#include
#include // file_operations
#include // cdev_*
#include // kfree
/
struct cdev *cdev;
dev_t devno;
/
int chrdev01_open (struct inode *inode, struct file *filp)
{
printk("chrdev01 open.\n");
return 0;
}
struct file_operations chrdev01_ops = {
.owner= THIS_MODULE,
.open = chrdev01_open,
};
/
static int __init chrdev01_init(void)
{
printk("chrdev01 init.\n");
devno = MKDEV(247, 1);
if (register_chrdev_region(devno, 1, "chrdev01")<0) {
//if (alloc_chrdev_region(&devno, 0, 1, "chrdev01")<0) {
printk("alloc_chrdev_region() failed!\n");
return -ENOMEM;
}
printk("chrdev01 major is:%d\n", MAJOR(devno));
cdev = cdev_alloc();
if (cdev==NULL) {
printk("cdev_alloc() failed!\n");
goto ERR_ALLOC;
}
cdev->owner = THIS_MODULE;
cdev_init(cdev, &chrdev01_ops);
if (cdev_add(cdev, devno, 1)<0) {
goto ERR_CDEV_ADD;
}
return 0;
ERR_CDEV_ADD:
ERR_ALLOC:
unregister_chrdev_region(devno, 1);
return -ENOMEM;
}
static void __exit chrdev01_exit(void)
{
cdev_del(cdev);
kfree(cdev);
unregister_chrdev_region(devno, 1);
printk("chrdev01 exit.\n");
}
module_init(chrdev01_init);
module_exit(chrdev01_exit);
MODULE_AUTHOR("Rbin.Yao");
MODULE_DESCRIPTION("A simple char device driver.");
MODULE_VERSION("V1.0");
MODULE_LICENSE("GPL");
Makefile:
obj-m := chrdev01.o
PWD := $(shell pwd)
KDIR := /lib/modules/$(shell uname -r)/build
all: chrdev01_test
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
@rm -f chrdev01_test
chrdev01_test:chrdev01_test.c
gcc $< -o $@
chrdev_test.c测试代码:
#include
#include
#include
#include
int main(int argc, char **argv)
{
int fd;
fd = open("/dev/chrdev01", O_RDONLY);
if (fd<0) {
perror("open /dev/chrdev01 failed!");
} else {
printf("open /dev/chrdev01 ok!\n");
close(fd);
}
return 0;
}
https://blog.csdn.net/zqixiao_09/article/details/50839042