1. 程式人生 > >《5.linux驅動開發-第2部分-5.2.字元裝置驅動基礎》

《5.linux驅動開發-第2部分-5.2.字元裝置驅動基礎》

《5.linux驅動開發-第2部分-5.2.字元裝置驅動基礎》

第一部分、章節目錄
5.2.1.開啟驅動開發之路
5.2.2.最簡單的模組原始碼分析1
5.2.3.最簡單的模組原始碼分析2
5.2.4.最簡單的模組原始碼分析3
5.2.5.用開發板來除錯模組
5.2.6.字元裝置驅動工作原理1
5.2.7.字元裝置驅動工作原理2
5.2.8.字元裝置驅動程式碼實踐1
5.2.9.字元裝置驅動程式碼實踐2
5.2.10.應用程式如何呼叫驅動
5.2.11.新增讀寫介面
5.2.12.讀寫介面實踐
5.2.13.驅動中如何操控硬體
5.2.14.靜態對映操作LED1
5.2.15.靜態對映操作LED2
5.2.16.靜態對映操作LED3
5.2.17.動態對映操作LED

第二部分、章節介紹
5.2.1.開啟驅動開發之路
本節主要是講述並且實踐示範驅動開發的環境搭建、構建核心原始碼樹、常用模組安裝解除安裝命令等。
5.2.2.最簡單的模組原始碼分析1
本節主要講解模組安裝和過程,及其和module_init巨集的關聯,並且重點講解了模組安裝時的安全性相容性校驗問題。
5.2.3.最簡單的模組原始碼分析2
本節主要講解了模組的解除安裝過程、MODULE_LICENSE等資訊新增巨集和__init、__exit巨集。
5.2.4.最簡單的模組原始碼分析3
本節主要分析了printk函式及其列印級別定義,以及模組編譯的Makefile檔案工作原理。
5.2.5.用開發板來除錯模組
本節在開發板上搭建模組測試的環境,主要是核心原始碼樹的編譯、核心的下載、nfs掛載rootfs的設定,uboot中bootcmd和bootargs的設定等細節。
5.2.6.字元裝置驅動工作原理1
本節開始講解字元裝置驅動的工作原理,重點是file_operations結構體及整個應用+驅動體系的框架。
5.2.7.字元裝置驅動工作原理2
本節講解了register_chrdev函式,並且重點講解了核心內部對字元裝置驅動的管理機制。
5.2.8.字元裝置驅動程式碼實踐1
本節開始字元裝置驅動的編碼實踐,首先把整體框架理順,然後構建了file_operations結構體變數,並且填充了open和close方法。
5.2.9.字元裝置驅動程式碼實踐2
本節進行字元裝置驅動的註冊,以及編譯和實際在開發板上的測試。
5.2.10.應用程式如何呼叫驅動
本節引入裝置檔案的概念,同時結合前面講述的主次裝置號,綜合講解應用程式對驅動程式的呼叫等整體流程。
5.2.11.新增讀寫介面
本節開始在我們的驅動中新增write和read方法,並且講解了copy_from_user和copy_to_user這兩個重要函式。
5.2.12.讀寫介面實踐
本節完成write和read函式的編寫,並且在開發板上進行實踐測試。
5.2.13.驅動中如何操控硬體
本節研究驅動中對硬體的操作和裸機中的不同,並且重點講解了linux核心中虛擬地址對映的2種機制和原理。
5.2.14.靜態對映操作LED1
本節講解我們所使用的核心版本中和靜態虛擬地址對映有關的檔案,並且帶大家實際分析這部分程式碼,以深入理解靜態對映表的實質。
5.2.15.靜態對映操作LED2
本節使用靜態虛擬地址對映方法,參考裸機中的LED控制方法,在驅動中新增LED的操作方法,在對比中讓大家理解驅動和裸機的區別。
5.2.16.靜態對映操作LED3
本節結合應用程式和驅動程式,實現一整套控制LED亮滅的完整體系。讓大家初步體會到應用和驅動的配合。
5.2.17.動態對映操作LED
本節介紹並使用動態對映的方式對暫存器進行對映,然後實現應用操作,主要目的是學習動態核心對映的原理和實踐方法。

