linux驱动(第三课 SYSFS LDM )

在linux中,一切IO资源皆文件。自然的,一切设备皆文件。
所以,用户程序中如果要进行IO,首先要open一个FILE,指定FILE_PATH,让内核能够找到对应的IOResource(IOR),并填充一个FileControlBlock,然后返回一个filp给用户。之后,用户可以利用filp作为句柄来操作IOR。
对于用户而言,read和write总是发生在userbuf和IOR之间的。所以我们看到,read和write都会指定fd和userbuf。

struct file{
	struct inode *f_inode;
	struct file_operations *f_ops;
	void *private_data;

	struct list_head f_ep_links;
	struct list_head f_tfile_llink;
}

这是FILE中,对于LDD而言比较关注的几个成员。
我们看到,FILE关联到INODE,LINUX中,是用INODE表示文件系统中的节点的。可以看出,FILE是依赖于对INODE的引用来实现操作的。
FILE关联到FOPS,linux中,用FOPS作为函数接口,提供了指向TextInstruction的能力。在数据布局中,一个配置好的FOPS,其中的指针都已经指向了合适的函数文本代码。可以看出,FILE是依赖于对FOPS的引用来找到正确的操作函数,并进一步被内核调用的。
FILE关联到PrivData,它是FILE的私有数据的句柄。之所以用关联句柄,而不是内嵌实体,是因为并不能确定PrivData的长度。
FILE内嵌了两个ListHead,说明FILE可以被放到两个不同的链表中。这样,内核就可以通过两个不同的ListRoot找到FILE。除了这两个ListEntry,内核还可以通过FD_Vector来找到这个FILE。FD_Vector是用户的一个ArrayTable,存放32个filp。

struct inode {
	dev_t i_rdev;
	union{
		struct cdev *i_cdev;
		struct block_device *i_bdev;
	}
};

对于INODE,我们看到,内嵌的i_rdev表示设备号。高12为major,低20为minor。
如果是CDEV,那么INODE关联到一个CDEV,内核通过INODE可以索引到CDEV数据对象上。
如果是BDEV,那么INODE关联到一个BDEV,内核通过INODE可以索引到BDEV数据对象上。

当用户通过
#ls /proc/devices
命令访问文件夹时,内核会找到devices文件的read方法,并调用read。简单而言,这个/proc/devices.fops->read方法会遍历内核中的INODE,并把所有的属性为DEVICES的INODE的DEVID打印出来。
注意,我们心理一定要有这个概念,每一个FILE_PATH,都对应于自己的FOPS。为了清晰显示,我们使用了/proc/devices.fops->read这个表达方式。

我们虽然使用了一条命令,但是这个命令执行的时候,却是执行了多个步骤。
/.fops->open(proc)
/proc.fops->open(devices)
/proc/devices.fops->read()
它需要层层推进,打开每一层的FILE,并找到下一层的INODE,然后打开下一层的FILE。
/proc文件系统是一个伪文件系统,它并不在磁盘上,而是内核动态创建的。

同样的,sysfs也是一个伪文件系统,它也不在磁盘上。
它和kobject紧密相关。所以,sysfs是和LDM配合使用的。
当一个kobject注册到内核中时,注册函数要做两件事,一是把kobject插入kset的链表,二是在sysfs中创建对应的目录。
sysfs是需要挂载的。
所以我们在/etc/fstab中看到,有如下脚本。

#mount -t sysfs sysfs /sys

这个命令,把sysfs挂载到/sys这个目录下面。
mount执行的结果,就是修改了/sys的INODE。使得/sys的INODE指向sysfs的TOPNODE。
这样,之后的操作,就将使用新的INODE和新的FOPS。
/sys/bus/
/sys/devices/
/sys/class/
/sys/firmware/
以上,都是sysfs中必须有的子目录。每一个子目录对应kset,子目录中的文件,又对应kobject。从而形成树状结构。

#cd /sys
#tree

运行上述命令,将得到sysfs的树状结构的文件层次关系。
我们可以发现,其中大部分都是符号链接。实体是放在/sys/devices目录中的。
例如/sys/bus/pci/devices目录中的文件,其实是对/sys/devices中的相应文件的符号链接。
这就引出了Linux Devices Model (LDM)。将设备模型分离为BUS,DEVICE,DRIVER,ATTR。简称BDDA。

struct bus_type{
	const char* name;
	const char* dev_name;
	struct device* dev_root;
	
	const struct attribute_group **bus_groups;
	const struct attribute_group **dev_groups;
	const struct attribute_group **drv_groups;
	
