重点: file_operations 结构体
(1) 里面元素是函数指针, 用来指向实体函数地址
(2) 每个设备驱动都需要一个改结构体类型的变量
(3) 设备驱动需要向内核注册, 在注册的时候, 是要提供此结构体的变量的
注册字符驱动:
(1) 驱动是需要向内核注册的, 不然内核不知道有这个驱动
(2) 注册的函数主要是register_chrdev 函数去注册
register_chrdev 函数
作用: 驱动向内核注册自己的file_operations
注册成功: 返回值 0 不成功 返回值 -1
static inline int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
{
return __register_chrdev(major, 0, 256, name, fops);
}
int __register_chrdev(unsigned int major, unsigned int baseminor,
unsigned int count, const char *name,
const struct file_operations *fops)
{
struct char_device_struct *cd;
struct cdev *cdev;
int err = -ENOMEM;
cd = __register_chrdev_region(major, baseminor, count, name); // 注册主设备号
if (IS_ERR(cd))
return PTR_ERR(cd);
cdev = cdev_alloc(); // 分配内存
if (!cdev)
goto out2;
cdev->owner = fops->owner;
cdev->ops = fops; // 然后将file_operations结构体和cdev结构体进行挂钩
kobject_set_name(&cdev->kobj, "%s", name);
err = cdev_add(cdev, MKDEV(cd->major, baseminor), count); // 接着进行将cdev结构体和主次设备好进行绑定
if (err)
goto out;
cd->cdev = cdev;
return major ? 0 : cd->major;
out:
kobject_put(&cdev->kobj);
out2:
kfree(__unregister_chrdev_region(cd->major, baseminor, count));
return err;
}
inline 声明的函数能够减少函数调度的花销, 但是带来的副作用是可以把函数放到头文件中去了.
(1) 内核中有一个数组用来存储注册的字符设备驱动
(2) register_chrdev 内部将我们要注册地的驱动信息() 存储在数组中相应的位置
(3) cat /proc/devices 查看内核中已经注册过的字符设备驱动(和块设备驱动)
/proc 虚拟文件系统, 是将内核中的相关数据结构 通过文件虚拟构建出来, 然后通过命令, 可以看到相关的数据变量的信息
应用程序怎么调用驱动程序
需要的就是设备文件, 设备文件关键的就是主次设备号
使用mknod /dev/test c 250(主设备号) 0 (次设备号)
然后在应用程序中通过open close read writer 等函数操作设备文件
其实操作的就是open_operations 里面的函数指针对应的函数
copy_from_user 用来将数据从用户空间复制到内核空间
copy_to_user
然后其实就是在应用程序中得read 和 write函数中操作, 最终操作的是 copy_from_user 和copy_to_user函数,.
这样就实现应用程序拷贝数据到驱动中了,
还有注意的是copy_from_user和copy_to_user函数的返回值是拷贝总数-拷贝成功数, 比如拷贝长度50 成功拷贝了40 则返回值就是
50-40=10
驱动中操作硬件,
操作的是虚拟地址, 不是物理地址,
虚拟地址映射分为静态和动态映射
静态有点像C语言中的全局变量(通过硬编码,然后编译内核固定的), 而动态映射像malloc 动态申请内存一样.
静态映射好处就是执行效率高, 坏处就是时钟占用虚拟地址, 动态映射的好处就是按需分配, 坏处就是每次都需要建立和销毁映射.
寻找内核中的静态映射表, 关键字 map VA
PA 还有就是arch/arm里面关于平台中额plat/include
驱动层代码只负责操作硬件的代码, 相关逻辑的判断应该在应用层中处理完成
字符设备新注册接口
register_chrdev_regpion/alloc_chrdev_regpion+cdev
cdev 是一个结构体里面重点包含file_operations结构体
以及主次设备号
相关的函数: cdev_alloc, cdev_init, cdev_addr, cdev_del
以及相关的宏定义
MKDEV, MAJOR, MINOR
register_chrdev_region + cdev_init+cdev_addr 进行驱动的注册以及添加
然后使用cdev_del + unregister_chrdev_region 进行删除和注销设备驱动
自动创建设备文件
方法, udev(嵌入式中用的是mdev)
udev 是一个应用程序
在busybox 中的指令, 然后在内核启动过程中
已经运行了, 在rs.S中
mdev 使得内核驱动和应用层之间有一套信息传输机制(netlink协议)
驱动注册和注销时信息会传输给udev。 因此udev可以在应用层中对设备文件进行创建和删除
具体函数
class_create
device_create.
sys文件系统和proc文件系统一样都是虚拟文件系统,
有关静态映射表的建立过程,
重点是MACHINE_START 宏的结构体中的
的map_io 函数里面一直追溯到
iotable_init 函数参数中的一个 struct map_desc 变量,
那个变量就是静态映射表
是一个结构体数组
内核提供的读写寄存器的接口方式
writel 和 readl
还有就是iowrite32和 ioread32
我们在调用
class_register(&input_class); 函数的时候可以在 /sys/class/ 里面看到相对应的类文件夹
但是里面的设备文件是需要
device_register 函数进行注册才能看得到
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
注意, 其实device_register 里面是两个文件, 有一些代码是调用
device_register 里面的device_initialize 和 device_add进行分开调用的,
因此看到device_initialize 和device_add出现的时候, 其实就是相当于device_register