第三部分、隨堂記錄
5.2.1.開啟驅動開發之路
5.2.1.1、驅動開發的準備工作
(1)正常執行linux系統的開發板。要求開發板中的linux的zImage必須是自己編譯的,不能是別人編譯的。
(2)核心原始碼樹,其實就是一個經過了配置編譯之後的核心原始碼。
(3)nfs掛載的rootfs,主機ubuntu中必須搭建一個nfs伺服器。
5.2.1.2、驅動開發的步驟
(1)驅動原始碼編寫、Makefile編寫、編譯
(2)insmod裝載模組、測試、rmmod解除安裝模組
5.2.1.3、實踐
(1)copy原來提供的x210kernel.tar.bz2,找一個乾淨的目錄(/root/driver),解壓之,並且配置編譯。編譯完成後得到了:1、核心原始碼樹。2、編譯ok的zImage
(2)fastboot將第1步中得到的zImage燒錄到開發板中去啟動(或者將zImage丟到tftp的共享目錄,uboot啟動時tftp下載啟動),將來驅動編譯好後,就可以在這個核心中去測試。因為這個zImage和核心原始碼樹是一夥的,所以驅動安裝時版本校驗不會出錯。

    #ubuntu的核心原始碼樹,如果要編譯在ubuntu中安裝的模組就開啟這2個
    #KERN_VER = $(shell uname -r)
    #KERN_DIR = /lib/modules/$(KERN_VER)/build	
    
    		
    # 開發板的linux核心的原始碼樹目錄
    KERN_DIR = /home/qwer/AIO-RK3399-J/kernel
    
    obj-m	+= module_test.o
    
    all:
    	make -C $(KERN_DIR) M=`pwd` modules 
    
    cp:
    	cp *.ko /root/porting_x210/rootfs/rootfs/driver_test
    
    .PHONY: clean	
    clean:
    	make -C $(KERN_DIR) M=`pwd` modules clean

   #include <linux/module.h>		// module_init  module_exit
   #include <linux/init.h>			// __init   __exit
   
   // 模組安裝函式
   static int __init chrdev_init(void)
   {	
   	printk(KERN_INFO "chrdev_init helloworld init\n");
   	//printk("<7>" "chrdev_init helloworld init\n");
   	//printk("<7> chrdev_init helloworld init\n");
   
   	return 0;
   }
   
   // 模組下載函式
   static void __exit chrdev_exit(void)
   {
   	printk(KERN_INFO "chrdev_exit helloworld exit\n");
   }
   
   
   module_init(chrdev_init);
   module_exit(chrdev_exit);
   
   // MODULE_xxx這種巨集作用是用來新增模組描述資訊
   MODULE_LICENSE("GPL");				// 描述模組的許可證
   MODULE_AUTHOR("aston");				// 描述模組的作者
   MODULE_DESCRIPTION("module test");	// 描述模組的介紹資訊
   MODULE_ALIAS("alias xxx");			// 描述模組的別名資訊

5.2.2.最簡單的模組原始碼分析1
5.2.2.1、常用的模組操作命令
(1)lsmod(list module,將模組列表顯示),功能是打印出當前核心中已經安裝的模組列表
(2)insmod(install module,安裝模組),功能是向當前核心中去安裝一個模組,用法是insmod xxx.ko
(3)modinfo(module information,模組資訊),功能是打印出一個核心模組的自帶資訊。,用法是modinfo xxx.ko
(4)rmmod(remove module,解除安裝模組),功能是從當前核心中解除安裝一個已經安裝了的模組,用法是rmmod xxx(注意解除安裝模組時只需要輸入模組名即可,不能加.ko字尾)
(5)剩下的後面再說,暫時用不到(如modprobe、depmod等)
5.2.2.2、模組的安裝
(1)先lsmod再insmod看安裝前後系統內模組記錄。實踐測試標明核心會將最新安裝的模組放在lsmod顯示的最前面。
(2)insmod與module_init巨集。模組原始碼中用module_init巨集聲明瞭一個函式(在我們這個例子裡是chrdev_init函式),作用就是指定chrdev_init這個函式和insmod命令繫結起來,也就是說當我們insmod module_test.ko時,insmod命令內部實際執行的操作就是幫我們呼叫chrdev_init函式。
照此分析,那insmod時就應該能看到chrdev_init中使用printk打印出來的一個chrdev_init字串,但是實際沒看到。原因是ubuntu中攔截了,要怎麼才能看到呢?在ubuntu中使用dmesg命令就可以看到了。
(3)模組安裝時insmod內部除了幫我們呼叫module_init巨集所宣告的函式外,實際還做了一些別的事(譬如lsmod能看到多了一個模組也是insmod幫我們在內部做了記錄),但是我們就不用管了。
5.2.2.3、模組的版本資訊
(1)使用modinfo檢視模組的版本資訊
(2)核心zImage中也有一個確定的版本資訊
(3)insmod時模組的vermagic必須和核心的相同,否則不能安裝,報錯資訊為:insmod: ERROR: could not insert module module_test.ko: Invalid module format
(4)模組的版本資訊是為了保證模組和核心的相容性,是一種安全措施
(5)如何保證模組的vermagic和核心的vermagic一致?編譯模組的核心原始碼樹就是我們編譯正在執行的這個核心的那個核心原始碼樹即可。說白了就是模組和核心要同出一門。

