文件分为: 1.文件内容;2.文件名;3.元信息(时间戳,文件大小等)
一、Linux内核对设备的分类
linux的文件种类:
-
-:普通文件
-
d:目录文件
-
p:管道文件
-
s:本地socket文件
-
l:链接文件
-
c:字符设备
-
b:块设备
Linux内核按驱动程序实现模型框架的不同,将设备分为三类:
-
字符设备:按字节流形式进行数据读写的设备,一般情况下按顺序访问,数据量不大,一般不设缓存
-
块设备:按整块进行数据读写的设备,最小的块大小为512字节(一个扇区),块的大小必须是扇区的整数倍,Linux系统的块大小一般为4096字节(4K),随机访问,设缓存以提高效率
-
网络设备:针对网络数据收发的设备
网络设备名字不是文件;(一切皆文件的反例)
总体框架图:
二、设备号------内核中同类设备的区分
内核用设备号来区分同类里不同的设备,设备号是一个无符号32位整数,数据类型为dev_t,设备号分为两部分:
-
主设备号:占高12位,用来表示驱动程序相同的一类设备
-
次设备号:占低20位,用来表示被操作的哪个具体设备
应用程序打开一个设备文件时,通过设备号来查找定位内核中管理的设备。
MKDEV宏用来将主设备号和次设备号组合成32位完整的设备号,用法:
dev_t devno; int major = 251;//主设备号 int minor = 2;//次设备号 devno = MKDEV(major,minor);
MAJOR宏用来从32位设备号中分离出主设备号,用法:
dev_t devno = MKDEV(249,1); int major = MAJOR(devno);
MINOR宏用来从32位设备号中分离出次设备号,用法:
dev_t devno = MKDEV(249,1); int minor = MINOR(devno);
如果已知一个设备的主次设备号,应用层指定好设备文件名,那么可以用mknod命令在/dev目录创建代表这个设备的文件,即此后应用程序对此文件的操作就是对其代表的设备操作,mknod用法如下:
@ cd /dev @ mknod 设备文件名 设备种类(c为字符设备,b为块设备) 主设备号 次设备号 //ubuntu下需加sudo执行
在应用程序中如果要创建设备可以调用系统调用函数mknod,其原型如下:
int mknod(const char *pathname,mode_t mode,dev_t dev); pathname:带路径的设备文件名,无路径默认为当前目录,一般都创建在/dev下 mode:文件权限 位或 S_IFCHR/S_IFBLK dev:32位设备号 返回值:成功为0,失败-1
三、申请和注销设备号
字符驱动开发的第一步是通过模块的入口函数向内核添加本设备驱动的代码框架,主要完成:
-
申请设备号
-
定义、初始化、向内核添加代表本设备的结构体元素
int register_chrdev_region(dev_t from, unsigned count, const char *name) 功能:手动分配设备号,先验证设备号是否被占用,如果没有则申请占用该设备号 参数: from:自己指定的设备号 count:申请的设备数量 name:/proc/devices文件中与该设备对应的名字,方便用户层查询主设备号 返回值: 成功为0,失败负数,绝对值为错误码
int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count, const char *name) 功能:动态分配设备号,查询内核里未被占用的设备号,如果找到则占用该设备号 参数: dev:分配设备号成功后用来存放分配到的设备号 baseminior:起始的次设备号,一般为0 count:申请的设备数量 name:/proc/devices文件中与该设备对应的名字,方便用户层查询主次设备号 返回值: 成功为0,失败负数,绝对值为错误码
分配成功后在/proc/devices 可以查看到申请到主设备号和对应的设备名,mknod时参数可以参考查到的此设备信息
void unregister_chrdev_region(dev_t from, unsigned count) 功能:释放设备号 参数: from:已成功分配的设备号将被释放 count:申请成功的设备数量
释放后/proc/devices文件对应的记录消失
附代码-设备号的申请与销毁
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
int major = 11;//主设备号
int minor =0;//次设备号
int mychar_num = 1;//次设备数量
int __init mychar_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major,minor);//组合成完整的设备号
ret = register_chrdev_region(devno,mychar_num,"mychar");
if(ret)//ret非0,表示失败
{
ret = alloc_chrdev_region(&devno,minor,mychar_num,"mychar");
//此设备号申请后填写到devno地址中去,从minor开始申请mychar_num个
if(ret)
{
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);//获取新的设备号,不要遗漏
}
return 0;
}
void __exit mychar_exit(void)
{
dev_t devno = MKDEV(major,minor);
unregister_chrdev_region(devno,mychar_num);
}
MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);