1. 程式人生 > >【迅為iTop4412學習筆記】8.註冊雜項裝置,以及生成裝置節點

【迅為iTop4412學習筆記】8.註冊雜項裝置,以及生成裝置節點

宣告

以下都是我剛開始看驅動視訊的個人強行解讀,如果有誤請指出,共同進步。

本節目標

註冊雜項裝置,並生成裝置節點


首先講一下之前

我們說過linux的關鍵是:驅動和裝置掛載在總線上

比如微控制器EEPROM是IIC協議的,那麼裝置EEPROM掛載在IIC總線上,編寫驅動即可。

對於匯流排

那麼對於LED這種沒協議的咋整?他就是個GPIO,所以Linux弄了一個platform虛擬匯流排,讓這種沒有匯流排的都掛載在這個虛擬總線上,好了 匯流排的事情完成了。

對於裝置

比如我們有1個LED,就註冊一個LED的裝置,LINUX為他分配一個裝置號,那麼假如我有1000個不同的裝置,是不是可以註冊1000個裝置呢?這裡面就有學問了。

Linux為裝置分配裝置號,而每個裝置號又分為主裝置號和次裝置號。主裝置號用來區分不同種類的裝置(0-255一共256個),而次裝置號用來區分同一型別的多個裝置。而對於常用裝置,Linux定死了他的裝置號。我們用命令檢視

cat /proc/devices

返回內容(只截取了字元裝置部分)

Character devices:
  1 mem
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  5 ttyprintk
  6 lp
  7 vcs
 10 misc
 13 input
 14 sound
 21 sg
 29 fb
 99 ppdev
108 ppp
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
216 rfcomm
226 drm
251 hidraw
252 bsg
253 watchdog
254 rtc

這些主裝置號其實並不是完全任由我們設定,linux會把常用的裝置固定裝置號,比如misc雜項裝置的主裝置號就是10,然後給歸類misc的裝置分配次裝置號來區分。

正文

對於迅為的教程,是本身核心已經有一個裝置的情況下 載入模組 -> 載入編譯進核心的裝置的驅動-> 呼叫probe函式。
這是一個標準的註冊裝置,註冊驅動,呼叫probe()的過程。
然後他在probe()裡又註冊了一個雜項裝置,這樣程式碼很冗長,所以我在此做了一些修改,直接像註冊普通裝置一樣註冊雜項裝置。(程式碼相比,區別就是函式不一樣)。

以模組module來當模板

#include <linux/init.h>
#include <linux/module.h> //#include <linux/platform_device.h> MODULE_LICENSE("Dual BSD/GPL"); MODULE_AUTHOR("MrYang"); static int mryang_init(void) { printk(KERN_EMERG "HELLO MrYang\n"); // platform_device_register(&mryang_device); return 0; } static void mryang_exit(void) { printk(KERN_EMERG "Bye MrYang\n"); // platform_device_unregister(&mryang_device); } module_init(mryang_init); module_exit(mryang_exit);

無論註冊裝置還是驅動,我們都要用API函式以及定義結構體,這次也不例外,我們首先開啟雜項裝置的標頭檔案看看

vim include/linux/miscdevice.h

搜尋結構體miscdevice,以下就是我們要用到的部分

#define MISC_DYNAMIC_MINOR      255

struct miscdevice  {
        int minor;
        const char *name;
        const struct file_operations *fops;
        struct list_head list;
        struct device *parent;
        struct device *this_device;
        const char *nodename;
        mode_t mode;
};

extern int misc_register(struct miscdevice * misc);
extern int misc_deregister(struct miscdevice *misc);

不知道有沒有注意到,以往我們程式碼只看了結構體和註冊解除安裝函式,為什麼會把巨集定義也放上來呢?因為MISC_DYNAMIC_MINOR是動態次裝置號,結構體內的minor需要我們指定次裝置號是多少。

首先我們要包含標頭檔案(註冊裝置節點的標頭檔案後面再說)

#include <linux/miscdevice.h>			// 註冊雜項裝置的標頭檔案
#include <linux/fs.h>					// 註冊裝置節點的結構體的標頭檔案

我們要定義結構體,然後在probe函式裡,把結構體傳給註冊函式進行註冊(解除安裝同理)

static  struct miscdevice mryang_dev= {
	// 次裝置號,可以指定次裝置號是多少,若是想讓linux自己去分配,則定義為巨集定義
	.minor = MISC_DYNAMIC_MINOR,
	// 次裝置的名字
	.name = "mryang_misc_ctl",
	
	.fops = &mryang_ops,
};

結構體為什麼這麼定義,我們舉個例子

我們之前註冊裝置,有name叫mryang_ctl,其id為-1。怎麼雜項裝置就沒有了呢?其實不然,此雜項裝置非彼雜項裝置,雜項裝置misc是一個統稱,misc才是一個有name有id的裝置。而我們現在說的雜項裝置,misc旗下的亂七八糟的裝置,是這種雜項裝置!

