Linux下驱动:分层、分离机制学习笔记

 一、分层、分离的概念

        分层即把硬件相关和相对稳定的东西给抽出来,并向上提供统一的接口,每一层专注于自己的事情,比如输入子系统。后续文章中会继续介绍相关例子;

       分离即把硬件相关和相对稳定的东西给分离开来,实际上即是bus-dev-drv模型(平台总线、平台设备、平台驱动),本文章中的led驱动即使用了此模型。

二、bus-dev-drv模型

     

       在硬件相关部分(即硬件操作的部分,其实在drv中也会有操作硬件,只是基本上不用改动,比较稳定。要改硬件,一般改dev部分代码即可。),入口函数中会调用platform_device_register→platform_device_add→device_add,device_add函数的作用如下:

(1)把device(结构体)放入bus(结构体)的dev链表中去。即注册。

(2)从bus(结构体)的drv(结构体)链表中取出每个drv,用bus的match函数判断drv能否支持此dev。

(3)如果支持(实际上是判断结构的.name成员是否一样),调用drv(结构体)的probe函数。

       在driver相关部分,会向上注册一个结构体,实际上就是调用platform_driver_register函数→driver_register函数把driver结构体放到bus 里(结构体)的driver链表里:

(1)把driver(结构体)放入bus(结构体)的drv链表中去。 即注册。

(2)从bus(结构体)的dev(结构体)链表中取出每个dev,跟此drv一一比较(用bus的match函数去比较)。

(3)如果支持,调用drv(结构体)的probe函数。

        总的来说,上述只不过是左右建立一种联系的机制,在probe里面做什么完全由自己决定,比如打印一句话,注册一个字符设备,或者注册一个input_dev结构体。如果要修改硬件,只需要修改硬件相关的代码即可,右边比较稳定的代码可以不动,这样大家就有一种约定,其实不管是device还是driver(它们只是一个结构体而已)。

三、bus-dev-drv模型具体实例

      以一个LED驱动的例子说明分层分离的概念,源码分为led_dev.c,led_drv.c,led_test.c。led_dev.c为硬件相关,led_drv.c为右边较稳定的部分代码,led_test.c为应用程序测试代码。基本功能是点亮或关闭LED灯。

     其实这个程序完全可以用简单的字符型驱动程序框架实现,这里只是为了理解平台总线、设备、驱动模型,而用此框架实现的驱动。具体代码如下:

led_dev.c :     

</pre><pre name="code" class="cpp">/* 分配/设置/注册一个platform_device */
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/timer.h>
#include <linux/init.h>
#include <linux/serial_core.h>
#include <linux/platform_device.h>

/*   "~~~~~~~~~~~Tiny4412支持的Linux3.5中不需要用资源的方式~~~~~~~~~~~"
static struct resource led_resource[] = {
	//寄存器起始地址,如果要换硬件寄存器,只需要在这里修改即可
    [0] = {
        .start = 0x110002E0,
        .end   = 0x110002E0 + 8 - 1,//结束地址
        .flags = IORESOURCE_MEM,//表示哪类资源
    },
	//哪根引脚,如果要换一个led亮,只需要修改下面的数字即可
    [1] = {
        .start = 0,//Tiny4412中GPM4(0-3)分别接led0-3,所以0、1、2、3分别表示led1、led2、led3、led4
        .end   = 0,
        .flags = IORESOURCE_IRQ,
    }

};*/

static void led_release(struct device * dev)
{
}

static struct platform_device led_dev = {
    .name         = "myled",//这个名字要和led_dev.c中的相同
    .id       = -1,
    //.num_resources    = ARRAY_SIZE(led_resource),
	.num_resources    = 0,
    //.resource     = led_resource,
    .dev = { 
    	.release = led_release, //如果不提供此函数,在卸载模块时会报错
	},
};

static int led_dev_init(void)
{
	//platform_device_register→platform_device_add→device_add即把设备放入平台总线里的设备链表中去
	//即加载lsmod led_dev.ko后,会调用driver的probe函数。
	platform_device_register(&led_dev);
	return 0;
}

static void led_dev_exit(void)
{
	/*
	  rmmod  led_dev模块后,调用此函数,而此函数会从bus总线的dev链表中找出此设备,然后
	  去掉并根据match函数找到对应的drv,然后调用里面(drv结构体)的remove函数做些
	  清理工作*/
	platform_device_unregister(&led_dev);//会调用platform_driver中led_remove函数
}
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");


led_drv.c : 

/* 分配/设置/注册一个platform_driver */
#include <linux/version.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>

#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>