	int (*match)();
	int (*probe)();
	int (*remove)();

}

BUS用bus_type结构体来作为控制块。
BUS关联到一个DEVICE,这个DEVICE作为DEVROOT。
DEVICE可以形成一个链表。BUS通过DEVROOT,就可以找到所有从属于BUS的DEVICE。
其中与驱动相关的几个成员是,name,match,probe,remove。
name是内核用来做字符串比较的文本。
match函数用来对从属于该总线的dev和drv进行匹配处理。
probe函数是BUS被加载时,被内核调用的Callback。
remove函数是BUS被卸载时,被内核调用的Callback。

我们注意到,BUS中没有内嵌listhead,所以BUS并不能添加到链表中。
当bus_register内核API把BUS注册到内核中后,内核只能通过BUS_VectorTable来索引到BUS,并不能通过某些ListRoot来索引到BUS。

struct device_driver{
	const char* name;
	struct bus_type* bus;
	
	int (*probe)();
	int (*remove)();
	
	const struct attribute_group **drv_groups;
	
    const struct of_device_id* of_match_table;
}

struct of_device_id{
	char cmpatible[128];
	const void* data;
}

DRIVER用device_driver结构体来作为控制块。
可以看到,DRIVER关联到一个BUS结构体。
其中与驱动相关的几个成员是,name,bus,probe,remove。
name是内核用来做字符串比较的文本。
bus是关联的BUS结构体。
probe函数是DRV被加载时,被内核调用的Callback。
remove函数是DRV被卸载时,被内核调用的Callback。

DRVIER关联到一个OF_DEV_ID的TABLE。这可以方便的从DTB中,借助于字符串比较,找到匹配的设备。
(注意,在Linux中,如果命名为Table,则通常是用Array来分配内存的,而如果命令为List,则通常是用链表来分配内存的。我们知道,Array通常用指针来引用。这是linux常见的编程方式。另外就是PointerArray,在linux中,通常是用二级指针来引用。)

我们注意到,DRIVER中没有内嵌listhead,所以DRIVER并不能添加到链表中。当driver_register内核API把DRIVER注册到内核中后,内核只能通过DRV_VectorTable来索引到DRIVER,并不能通过某些ListRoot来索引到DRIVER。

当模块调用driver_register这个内核API来注册某个BUS的DRIVER时,内核会通过DRIVER的关联的BUS,找到BUS中的DEVROOT。然后遍历这些DEVICE,内核这时会调用BUS的match函数,这个callback处理传入的DRIVER和传入的DEVICE的匹配关系。如果匹配,会调用DRIVER的probe函数这个Callback,并把DEV添加到BUS的DEVROOT为首元的链表中。
然后为BUS,DRIVER,DEVICE三者配置指针,使DRIVER能够索引到BUS,使DEVICE能够索引到DRIVER和BUS。

struct device{
	const char* init_name;
	struct device* parent;
	struct kobject kobj;
	const struct device_type* type;
	
	void (*release)();
	
	struct bus_type* bus;
	struct device_driver* driver;
	

	const struct attribute_group **dev_groups;

	void* platform_data;
	struct device_node* of_node;
    
    struct list_head devres_head;
    struct klist_head knode_class;
}

DEVICE用device结构体来作为控制块。
它内嵌了一个kobject实体,并且内嵌了两个listhead,可以由两个不同的listroot进行查找。
它关联了一个DEV,指向它的父设备。
它关联了一个DEV_TYPE,用来描述设备的类型。

几个关键的成员,init_name,bus,driver,release,of_node,platform_data。
它关联了一个BUS和一个DRIVER,从而使DEVICE在逻辑上从属于BUS和DRIVER。通过关联的指针,可以索引到对应的BUS和对应的DRIVER。
release函数,是DEV的引用数为0时,被内核调用的Callback。
of_node关联到一个OF_NODE结构体,它可以方便的从DTB中,找到匹配的设备。

我们注意到,DEV中内嵌listhead,所以DEV能添加到链表中。当device_register内核API把DEV注册到内核中后,内核除了能通过DEV_VectorTable来索引到DRIVER,还能通过某些ListRoot来索引到DEV。

