1. 程式人生 > >Linux下驅動:分層、分離機制學習筆記

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函式成員。

相關推薦

Linux驅動:分層分離機制學習筆記

 一、分層、分離的概念         分層即把硬體相關和相對穩定的東西給抽出來,並向上提供統一的介面,每一層專注於自己的事情,比如輸入子系統。後續文章中會繼續介紹相關例子;        分離即把硬體相關和相對穩定的東西給分離開來,實際上即是bus-dev-drv模型(平

Linux驅動模塊學習

14. 移除 return 加載 控制器 就是 偏移地址 模塊插入 出現 1.modutils中提供了相關的insmod,rmmod,modinfo工具2.modprobe在識別出目標模塊所依賴模塊後也是調用insmod.3.從外部看模塊只是普通可重定位的目標文件。可重定位

PX4概念學習(1)——Linux多程序多執行緒基礎

【學習Freeape大神的uORB時,乘機補補有關Linux多程序、多執行緒的知識】 uORB(Micro Object Request Broker,微物件請求代理器)是PX4/Pixhawk系統中非常重要且關鍵的一個模組,它肩負了整個系統的資料傳輸任務,所有的感測器資料

linux多執行緒同步機制之訊號量互斥量讀寫鎖條件變數

之前有寫過類似的部落格,這東西不用老忘,現在又有更清晰的理解了。 一、訊號量 編譯時候加入-lrt 訊號量最基本的兩個操作就是PV操作:P()操作實現訊號量減少,V()操作實現訊號量的增加 訊號量的值取決於訊號量的型別,訊號量的型別有多種: (1)二進位制訊號量:0與1.

linux導入導出mysql數據庫命令

數據庫 選擇 sql數據庫 用戶 目錄 sql文件 utf linux下 eat 一、導出數據庫用mysqldump命令(註意mysql的安裝路徑,即此命令的路徑):1、導出數據和表結構:mysqldump -u用戶名 -p密碼 數據庫名 > 數據庫名.sql#/us

Linux同步模式異步模式阻塞調用非阻塞調用總結

center 工作 目前 試圖 本質 建議 其他 滿足 並不會 同步和異步:與消息的通知機制有關。 本質區別 現實例子 同步模式 由處理消息者自己去等待消息是否被觸發 我去銀行辦理業務,選擇排隊等,排到頭了就辦理。 異步模式

Linux分區格式化自動掛載

-1 tar code sudo mkf mount -a etc 目錄 detail 說明:現在硬盤基本沒有了IDE,所以基本是從SCSI開始說起,第一塊硬盤標示為sda,第二塊為sdb,以此類推。那麽第一塊硬盤的第一個分區為sda1,也是以此類推。 一、硬盤分區

Linux監視GPUCPU的使用情況

inux volatile 情況 顯存 相關信息 查看 計算 display AR 1、在運行Tensorflow等程序時會使用到NVIDIA的GPU,所以在程序運行時需要監控GPU的運行情況 使用 nvidia-smi 命令 ,顯示如下: nvidia-smi 顯

Linux查看關閉及開啟防火墻命令

star firewall 永久 NPU accept 查看 說明 linu -s Linux下查看、關閉及開啟防火墻命令 1)永久性生效,重啟後不會復原 開啟: chkconfig iptables on 關閉: chkconfig iptables off 2)即時生效

linux的sortuniqjoin的使用

去除 列數 tro 可見 ron family style 小寫字母 vertica >>>Sort 命令常用相關選項:選項說明-c會檢查文件順序是否已排好序,如果亂序,則輸出第一個亂序的行的相關信息,然後返回1-k指定排序的列數-b忽略每一行前面所有的空

LinuxNginx實現負載均衡 Nginx學習系列之搭建環境

關於在本地虛擬機器(VMware 14)下安裝Linux同時安裝Nginx,請參考Nginx學習系列之搭建環境 1、啟動Nginx 在Nginx安裝成功的前提下,啟動Nginx 已root模式登陸(許可權需要),接著找到Nginx的安裝目錄,啟動Nginx,並且指定Nginx啟動所需的配置檔案,該檔

linux操作mysqlnginx,vim操作,檔案許可權設定等

inux下操作mysql、nginx,vim操作,檔案許可權設定等 #在Linux系統下,預設所有系統配置檔案都在/etc這個路徑下的 #Linux環境下安裝mysql資料庫 ##1、切換到root許可權下,採用yum命令安裝 同時安裝mariadb的客戶端和服務端

Linux使用者使用者組檔案許可權學習筆記

最近打算更仔細學習一下linux作業系統。先是惡補了一下使用者、使用者組、檔案許可權這三樣比較重要的知識。學習這幾樣東西,得先掌握linux的許可權系統相關知識。linux的許可權系統主要是由使用者、使用者組和許可權組成。使用者就是一個個的登入並使用linux的使用者。linux內部用UID表示。使用者組就是

Linux靜態庫動態庫的建立和使用

Linux下靜態庫、動態庫的建立和使用 Linux庫檔名由:字首lib、庫名和字尾3部分組成,靜態庫通常以.a作為字尾,動態庫以.so作為字尾, Linux下把動態庫叫做共享庫,so即shared object的縮寫。 靜態庫是程式編譯連結時使用,動態庫是程式執行時使用。

linux檢視CPU記憶體磁碟資訊,資料夾資訊,GPU資訊

1、檢視CPU資訊 # 總核數 = 物理CPU個數 X 每顆物理CPU的核數  # 總邏輯CPU數 = 物理CPU個數 X 每顆物理CPU的核數 X 超執行緒數 # 檢視物理CPU個數 cat /proc/cpuinfo| grep "physical id"| sort| uniq| w

Linux配置MySQLMongoDBTomcat等軟體和使用

配置阿里雲的yum倉庫 獲取倉庫配置 wgCentos-7.repoet http://mirrors.aliyun.com/repo/ 修改Linux預設的yum配置 將Centos-7.repo中的內容拷貝到/etc/yum.repo.d/CentOS-Base.repo檔案

LINUX檢視CPU記憶體使用率的命令

1.top 使用許可權:所有使用者 使用方式:top [-] [d delay] [q] [c] [S] [s] [i] [n] [b] 說明:即時顯示process的動態 d :改變顯示的更新速度,或是在交談式指令列( interactive command)按s

2018/11/26 linux 基本概念基本語法

今天整理了一些之前linux零碎的知識點,加深理解,下面這幾張圖是學習綱要 1 Unix:1969   linux:1991    託瓦茲在老師minx的基礎上開發linux 2Hadoop 、Spark、 scala都是用 java開發的

linux保護視力定時強制鎖定軟體: Workrave

  超負荷地工作會累壞身體的,而且效率也不高,所以工作一段時間就應該休息一下。長時間在電腦前一動不動,很容易患上“重複性勞損”,即 Repetitive Strain Injury (RSI)。具體現象大家一定有過體會,上機一段時間後肩膀、脖子、手臂痠痛,眼睛模糊等等。 WorkRav

Linux標準I/O緩衝機制

平臺:Ubuntu作業系統 編譯器:g++ 首先讓我們看一段程式碼: #include <iostream> #include <unistd.h> using namespace std; int main() { for (int i