5.2.3.最簡單的模組原始碼分析2
5.2.3.1、模組解除安裝
(1)module_exit和rmmod的對應關係
(2)lsmod檢視rmmod前後系統的模組記錄變化
5.2.3.2、模組中常用巨集
(1)MODULE_LICENSE,模組的許可證。一般宣告為GPL許可證,而且最好不要少,否則可能會出現莫名其妙的錯誤(譬如一些明視訊記憶體在的函式提升找不到)。
(2)MODULE_AUTHOR
(3)MODULE_DESCRIPTION
(4)MODULE_ALIAS
5.2.3.3、函式修飾符
(1)__init,本質上是個巨集定義,在核心原始碼中就有#define __init xxxx。這個__init的作用就是將被他修飾的函式放入.init.text段中去(本來預設情況下函式是被放入.text段中)。
整個核心中的所有的這類函式都會被連結器連結放入.init.text段中,所以所有的核心模組的__init修飾的函式其實是被統一放在一起的。核心啟動時統一會載入.init.text段中的這些模組安裝函式,載入完後就會把這個段給釋放掉以節省記憶體。
(2)__exit
5.2.3.4、static

5.2.4.最簡單的模組原始碼分析3
5.2.4.1、printk函式詳解
(1)printk在核心原始碼中用來列印資訊的函式,用法和printf非常相似。
(2)printk和printf最大的差別:printf是C庫函式,是在應用層程式設計中使用的,不能在linux核心原始碼中使用;printk是linux核心原始碼中自己封裝出來的一個列印函式,是核心原始碼中的一個普通函式,只能在核心原始碼範圍內使用,不能在應用程式設計中使用。
(3)printk相比printf來說還多了個:列印級別的設定。printk的列印級別是用來控制printk列印的這條資訊是否在終端上顯示的。應用程式中的除錯資訊要麼全部開啟要麼全部關閉,一般用條件編譯來實現(DEBUG巨集),但是在核心中,因為核心非常龐大,列印資訊非常多,有時候整體除錯核心時列印資訊要麼太多找不到想要的要麼一個沒有沒法除錯。所以才有了列印級別這個概念。
(4)作業系統的命令列中也有一個列印資訊級別屬性,值為0-7。當前作業系統中執行printk的時候會去對比printk中的列印級別和我的命令列中設定的列印級別,小於我的命令列設定級別的資訊會被放行打印出來,大於的就被攔截的。譬如我的ubuntu中的列印級別預設是4,那麼printk中設定的級別比4小的就能打印出來,比4大的就不能打印出來。
(5)ubuntu中這個printk的列印級別控制沒法實踐,ubuntu中不管你把級別怎麼設定都不能直接打印出來,必須dmesg命令去檢視。
5.2.4.2、關於驅動模組中的標頭檔案
(1)驅動原始碼中包含的標頭檔案和原來應用程式設計程式中包含的標頭檔案不是一回事。應用程式設計中包含的標頭檔案是應用層的標頭檔案,是應用程式的編譯器帶來的(譬如gcc的標頭檔案路徑在 /usr/include下,這些東西是和作業系統無關的)。驅動原始碼屬於核心原始碼的一部分,驅動原始碼中的標頭檔案其實就是核心原始碼目錄下的include目錄下的標頭檔案。