#define DEVICE_NAME "bus_dev_drv_leds"
#define LED_NUM		ARRAY_SIZE(led_gpios)
static int led_gpios[] = {
	EXYNOS4X12_GPM4(0),
	EXYNOS4X12_GPM4(1),
	EXYNOS4X12_GPM4(2),
	EXYNOS4X12_GPM4(3),
};
/*
static int major;
static struct class *cls;
static volatile unsigned long *gpio_con;
static volatile unsigned long *gpio_dat;
static int pin;
*/

static int led_open(struct inode *inode, struct file *file)
{
	printk("Kernel:bus_dev_drv_leds OPEN\n");
	/*
	// 配置为输出 
	*gpio_con &= ~(0x3<<(pin*2));
	*gpio_con |= (0x1<<(pin*2));*/
	return 0;	
}

static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
	int val;

	printk("Kernel:dev_drv_leds WRITE\n");

	if(copy_from_user(&val, buf, count)) //	copy_to_user();
	{
		printk("LED_DRV:copy_from_user ERR!\n");
		return -1;
	}

	if (val == 1)
	{
		// 点灯
		//*gpio_dat &= ~(1<<pin);
		gpio_set_value(led_gpios[0], 0);
	}
	else
	{
		// 灭灯
		//*gpio_dat |= (1<<pin);
		gpio_set_value(led_gpios[0], 1);
	}
	
	return 0;
}

/*
static struct file_operations led_fops = {
    .owner  =   THIS_MODULE,    // 这是一个宏,推向编译模块时自动创建的__this_module变量 
    .open   =   led_open,     
	.write	=	led_write,	   
};*/

static struct file_operations tiny4412_led_dev_fops = {
	.owner			= THIS_MODULE,
	//.unlocked_ioctl	= tiny4412_leds_ioctl,
	.open   =   led_open,     
	.write	=	led_write,
};

static struct miscdevice tiny4412_led_dev = {
	.minor			= MISC_DYNAMIC_MINOR,
	.name			= DEVICE_NAME,
	.fops			= &tiny4412_led_dev_fops,
};

static int led_probe(struct platform_device *pdev)//这里面想做什么就做什么,用户决定
{
	/*struct resource		*res;

	//根据platform_device的资源进行ioremap 
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	gpio_con = ioremap(res->start, res->end - res->start + 1);
	gpio_dat = gpio_con + 1;//指针加一相当于加4,即指向另外一个寄存器

	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
	pin = res->start;*/
	
	int ret;
	int i;
	for (i = 0; i < LED_NUM; i++) {
		ret = gpio_request(led_gpios[i], "LED");
		if (ret) {
			printk("%s: request GPIO %d for LED failed, ret = %d\n", DEVICE_NAME,
					led_gpios[i], ret);
			return ret;
		}

		s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT);
		gpio_set_value(led_gpios[i], 1);
	}
	
	/* 注册字符设备驱动程序 */
	printk("led_drv: led_probe, found led\n");
	misc_register(&tiny4412_led_dev);
/*
	major = register_chrdev(0, "myled", &led_fops);
	cls = class_create(THIS_MODULE, "myled");
	class_device_create(cls, NULL, MKDEV(major, 0), NULL, "led"); //设备文件dev/led 
*/	
	return 0;
}

static int led_remove(struct platform_device *pdev)
{
	/* 卸载字符设备驱动程序 */	
	int i;
	printk("led_drv: led_remove, remove led\n");
	
	for (i = 0; i < LED_NUM; i++) {
		gpio_free(led_gpios[i]);
	}
	misc_deregister(&tiny4412_led_dev);
	/*
	class_device_destroy(cls, MKDEV(major, 0));
	class_destroy(cls);
	unregister_chrdev(major, "myled");
	iounmap(gpio_con);
	*/
	return 0;
}

struct platform_driver led_drv = {
	.probe		= led_probe,
	.remove		= led_remove,
	.driver		= {
		.name	= "myled",//这个名字要和led_dev.c中的相同
	}
};

static int led_drv_init(void)
{
	platform_driver_register(&led_drv);
	return 0;
}

