一、如何查看计算机下面的所有设备
ls -l /dev
可以看到计算机下面所有的设备
c开头的是字符串设备
b开头的是块设备
二、主设备号和次设备号
ls -l /dev 我们看到主设备号是 用户之后的第一列 次设备 是第二列
主设备号由用户使用 次设备号由内核使用
linux内核中有宏定义
<linux/types.h>中
主设备号是MAJOR宏
次设备是MINOR宏
三、分配和释放设备编号
在建立字符串设备之前我们首要的工作是获得一个或者多个设备编号。
register_chrdev_region(dev_t dev,int count,char* name);
name的设备名字将会产生在/proc/devices
运行过程中,可以使用alloc_chrdev_region动态创建
int alloc_chrdev_region(dev_t *dev,unsigned int firstminor, unsigned int count, char* name);
在不使用他们的时候,我们需要使用
unregister_chrdev_region(dev_t first, unsigned int count);
编写一个demo ,开始进行试验
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uaccess.h> /* copy_*_user */
int scull_major = 0;
int scull_minor = 0;
struct cdev cdev;
#ifndef SCULL_MAJOR
#define SCULL_MAJOR 0 /* dynamic major by default */
#endif
#define MAX_SIZE 10
size_t size = 0;
static int __init hello_init(void)
{
printk(KERN_ALERT "HELLO,KERNEL\n");
dev_t dev = 0;
int result;
if (scull_major) {
dev = MKDEV(scull_major, scull_minor);
result = register_chrdev_region(dev, 4, "scull");
}else {
result = alloc_chrdev_region(&dev, 0, 4, "scull");
scull_major = MAJOR(dev);
printk("scull_major:%d\n", scull_major);
printk("dev:%d\n", MINOR(dev));
}
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_ALERT "Goodbye, Kernel!\n");
}
module_init(hello_init);
module_exit(hello_exit);
编译加载到内核中
sudo make &&sudo insmod main.ko && sudo dmesg -c
运行结果这里我看到了我们希望的结果
```
[ 2219.577554] Goodbye, Kernel!
[ 2219.614546] HELLO,KERNEL
[ 2219.614549] scull_major:235
[ 2219.614549] dev:0
```
成功注册了一个235的主设备号,怎么样是不是很有意思啊
四、mknod
1.创建特殊文件。
mknod Name { b | c } Major Minor
2.创建 FIFO(已命名的管道)
mknod Name { p }
描述:
mknod 命令建立一个目录项和一个特殊文件的对应索引节点。第一个参数是 Name 项设备的名称。选择一个描述性的设备名称。mknod 命令有两种形式,它们有不同的标志。
第一种形式中,使用了b 或 c 标志。b 标志表示这个特殊文件是面向块的设备(磁盘、软盘或磁带)。c 标志表示这个特殊文件是面向字符的设备(其他设备)。
五、重要的数据结构
这些重要的数据结构分别是file_operation、file和inode。
文件操作
我们先要看file_operations结构就是用来建立这种连接的,其中包含了一组函数指针。
file_operations这类结构我们统称为fops,这个结构中的每一个字段都必须指向驱动程序中实现的特定函数。
__user 的标识只是一种形式,标识了一个用户空间地址。
我恐怕需要在scull中查看他是怎么写的,我自己从第一行开始抄写demo中的数据结构
owner 指针在书中的介绍
这个字段并不是一个操作;相反他是指向拥有该模块的结构指针。内核使用这个模块避免在模块的操作正在被使用的时候卸载这个模块。
几乎所有的情况下都是用THIS_MODULE来初始化这个位置,定义在<linux/module.h>中
int (*open) (struct inode*, struct file*);
始终是对设备文件执行的第一个操作,并不要求驱动一定要声明这个函数。如果这个位置是NULL,驱动将会永远被打开成功,但是永远不会通知驱动程序。
ssize_t (*read) (struct file*, char __user*, size_t, loff_t*);
用来从设备中读取数据,这个指针被赋值为NULL的时候,导致read系统调用出错,返回-EINVAL,函数返回非负数则是成功读取的字节数
loff_t(*llseek)(struct file*,loff_t int)
llseek用来修改文件的当前读取位置,并将新位置作为返回值返回。参数loff_t一个长偏移量。出错时候返回一个负数
int (*release) (struct inode*, struct file*)
当file结构被释放的时候,调用这个操作,也可以设置为NULL,和open相似
我们看一下scull中设计的数据结构
struct file_operations scull_fops = {
.owner = THIS_MODULE,
.llseek = scull_llseek,
.read = scull_read,
.write = scull_write,
.unlocked_ioctl = scull_ioctl,
.open = scull_open,
.release = scull_release,
};
看到这里,我再思考open read close lseek 会不会触发驱动中对应的函数呢?这真是一个有意思的思考啊,留给以后的日子见证吧!