5.2.4.3、驅動編譯的Makefile分析
(1)KERN_DIR,變數的值就是我們用來編譯這個模組的核心原始碼樹的目錄
(2)obj-m += module_test.o,這一行就表示我們要將module_test.c檔案編譯成一個模組
(3)make -C $(KERN_DIR) M=pwd modules 這個命令用來實際編譯模組,工作原理就是:利用make -C進入到我們指定的核心原始碼樹目錄下,然後在原始碼目錄樹下借用核心原始碼中定義的模組編譯規則去編譯這個模組,編譯完成後把生成的檔案還拷貝到當前目錄下,完成編譯。
(4)make clean ,用來清除編譯痕跡
總結:模組的makefile非常簡單,本身並不能完成模組的編譯,而是通過make -C進入到核心原始碼樹下借用核心原始碼的體系來完成模組的編譯連結的。這個Makefile本身是非常模式化的,3和4部分是永遠不用動的,只有1和2需要動。1是核心原始碼樹的目錄,你必須根據自己的編譯環境

5.2.5.用開發板來除錯模組
5.2.5.1、設定bootcmd使開發板通過tftp下載自己建立的核心原始碼樹編譯得到的zImage
set bootcmd ‘tftp 0x30008000 zImage;bootm 0x30008000’

5.2.5.2、設定bootargs使開發板從nfs去掛載rootfs(核心配置記得開啟使能nfs形式的rootfs)
setenv bootargs root=/dev/nfs nfsroot=192.168.1.141:/root/porting_x210/rootfs/rootfs ip=192.168.1.10:192.168.1.141:192.168.1.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC2,115200

5.2.5.3、修改Makefile中的KERN_DIR使其指向自己建立的核心原始碼樹
5.2.5.4、將自己編譯好的驅動.ko檔案放入nfs共享目錄下去
5.2.5.5、開發板啟動後使用insmod、rmmod、lsmod等去進行模組實驗

5.2.6.字元裝置驅動工作原理1
5.2.6.1、系統整體工作原理
(1)應用層->API->裝置驅動->硬體
(2)API:open、read、write、close等
(3)驅動原始碼中提供真正的open、read、write、close等函式實體
5.2.6.2、file_operations結構體
(1)元素主要是函式指標,用來掛接實體函式地址
(2)每個裝置驅動都需要一個該結構體型別的變數
(3)裝置驅動向核心註冊時提供該結構體型別的變數
5.2.6.3、註冊字元裝置驅動
(1)為何要註冊驅動
(2)誰去負責註冊
(3)向誰註冊
(4)註冊函式從哪裡來
(5)註冊前怎樣?註冊後怎樣?註冊產生什麼結果?