假如說misc有五個兒子,我們喊:misc的兒子出來一下,結果五個孩子根本不知道叫的誰。所以我們要為五個孩子分配一下次裝置號minor,以及次裝置的名字name,這下就對了。喊misc的1號兒子xxx出來,那麼叫誰清清楚楚。

那麼fops是什麼呢?結構體內是這麼定義的

const struct file_operations *fops;

他是一個檔案操作的結構體,對於linux來說,一切皆檔案,所以如果我們的應用要操控LED,自然是開啟LED的檔案描述符,寫入1開,寫入0關(這只是隨便舉的例子!)。這也是為什麼定義標頭檔案fs.h的原因。

沒錯我們又要定義一個結構體,叫mryang_ops,裡面存放這操控這個雜項裝置的各種檔案操作函式,當然,這些操作函式我們也要寫。。。函式該怎麼定義需要去檢視標頭檔案看函式定義是如何定義的。

// 3個檔案操作函式
static int mryang_open(struct inode *inode, struct file *file){
	printk(KERN_EMERG "mryang_open!\n");
	return 0;
}

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

static long mryang_unlocked_ioctl( struct file *files, unsigned int cmd, unsigned long arg)
{	
	printk("cmd is %u\n",cmd);
	printk("arg is %lu\n",arg);
	return 0;
}
// file_operations 結構體 mryang_ops
static struct file_operations mryang_ops = {
	.owner = THIS_MODULE,
	.open = mryang_open,
	.release = mryang_release,
	.unlocked_ioctl = mryang_unlocked_ioctl,
};

然後我們把這個結構體mryang_ops傳給我們註冊雜項裝置的結構體mryang_dev的成員fops。

這個流程感覺像一個套娃,你開啟一個套娃,發現還要開啟一個,你再開啟,發現還要再開啟三個…不過好在全部編寫完成了。

此處挖坑…

結構體定義好了,我們在載入模組的時候註冊,解除安裝模組的時候解除安裝就行,分別寫入註冊、解除安裝雜項裝置的函式

// 寫入載入模組的函式內
misc_register(&mryang_dev);
// 寫入解除安裝模組的函式內
misc_deregister(&mryang_dev);

整個程式

#include <linux/init.h>
#include <linux/module.h>

#include <linux/miscdevice.h>			// 註冊雜項裝置的標頭檔案
#include <linux/fs.h>					// 註冊裝置節點的結構體的標頭檔案

#define DEVICE_NAME "mryang_misc_ctl"

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("MrYang");

/*
	int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	int (*open) (struct inode *, struct file *);
	int (*release) (struct inode *, struct file *);
*/

static int mryang_open(struct inode *inode, struct file *file){
	printk(KERN_EMERG "mryang_open!\n");
	return 0;
}

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

static long mryang_unlocked_ioctl( struct file *files, unsigned int cmd, unsigned long arg)
{	
	printk("cmd is %u\n",cmd);
	printk("arg is %lu\n",arg);
	return 0;
}

static struct file_operations mryang_ops = {
	.owner = THIS_MODULE,
	.open = mryang_open,
	.release = mryang_release,
	.unlocked_ioctl = mryang_unlocked_ioctl,
};

static  struct miscdevice mryang_dev= {
	// 次裝置號,可以指定次裝置號是多少,若是想讓linux自己去分配,則定義為巨集定義
	.minor = MISC_DYNAMIC_MINOR,
	// 次裝置的名字
	.name = "mryang_misc_ctl",
	
	.fops = &mryang_ops,
};

static int mryang_init(void)
{
	printk(KERN_EMERG "HELLO MrYang\n");
	printk(KERN_EMERG "return %d\n", misc_register(&mryang_dev) );
	return 0;
}

static void mryang_exit(void)
{
	printk(KERN_EMERG "Bye MrYang\n");
	misc_deregister(&mryang_dev);
}

module_init(mryang_init);
module_exit(mryang_exit);

載入和解除安裝只會輸出模組資訊,那麼我們怎麼判斷是否成功註冊了雜項裝置呢?

  1. misc_register()和之前的註冊的函式都有返回值,我們可以在程式裡列印他們的返回值來檢視
  2. 使用命令檢視

此命令檢視裝置節點

ls /dev

mryang_misc_ctl 確實出現在了返回的列表裡

檢視雜項裝置號

cat /proc/misc

返回 46 mryang_misc_ctl,說明雜項裝置的次裝置有mryang_misc_ctl,其次裝置號為46

結束

註冊雜項裝置雖然步驟跟註冊裝置類似,但是我們要注意到,我們現在說的註冊雜項裝置,其實只是為了在雜項裝置misc下注冊的,目的是為了給他分配一個次裝置號(兒子就是兒子,地位和爸爸和叔叔不是同一輩啊…)

除了註冊了雜項裝置,我們還為裝置生成了裝置節點,以便上層呼叫。比如上層發出命令LED0開,LED1關。裝置節點裡的檔案操作函式就會發揮作用。比如開啟就呼叫mryang_open,關閉呼叫mryang_release,操作裝置就呼叫mryang_unlocked_ioctl()傳入命令和引數。

對於本節我們雖然寫了 但是沒有用上,而後面,我們就會來使用這些。