当模块调用device_register这个内核API来注册某个BUS的DEV时,内核会通过DRV_VectorTable,遍历注册到内核中的DRIVER,而DRIVER会找到关联的BUS,并调用BUS的match函数,这个callback处理传入的DRIVER和传入的DEVICE的匹配关系。如果匹配,会调用DRIVER的probe函数这个Callback,并把DEV添加到BUS的DEVROOT为首元的链表中。
然后为BUS,DRIVER,DEVICE三者配置指针,使DRIVER能够索引到BUS,使DEVICE能够索引到DRIVER和BUS。

struct atrribute{
	const char* name;
	umode_t mode;
}
struct bus_atrribute{
	struct atrribute attr;
	ssize_t (*show)();
	ssize_t (*store)();
}
struct device_atrribute{
	struct atrribute attr;
	ssize_t (*show)();
	ssize_t (*store)();
}
struct driver_atrribute{
	struct atrribute attr;
	ssize_t (*show)();
	ssize_t (*store)();
}

ATTR用attribute结构体来作为控制块。BDD对ATTR进行了扩展,内嵌一个ATTR实体后,形成了BUSATTR,DEVATTR和DRVATTR三种结构体。

sysfs中的目录,对应于BDD,而目录中的文件,则对应于ATTR。
这样,当用户通过sysfs访问到ATTR时,read操作将调用ATTR.show这个Callback。write操作将调用ATTR.store这个Callback。

来看一个简单的例子。我们定义了三个模块。vbus,vdrv,vdev。

static struct bus_type vbus = {
	.name = "vbus",
	.match = vbus_match,
}
static int __init vbus_init(void)
{
	int ret;
	ret = bus_register(&vbus);
	return ret;
}


static struct device_driver vdrv = {
	.name = "vdrv",
	.bus = &vbus,
}
static int __init vdrv_init(void)
{
	int ret;
	ret = driver_register(&vdrv);
	return ret;
}

static struct device vdev = {
	.init_name = "vdev",
	.bus = &vbus,
	.release = vdev_release,
}
static int __init vdev_init(void)
{
	int ret;
	ret = device_register(&vdev);
	return ret;
}
static void vdev_release(struct device *dev)
{
	return;
}

从中可以看到,BUS,DEV,DRV相互之间的关联关系。
再来看一个更具有代表性的例子。
仍然是定义了三个模块。packt_bus,packt_bus_drv,packt_bus_dev

struct bus_type packt_bus_type = {
	.name = "packt",
	.match = packt_device_match,
	.probe = packt_device_probe,
	.remove = packt_device_remove,
	.shutdown = packt_device_shutdown,
}

struct device packt_bus = {
	.parent = NULL,
	.release = packt_bus_release,
}

static int __init packt_init(void)
{
	int status;
	status = bus_register(&packt_bus_type);
	if(status < 0)
		goto err0;

	status = class_register(&packt_master_class);
	if(status < 0)
		goto err1;
	
	device_register(&packt_bus);

	return 0;

err1:
	bus_unregister(&packt_bus_type);
err0:
	return status;

}
module_init(packt_init);
EXPORT_SYMBOL(packt_bus );
EXPORT_SYMBOL(packt_bus_type );

在这个模块中,我们定义了一个BUS的实体,和一个DEV的实体,并静态化处理。
在init函数被调用时,将BUS和DEV注册到内核中。
错误处理代码,用的是堆栈式回滚处理方式。
当device_register被调用时,内核会遍历DRIVER,所以这个模块的依赖模块,是下面这个DRV模块。

struct packt_device {
	struct device dev;
	long price;
}
struct packt_driver{
	struct device_driver driver;
	int (*probe)(struct packt_device * packt);
	int (*remove)(struct packt_device * packt);
}
static packt_driver packt_drv = {
	.driver = {},
}
static packt_device packt_dev = {
	.dev = {},
}

int packt_register_driver(struct packt_driver *drv)
{
	int ret;
	drv.driver.bus = & packt_bus_type;
	ret = driver_register(&drv.driver);
	return ret;
}
int packt_device_register(struct packt_device * packt)
{
	int ret;
	packt.dev.parent = &parent_bus;
	packt.dev.bus = &packt_bus_type;
	
	ret = device_register(&packt.dev);
	return ret;

}
static int __init packt_driver_init(void)
{
	int status;
	status = packt_register_driver(&packt_drv);
	if(status < 0)
		goto err0;

	packt_device_register(&packt_dev );

	return 0;

err0:
	return status;

}

module_init(packt_driver_init);
EXPORT_SYMBOL(packt_register_driver);
EXPORT_SYMBOL(packt_device_register);

这个例子中,对BUS,DEV,DRV进行了扩展,衍生了一些数据成员,用来进行具体的驱动任务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值