static void led_drv_exit(void)
{
	platform_driver_unregister(&led_drv);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");

led_test.c : 

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

/* led_test on
 * led_test off
 */
int main(int argc, char **argv)
{
	int fd;
	int val = 1;
	fd = open("/dev/bus_dev_drv_leds", O_RDWR);
	if (fd < 0)
	{
		printf("can't open!\n");
	}
	if (argc != 2)
	{
		printf("Usage :\n");
		printf("%s <on|off>\n", argv[0]);
		return 0;
	}

	if (strcmp(argv[1], "on") == 0)
	{
		val  = 1;
	}
	else
	{
		val = 0;
	}
	
	write(fd, &val, 4);
	return 0;
}


Makefile:

KERN_DIR = /home/samba/linuxKernel_ext4Fs_src/linux-3.5-2015-8

all:
	make -C $(KERN_DIR) M=`pwd` modules 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-m	+= led_drv.o
obj-m	+= led_dev.o

 

       如果是先装载led_dev,会把dev加入bus的设备链表中去,然后找对应的drv,此时找不到没关系,等装载了led_drv后,会把drv加入到bus的driver链表中去,然后会在dev链表中找一遍有没有匹配的dev(这样就相当于再找了一遍,只是找的是dev而已,如果找到,也将调用drv的probe函数。所以,不管是在dev链表中找设备还是在drv链表中找驱动,只要找到了,都会调用drv中的probe函数,这样设备和驱动可以不同时加载,也可不分顺序加载,通过试验也表明,不管先装载led_drv.ko还是先装载led_dev.ko,都是等两个都装载了后才会匹配并调用drv的probe函数。

        所以说,不管先加载led_dev.ko还是led_drv.ko,只要匹配了,就会调用drv结构的probe函数成员。不管先卸载led_dev还是led_drv,凡是第一个卸载后,就会调用drv结构的led_remove函数成员。注意,本文是学校韦东山老师驱动视频后做的笔记,方便后面参考。

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Linux驱动分离分层是指将不同的驱动程序分离开来,分别放置在不同的层次结构中,以便于管理和维护。这样可以使得不同的驱动程序之间相互独立,互不干扰,同时也可以提高系统的可靠性和稳定性。在Linux系统中,驱动程序一般分为以下几个层次:硬件层、设备驱动层、总线驱动层、文件系统层等。每个层次都有其特定的功能和作用,通过分层的方式可以使得驱动程序的开发和维护更加方便和高效。 ### 回答2: Linux驱动分离分层是指将Linux内核中的驱动程序分成不同的功能层级并独立开发的过程。在Linux开发社区中,这种分离分层的开发模式被广泛采纳,以提高整个系统的稳定性和可维护性。 Linux驱动分离分层主要包括以下几个层级: 1. 用户空间驱动层:用户空间驱动程序是指运行在用户空间的驱动程序,如USB驱动等。这些驱动程序通常在内核提供的接口上实现,通过系统调用和驱动程序接口对硬件进行操作。 2. 设备驱动层:设备驱动层是指连接硬件和内核的驱动程序,用来管理计算机的输入和输出设备。这一层级的驱动程序通常是编写最复杂的。 3. 驱动控制器层:驱动控制器层是指连接设备驱动程序和硬件的控制器层,比如PCI控制器、DMA控制器等。这些控制器集成在计算机的芯片组中,工作在硬件和软件之间的接口。 4. 硬件抽象层:硬件抽象层是指对硬件的抽象描述,这一层级的驱动程序提供了一个统一的接口来存取硬件设备。 总体来说,Linux驱动分离分层可以提高系统的可维护性和可靠性,同时也为内核扩展提供了可靠的机制。因此,Linux开发社区鼓励发展和维护一个分离分层驱动程序生态系统。 ### 回答3: 在Linux系统中,驱动程序是操作系统与硬件之间的桥梁,它们向操作系统提供了与硬件设备交互的能力。为了使Linux操作系统的驱动程序更加模块化和灵活,Linux设备驱动在设计时采用了分离分层的策略。 分离是指将不同类型的驱动程序放在不同的模块中,这样可以让相似的驱动程序共享一些关键代码,减少代码量,并且提高了代码的重用性。对于Linux系统中的设备,常见的驱动程序有字符设备驱动、块设备驱动以及网络设备驱动等。每种类型的驱动程序都有自己的特点和工作方式,因此将它们分离出来更便于管理和维护。 分层是指将驱动程序设计成多个层次,每个层次依赖于下一个层次,以便在需要时实现相应的功能。通常把驱动程序分为五个层次:应用程序接口层、驱动程序核心层、总线接口层、体系结构层和硬件控制层。每个层次的驱动程序都有自己的职责和功能,它们通过下一层向上提供服务,并从上一层向下接收请求。 应用程序接口层是驱动程序提供给应用程序的接口,应用程序可以通过这些接口来控制硬件设备。驱动程序核心层是驱动程序的核心部分,它在驱动程序的设计中起到桥梁的作用,它管理整个驱动程序的请求过程,同时也与硬件设备交互。总线接口层负责管理总线设备的操作,比如对于PCI总线来说,总线接口层可以管理PCI卡中的设备。体系结构层是根据系统体系结构设计的驱动程序层次,比如ARM体系结构、MIPS体系结构等。硬件控制层是直接与硬件设备交互的层次,它负责整个驱动程序对硬件设备的控制。 总之,Linux设备驱动程序的分离分层策略可以提高驱动程序的模块化和灵活性,同时也更利于维护和管理驱动程序。这种策略已经被广泛应用于各种Linux系统中,使得Linux操作系统更加强大和稳定。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值