5.2.7.字元裝置驅動工作原理2
5.2.7.1、register_chrdev詳解(#include <linux/fs.h>)
(1)作用,驅動向核心註冊自己的file_operations
(2)引數
(3)inline和static
5.2.7.2、核心如何管理字元裝置驅動
(1)核心中有一個數組用來儲存註冊的字元裝置驅動
(2)register_chrdev內部將我們要註冊的驅動的資訊(主要是 )儲存在陣列中相應的位置
(3)cat /proc/devices檢視核心中已經註冊過的字元裝置驅動(和塊裝置驅動)
(4)好好理解主裝置號(major)的概念
5.2.7.3、回顧和展望
(1)回顧:inline、static等關鍵字
(2)回顧:/proc檔案系統的作用
(3)展望:將來深入學習驅動時可以去跟register_chrdev到內部看,驗證我們上面講的原理

5.2.8.字元裝置驅動程式碼實踐1
5.2.8.1、思路和框架
(1)目的:給空模組新增驅動殼子
(2)核心工作量:file_operations及其元素填充、註冊驅動
5.2.8.2、如何動手寫驅動程式碼
(1)腦海裡先有框架,知道自己要幹嘛
(2)細節程式碼不需要一個字一個字敲,可以到核心中去尋找參考程式碼複製過來改
(3)寫下的所有程式碼必須心裡清楚明白,不能似懂非懂
5.2.8.3、開始動手
(1)先定義file_operations結構體變數
(2)open和close函式原型確定、內容填充

#ubuntu的核心原始碼樹,如果要編譯在ubuntu中安裝的模組就開啟這2個
#KERN_VER = $(shell uname -r)
#KERN_DIR = /lib/modules/$(KERN_VER)/build	

		
# 開發板的linux核心的原始碼樹目錄
KERN_DIR = /root/driver/kernel

obj-m	+= module_test.o

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

cp:
	cp *.ko /root/porting_x210/rootfs/rootfs/driver_test

.PHONY: clean	
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	
#include <linux/module.h>		// module_init  module_exit
#include <linux/init.h>			// __init   __exit
#include <linux/fs.h>

#define MYMAJOR		200
#define MYNAME		"testchar"
int mymajor;

static int test_chrdev_open(struct inode *inode, struct file *file)
{
	// 這個函式中真正應該放置的是開啟這個裝置的硬體操作程式碼部分
	// 但是現在暫時我們寫不了這麼多,所以用一個printk列印個資訊來做代表。
	printk(KERN_INFO "test_chrdev_open\n");
	
	return 0;
}

static int test_chrdev_release(struct inode *inode, struct file *file)
{
	printk(KERN_INFO "test_chrdev_release\n");
	
	return 0;
}

// 自定義一個file_operations結構體變數,並且去填充
static const struct file_operations test_fops = {
	.owner		= THIS_MODULE,				// 慣例,直接寫即可
	
	.open		= test_chrdev_open,			// 將來應用open開啟這個裝置時實際呼叫的
	.release	= test_chrdev_release,		// 就是這個.open對應的函式
};


// 模組安裝函式
static int __init chrdev_init(void)
{	
	int ret = -1;
	
	printk(KERN_INFO "chrdev_init helloworld init\n");

	// 在module_init巨集呼叫的函式中去註冊字元裝置驅動
	mymajor = register_chrdev(0, MYNAME, &test_fops);
	if (mymajor)
	{
		printk(KERN_ERR "register_chrdev fail\n");
		return -EINVAL;
	}
	printk(KERN_INFO "register_chrdev success...\n");

	return 0;
}

// 模組下載函式
static void __exit chrdev_exit(void)
{
	printk(KERN_INFO "chrdev_exit helloworld exit\n");
	
	// 在module_exit巨集呼叫的函式中去登出字元裝置驅動
	unregister_chrdev(mymajor, MYNAME);
	
}


module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx這種巨集作用是用來新增模組描述資訊
MODULE_LICENSE("GPL");				// 描述模組的許可證
MODULE_AUTHOR("aston");				// 描述模組的作者
MODULE_DESCRIPTION("module test");	// 描述模組的介紹資訊
MODULE_ALIAS("alias xxx");			// 描述模組的別名資訊

5.2.9.字元裝置驅動程式碼實踐2
5.2.9.1、註冊驅動
(1)主裝置號的選擇
(2)返回值的檢測
5.2.9.2、驅動測試
(1)編譯等 make && make cp
(2)insmod並且檢視設備註冊的現象
(3)rmmod並且檢視設備註銷的現象
5.2.9.3、讓核心自動分配主裝置號
(1)為什麼要讓核心自動分配
(2)如何實現?
(3)測試

#ubuntu的核心原始碼樹,如果要編譯在ubuntu中安裝的模組就開啟這2個
#KERN_VER = $(shell uname -r)
#KERN_DIR = /lib/modules/$(KERN_VER)/build	

		
# 開發板的linux核心的原始碼樹目錄
KERN_DIR = /root/driver/kernel

obj-m	+= module_test.o

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

cp:
	cp *.ko /root/porting_x210/rootfs/rootfs/driver_test

.PHONY: clean	
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	

#include <linux/module.h>		// module_init  module_exit
#include <linux/init.h>			// __init   __exit
#include <linux/fs.h>

#define MYMAJOR		200
#define MYNAME		"testchar"

int mymajor;


static int test_chrdev_open(struct inode *inode, struct file *file)
{
	// 這個函式中真正應該放置的是開啟這個裝置的硬體操作程式碼部分
	// 但是現在暫時我們寫不了這麼多,所以用一個printk列印個資訊來做代表。
	printk(KERN_INFO "test_chrdev_open\n");
	
	return 0;
}

static int test_chrdev_release(struct inode *inode, struct file *file)
{
	printk(KERN_INFO "test_chrdev_release\n");
	
	return 0;
}

// 自定義一個file_operations結構體變數,並且去填充
static const struct file_operations test_fops = {
	.owner		= THIS_MODULE,				// 慣例,直接寫即可
	
	.open		= test_chrdev_open,			// 將來應用open開啟這個裝置時實際呼叫的
	.release	= test_chrdev_release,		// 就是這個.open對應的函式
};


// 模組安裝函式
static int __init chrdev_init(void)
{	
	printk(KERN_INFO "chrdev_init helloworld init\n");

	// 在module_init巨集呼叫的函式中去註冊字元裝置驅動
	// major傳0進去表示要讓核心幫我們自動分配一個合適的空白的沒被使用的主裝置號
	// 核心如果成功分配就會返回分配的主裝置好;如果分配失敗會返回負數
	mymajor = register_chrdev(0, MYNAME, &test_fops);
	if (mymajor < 0)
	{
		printk(KERN_ERR "register_chrdev fail\n");
		return -EINVAL;
	}
	printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor);

	return 0;
}

