linux 内核驱动工作原理

重点: 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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值