经过前两篇的学习,也算对驱动进行了祛魅,驱动本质上不过是在内核地址空间运行的一块儿代码,依据Linux独有的驱动架构,对外设进行类裸机操作,从而Linux驱动开发。
默念十遍的话:加载时注册,卸载时释放资源。加载时注册,卸载时释放资源。加载时注册,卸载时释放资源。
此章中的高级用法其实并不高级,叫新函数更为贴切,兼容老函数接口,后续个人使用应注意使用新接口!
1、进阶字符设备注册函数
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
major
: 主设备号。如果设置为0,内核将动态分配一个未使用的主设备号。如果指定了一个非零值,则表示你想要使用这个固定的主设备号。
name
: 用于在/dev
目录中创建设备文件的设备名称。
fops
: 指向file_operations
结构体的指针,这个结构体定义了设备支持的操作,如open
、read
、write
等。register_chrdev不支持从设备节点从代码中创建,若创建从设备节点,则使用mknod从整机用户空间下创建。
int register_chrdev_region(dev_t first, unsigned int count, const char *name);
first
: 设备号的起始值,类型为dev_t
。设备号通常由主设备号和次设备号组成,可以通过MKDEV(major, minor)
宏来生成,输入型参数。
count
: 需要注册的连续设备号的数量。
name
: 设备名称,通常是一个字符串,用于标识设备。相较于register_chrdev关联结构体指针,故需配合cdev接口使用,cdev_init()
int alloc_chrdev_region(dev_t *dev, unsigned int baseminor, unsigned int count, const char *name);
dev:字符设备结构体,用于存储分配好的主设备号和从设备号,。输出型参数。baseminor:从设备号的起始地址
count:从设备号的数量
name:设备名,在/dev目录下
MKDEV:
#define MKDEV(ma, mi) (((ma) << MINORBITS) | (mi))
一般主设备号12位,从设备号20位
MAJOR:获取主设备号
MINOR:获取从设备号
register_chrdev为早期Linux版本中分配主设备号的函数,register_chrdev_region为增强版本,不仅能分配主设备号,还能分配一定范围内的从设备号,并自动生成/dev下的文件名。
cdev(character device)为一个注册函数新接口的关键结构体。
1、
struct cdev *cdev_alloc(void);
作用:
在内核中动态分配一个 cdev 结构体。
如果分配成功,返回指向 cdev 结构体的指针;如果失败,返回 NULL。
相对于使用全局变量整个生命周期占用数据段,其在使用上更加灵活。
cdev_alloc申请的空间,需使用kfree释放。
2、
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
cdev:指向需要初始化的 cdev 结构体。
fops:指向 file_operations 结构体,包含字符设备的操作函数(如 open、read、write 等)。
在kernel代码中cdev_init()经常会被下面两行代码替代。因为cdev_init中的初始化部分已经在cdev_alloc中初始化,其他的处理即文件操作赋值。但建议仍然使用cdev_alloc,统一性会更好。
struct cdev *cdev = cdev_alloc();
if (cdev) {
cdev->ops = &my_fops;
cdev->owner = THISMODULE;
// 其他必要的初始化...
}
3、
int cdev_add(struct cdev *cdev, dev_t dev, unsigned count);
cdev:指向已初始化的 cdev 结构体。
dev:设备号(dev_t 类型),通常通过 MKDEV 宏生成。
count:次设备号的数量(即该设备支持的子设备数量)。
4、
void cdev_del(struct cdev *cdev);
cdev:指向需要移除的 cdev 结构体。
疑问:为啥需要cdev_alloc从内存中获取一段内存呢,驱动模块也不大啊,为啥不直接用全局结构体?
答:猜测是因为全局变量不支持卸载,频繁驱动的安装卸载会导致大量的内存泄漏,而且用全局变量的话得在程序设计时之初就已定义好或者新加载一个全局变量初始化模块,不如设计成随用随取的动态加载形式。全局变量会在整个生命周期内占据数据段。
以下两函数可以代替mknod(手工),使用以下两函数netlink的形式让应用层自己创建设备文件:
class_create:创建设备类。
device_create:创建设备节点,在/sys/class或/dev目录下。
/sys目录下的文件为内核与应用交互时使用,可以是内核提供的调试方式。
使用ioremap映射内存空间的作用?
1、ioremap
允许内核模块和用户空间程序共享内存区域。这可以用于高效地传递大量数据,而不需要在内核和用户空间之间进行频繁的拷贝操作。2、在设备驱动中,
ioremap
常用于将硬件设备的寄存器映射到用户空间,使得用户空间程序可以直接读取和写入硬件寄存器,从而控制硬件设备。
2、
驱动注册新老接口:老接口是适配老驱动存在,新接口新增特性,后续个人编码应用新接口。
主设备号用于记录Linux内核中记录所有字符设备的数组中的绝对位置。
诞生次设备号的原因:可以区分同一驱动的不同设备;主设备号用于关联到特定的驱动程序,而次设备号则用于在驱动程序内部区分设备实例,使得驱动程序能够处理多个相同类型的设备。
0x2001和0x2002为不同的设备,使用MAJOR与MINOR提取出不同平台的主次设备号!赋值时使用MKDEV。这三个宏是硬件无关宏,方便移植。
Linux内核编码十分规范,若形参没有const,则其为输出型参数;若包含const,则为输入型参数。
kernel的发展是个迭代的过程,跟安卓那种出新废旧不一样,因此会有大量的老代码,老代码中用了大量的老函数,无视即可,使用新函数!
cat /proc/devices
显示的设备列表反映了操作系统识别到的硬件设备,而这些设备的正常工作通常依赖于lsmod
中列出的相应内核模块。
static struct char_device_struct {
struct char_device_struct *next;
unsigned int major;
unsigned int baseminor;
int minorct;
char name[64];
struct cdev *cdev; /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE]; // 共255个数据项,即cat /proc/devices下的驱动。
// 可见,驱动注册完毕后即添加进255的一个容器中,然后在/proc/devices目录下可查。
cdev使用流程:分配设备号-->注册设备驱动
Linux中异常处理的办法:(此刻觉得goto蛮好用的,把异常处理都放到后面了)
.ko的驱动加载时,该放到数据段里面的东西也都被加载进去了,跟so一个性质,数据段很灵活,并不只是在开机时才会给全局变量分配。
越往底层,一般越定义指针,怕局部变量其他的把栈撑爆了,怕定义全局变量一直占用着数据段空间,示例:
__register_chrdev_region(unsigned int major, unsigned int baseminor,
int minorct, const char *name)
{
struct char_device_struct *cd, **cp; //定义指针更贴切,定义局部变量担心会把栈撑爆了。
........
........
}
设备文件为什么不在内核里面创建?
因为设备文件是在用户空间,不适合在内核中创建,在用户空间下用udev创建。
3、
Linux的文件系统详解:
/sys和/proc都是伪文件系统,是虚拟出来的,不占用内存空间,操作这个文件系统就是操作内核相关的东西。内容都是动态生成的,反映了当前内核和系统的状态。
shell下输入的指令如何解析出来?
当用户按下回车键,shell 会接收用户输入的指令。shell 首先解析指令,这包括分离指令名和它的参数,以及处理特殊字符。
cat xxx.c命令的执行过程:
整个过程可以用以下公式表示:
用户输入→shell解析→查找命令→创建子进程→执行命令→输出结果→结束命令具体到
cat
命令的执行流程:cat filename.txt→shell解析→查找 /bin/cat→fork() + exec()→读取文件→输出内容→退出
例:
4、
linux内核代码中以__开头的函数一般是在内核中使用的,不建议直接在上层使用。
常见的有 kmalloc()
的底层实现可能是 __kmalloc()
。
或者标记某些特殊行为:
__user
:标记用户空间指针。
__init
:标记初始化函数。
__exit
:标记模块退出函数。
__attribute__((...))
:用于指定编译器属性。
5、
什么是udev?
udev是一个用户空间的设备管理器,用于在系统启动时创建设备节点,以及在设备的插入或移除时更新/dev下的目录文件。
udev 根据内核提供的信息动态创建和删除
/dev
目录下的设备文件。这意味着只有当设备实际连接到系统时,udev 才会为该设备创建设备文件,从而避免了在/dev
目录下产生大量不存在的设备文件。udev 依赖于 sysfs 文件系统,后者提供了一个统一的接口来访问设备属性。当内核检测到设备状态变化时,它会通过 sysfs 更新设备信息,udev 则根据这些信息来更新
/dev
目录。