// 模組下載函式
static void __exit chrdev_exit(void)
{
	printk(KERN_INFO "chrdev_exit helloworld exit\n");
	
	// 在module_exit巨集呼叫的函式中去登出字元裝置驅動
	unregister_chrdev(mymajor, MYNAME);
	
}


module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx這種巨集作用是用來新增模組描述資訊
MODULE_LICENSE("GPL");				// 描述模組的許可證
MODULE_AUTHOR("aston");				// 描述模組的作者
MODULE_DESCRIPTION("module test");	// 描述模組的介紹資訊
MODULE_ALIAS("alias xxx");			// 描述模組的別名資訊

5.2.10.應用程式如何呼叫驅動
5.2.10.1、驅動裝置檔案的建立
(1)何為裝置檔案
(2)裝置檔案的關鍵資訊是:裝置號 = 主裝置號 + 次裝置號,使用ls -l去檢視裝置檔案,就可以得到這個裝置檔案對應的主次裝置號。
(3)使用mknod建立裝置檔案:mknod /dev/xxx c 主裝置號 次裝置號
5.2.10.2、寫應用來測試驅動
(1)還是原來的應用
(2)open、write、read、close等
(3)實驗現象預測和驗證
5.2.10.3、總結
(1)整體流程梳理、注意分層
(2)後續工作:新增讀寫介面

#ubuntu的核心原始碼樹,如果要編譯在ubuntu中安裝的模組就開啟這2個
#KERN_VER = $(shell uname -r)
#KERN_DIR = /lib/modules/$(KERN_VER)/build	

		
# 開發板的linux核心的原始碼樹目錄
KERN_DIR = /root/driver/kernel

obj-m	+= module_test.o

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	arm-linux-gcc app.c -o app

cp:
	cp *.ko /root/porting_x210/rootfs/rootfs/driver_test
	cp app /root/porting_x210/rootfs/rootfs/driver_test
	

.PHONY: clean	
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	

#include <linux/module.h>		// module_init  module_exit
#include <linux/init.h>			// __init   __exit
#include <linux/fs.h>

#define MYMAJOR		200
#define MYNAME		"testchar"

int mymajor;


static int test_chrdev_open(struct inode *inode, struct file *file)
{
	// 這個函式中真正應該放置的是開啟這個裝置的硬體操作程式碼部分
	// 但是現在暫時我們寫不了這麼多,所以用一個printk列印個資訊來做代表。
	printk(KERN_INFO "test_chrdev_open\n");
	
	return 0;
}

static int test_chrdev_release(struct inode *inode, struct file *file)
{
	printk(KERN_INFO "test_chrdev_release\n");
	
	return 0;
}

// 自定義一個file_operations結構體變數,並且去填充
static const struct file_operations test_fops = {
	.owner		= THIS_MODULE,				// 慣例,直接寫即可
	
	.open		= test_chrdev_open,			// 將來應用open開啟這個裝置時實際呼叫的
	.release	= test_chrdev_release,		// 就是這個.open對應的函式
};


// 模組安裝函式
static int __init chrdev_init(void)
{	
	printk(KERN_INFO "chrdev_init helloworld init\n");

	// 在module_init巨集呼叫的函式中去註冊字元裝置驅動
	// major傳0進去表示要讓核心幫我們自動分配一個合適的空白的沒被使用的主裝置號
	// 核心如果成功分配就會返回分配的主裝置好;如果分配失敗會返回負數
	mymajor = register_chrdev(0, MYNAME, &test_fops);
	if (mymajor < 0)
	{
		printk(KERN_ERR "register_chrdev fail\n");
		return -EINVAL;
	}
	printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor);

	return 0;
}

// 模組下載函式
static void __exit chrdev_exit(void)
{
	printk(KERN_INFO "chrdev_exit helloworld exit\n");
	
	// 在module_exit巨集呼叫的函式中去登出字元裝置驅動
	unregister_chrdev(mymajor, MYNAME);
	
}


module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx這種巨集作用是用來新增模組描述資訊
MODULE_LICENSE("GPL");				// 描述模組的許可證
MODULE_AUTHOR("aston");				// 描述模組的作者
MODULE_DESCRIPTION("module test");	// 描述模組的介紹資訊
MODULE_ALIAS("alias xxx");			// 描述模組的別名資訊

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


#define FILE	"/dev/test"			// 剛才mknod建立的裝置檔名


int main(void)
{
	int fd = -1;
	
	fd = open(FILE, O_RDWR);
	if (fd < 0)
	{
		printf("open %s error.\n", FILE);
		return -1;
	}
	printf("open %s success..\n", FILE);
	
	// 讀寫檔案
	
	
	// 關閉檔案
	close(fd);
	
	return 0;
}

5.2.11.新增讀寫介面
5.2.11.1、在驅動中新增
5.2.11.2、在應用中新增
5.2.11.3、測試
5.2.11.4、應用和驅動之間的資料交換
(1)copy_from_user,用來將資料從使用者空間複製到核心空間
(2)copy_to_user
注意:複製是和mmap的對映相對應去區分的

#ubuntu的核心原始碼樹,如果要編譯在ubuntu中安裝的模組就開啟這2個
#KERN_VER = $(shell uname -r)
#KERN_DIR = /lib/modules/$(KERN_VER)/build	

		
# 開發板的linux核心的原始碼樹目錄
KERN_DIR = /root/driver/kernel

obj-m	+= module_test.o

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	arm-linux-gcc app.c -o app

cp:
	cp *.ko /root/porting_x210/rootfs/rootfs/driver_test
	cp app /root/porting_x210/rootfs/rootfs/driver_test
	

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

#include <linux/module.h>		// module_init  module_exit
#include <linux/init.h>			// __init   __exit
#include <linux/fs.h>

#define MYMAJOR		200
#define MYNAME		"testchar"

int mymajor;


static int test_chrdev_open(struct inode *inode, struct file *file)
{
	// 這個函式中真正應該放置的是開啟這個裝置的硬體操作程式碼部分
	// 但是現在暫時我們寫不了這麼多,所以用一個printk列印個資訊來做代表。
	printk(KERN_INFO "test_chrdev_open\n");
	
	return 0;
}

static int test_chrdev_release(struct inode *inode, struct file *file)
{
	printk(KERN_INFO "test_chrdev_release\n");
	
	return 0;
}

ssize_t test_chrdev_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
	printk(KERN_INFO "test_chrdev_read\n");
	
	return 0;
}

// 寫函式的本質就是將應用層傳遞過來的資料先複製到核心中,然後將之以正確的方式寫入硬體完成操作。
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf,
	size_t count, loff_t *ppos)
{
	printk(KERN_INFO "test_chrdev_write\n");	
	
	return 0;
}


// 自定義一個file_operations結構體變數,並且去填充
static const struct file_operations test_fops = {
	.owner		= THIS_MODULE,				// 慣例,直接寫即可
	
	.open		= test_chrdev_open,			// 將來應用open開啟這個裝置時實際呼叫的
	.release	= test_chrdev_release,		// 就是這個.open對應的函式
	.write 		= test_chrdev_write,
	.read		= test_chrdev_read,
};


// 模組安裝函式
static int __init chrdev_init(void)
{	
	printk(KERN_INFO "chrdev_init helloworld init\n");

	// 在module_init巨集呼叫的函式中去註冊字元裝置驅動
	// major傳0進去表示要讓核心幫我們自動分配一個合適的空白的沒被使用的主裝置號
	// 核心如果成功分配就會返回分配的主裝置好;如果分配失敗會返回負數
	mymajor = register_chrdev(0, MYNAME, &test_fops);
	if (mymajor < 0)
	{
		printk(KERN_ERR "register_chrdev fail\n");
		return -EINVAL;
	}
	printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor);

	return 0;
}