国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

字符設(shè)備驅(qū)動

這篇具有很好參考價(jià)值的文章主要介紹了字符設(shè)備驅(qū)動。希望對大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

前面內(nèi)容:
1 Linux驅(qū)動—內(nèi)核模塊基本使用

2 Linux驅(qū)動—內(nèi)核模塊參數(shù),依賴(進(jìn)一步討論)

linux根據(jù)驅(qū)動程序?qū)崿F(xiàn)的模型框架將設(shè)備的驅(qū)動分為了三類:

  1. 字符設(shè)備驅(qū)動:以字節(jié)流為單位順序讀寫,不能隨機(jī)訪問。如,幀緩沖
    ( framebuffer)驅(qū)動,聲卡,串口等。
  2. 塊設(shè)備驅(qū)動:以固定大小的塊(block) 為單位讀寫,可以隨機(jī)訪問。
  3. 網(wǎng)絡(luò)設(shè)備驅(qū)動:網(wǎng)絡(luò)接口是一個(gè)能夠和其他主機(jī)交換數(shù)據(jù)的設(shè)備。接口通常是一個(gè)硬件設(shè)備,但也可能是個(gè)純軟件設(shè)備,比如回環(huán)(loopback)接口。網(wǎng)絡(luò)接口由內(nèi)核中的網(wǎng)絡(luò)子系統(tǒng)驅(qū)動,負(fù)責(zé)發(fā)送和接收數(shù)據(jù)包。

以上驅(qū)動程序的分類是按照驅(qū)動的模型框架進(jìn)行的,在現(xiàn)實(shí)生活中,有的設(shè)備很難被嚴(yán)格界定是字符設(shè)備還是塊設(shè)備。甚至有的設(shè)備同時(shí)具有兩類驅(qū)動,如MTD (存儲技術(shù)設(shè)備,如閃存)。一個(gè)設(shè)備的驅(qū)動屬于上述三類中的哪一類, 還要看具體的使用場合和最終的用途。

字符設(shè)備驅(qū)動基礎(chǔ)

在正式學(xué)習(xí)字符設(shè)備驅(qū)動之前,我們來看看相關(guān)的基礎(chǔ)知識。

在類UNIX系統(tǒng)中,有一個(gè)眾所周知的說法,即“一切皆文件”,當(dāng)然網(wǎng)絡(luò)設(shè)備是一個(gè)例外。這就意味著設(shè)備最終也會體現(xiàn)為一個(gè)文件,應(yīng)用程序要對設(shè)備進(jìn)行訪問,最終就會轉(zhuǎn)化為對文件的訪問,這樣做的好處是統(tǒng)一了 對上層的接口。

設(shè)備文件通常位于/dev目錄下,使用下面的命令可以看到很多設(shè)備文件及其相關(guān)的信息。
ls -l dev
字符設(shè)備驅(qū)動

字符設(shè)備驅(qū)動
在上面列出的信息中,前面的字母“b”表示是塊設(shè)備,“c” 表示是字符設(shè)備。比如sda、sda1, sda2、 sda5就是塊設(shè)備,實(shí)際上這些設(shè)備是筆者的Ubuntu主機(jī)上的一個(gè)硬盤和這個(gè)硬盤上的三個(gè)分區(qū),其中sda表示的是整個(gè)硬盤,而sdal1、sda2、 sda5分別是三個(gè)分區(qū)。tty0、 tty1 就是終端設(shè)備,shell 程序使用這些設(shè)備來同用戶進(jìn)行交互。

從上面的打印信息來看,設(shè)備文件和普通文件有很多相似之處,都有相應(yīng)的權(quán)限、所屬的用戶和組、修改時(shí)間和名字。但是設(shè)備文件會比普通文件多出兩個(gè)數(shù)字,這兩個(gè)數(shù)字分別是主設(shè)備號和次設(shè)備號。這兩個(gè)號是設(shè)備在內(nèi)核中的身份或標(biāo)志,是內(nèi)核區(qū)分不同設(shè)備的唯一信息。通常內(nèi)核用主設(shè)備號區(qū)別一類設(shè)備,次設(shè)備號用于區(qū)分同一類設(shè)備的不同個(gè)體或不同分區(qū)。而路徑名則是用戶層用于區(qū)別設(shè)備信息的。

通過mknod命令來創(chuàng)建一個(gè)設(shè)備文件

mknod /dev/vser0 c 256 0
ls -li /dev/vser0

mknod是make node的縮寫,就是創(chuàng)建一個(gè)節(jié)點(diǎn)(設(shè)備文件)。

在linux系統(tǒng)中,一個(gè)節(jié)點(diǎn)代表一個(gè)文件,創(chuàng)建一個(gè)文件最主要的工作就是分配一個(gè)新的節(jié)點(diǎn)。
包含節(jié)點(diǎn)號的分配(節(jié)點(diǎn)號在系統(tǒng)中是唯一的,可以區(qū)分不同的文件)。

如上面的命令的結(jié)果會出現(xiàn)
126695 crw-r--r-- 1 root root 256, 0 Jul 13 10:03 /dev/vser0

這里的126695就是節(jié)點(diǎn)號
然后初始化這個(gè)節(jié)點(diǎn)的(文件模式 crw-r--r-- 、訪問時(shí)間 Jul 13 10:03 、用戶ID 1 、組ID 13等信息
如果是設(shè)備文件需要初始化好設(shè)備號

再將這個(gè)初始化好的節(jié)點(diǎn)放入磁盤,還需要在文件所在目錄下添加一個(gè)目錄項(xiàng),目錄項(xiàng)中包含了前面分配的節(jié)點(diǎn)號和文件的名字,然后寫入磁盤。存在磁盤上的這個(gè)節(jié)點(diǎn)用一個(gè)結(jié)構(gòu)封裝。

下面用extr2文件系統(tǒng)為例:
在linux3.14內(nèi)核文件中
字符設(shè)備驅(qū)動

struct ext2_inode {
	__le16	i_mode;		/* File mode */
	__le16	i_uid;		/* Low 16 bits of Owner Uid */
	__le32	i_size;		/* Size in bytes */
	__le32	i_atime;	/* Access time */
	__le32	i_ctime;	/* Creation time */
	__le32	i_mtime;	/* Modification time */
	__le32	i_dtime;	/* Deletion Time */
	__le16	i_gid;		/* Low 16 bits of Group Id */
	__le16	i_links_count;	/* Links count */
	__le32	i_blocks;	/* Blocks count */
	__le32	i_flags;	/* File flags */
	.....
	__le32	i_block[EXT2_N_BLOCKS];/* Pointers to blocks */
	......
};

可以從這里清楚的看出一個(gè)node節(jié)點(diǎn)的結(jié)構(gòu)體包含的數(shù)值,前面說的節(jié)點(diǎn)的(文件模式 crw-r–r-- 、訪問時(shí)間 Jul 13 10:03 、用戶ID 1 、組ID 13等信息)都有

另外,對于i_blocks ,

  1. 如果是普通文件,則這個(gè)數(shù)組存放的是真正的文件數(shù)據(jù)所在的塊號(看成對文件的索引,所以ext2文件是按照索引的方式找的);
  2. 如果是設(shè)備文件,這個(gè)數(shù)組主要存放設(shè)備的主次設(shè)備號

可以看下面代碼:
字符設(shè)備驅(qū)動

static int __ext2_write_inode(struct inode *inode, int do_sync)
{
	struct ext2_inode * raw_inode = ext2_get_inode(sb, ino, &bh);
	.......
	raw_inode->i_mode = cpu_to_le16(inode->i_mode);
	.......
	if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode)) {
		if (old_valid_dev(inode->i_rdev)) {
			raw_inode->i_block[0] =
				cpu_to_le32(old_encode_dev(inode->i_rdev));
			raw_inode->i_block[1] = 0;
		} else {
			raw_inode->i_block[0] = 0;
			raw_inode->i_block[1] =
				cpu_to_le32(new_encode_dev(inode->i_rdev));
			raw_inode->i_block[2] = 0;
		}
	}
	......
}

這個(gè)struct ext2_inode * raw_inode = ext2_get_inode(sb, ino, &bh); 獲得了一個(gè)要寫入磁盤的ext2_inode結(jié)構(gòu),并初始化了部分成員。

這個(gè)

if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode)) {
		if (old_valid_dev(inode->i_rdev)) {
			raw_inode->i_block[0] =
				cpu_to_le32(old_encode_dev(inode->i_rdev));
			raw_inode->i_block[1] = 0;
		} else {
			raw_inode->i_block[0] = 0;
			raw_inode->i_block[1] =
				cpu_to_le32(new_encode_dev(inode->i_rdev));
			raw_inode->i_block[2] = 0;
		}
	}

判斷了設(shè)備的類型,如果是字符設(shè)備或塊設(shè)備,那么將設(shè)備號寫入i_ block 的前2個(gè)或前3個(gè)元素,其中ionde的i_dev 成員就是設(shè)備號。而這里的inode是存在于內(nèi)存中的節(jié)點(diǎn),是涉及文件操作的一個(gè)非常關(guān)鍵的數(shù)據(jù)結(jié)構(gòu)

關(guān)于該結(jié)構(gòu)我們之后還要討論,這里只需要知道寫入磁盤中的ext2_ inode 結(jié)構(gòu)內(nèi)的成員基本上都是靠存在于內(nèi)存中的inode中對應(yīng)的成員初始化的即可,其中就包含了這里講的設(shè)備號。之前我們說過,設(shè)備號有主、次設(shè)備號之分,而這里的設(shè)備號只有一個(gè)。原因是主、次設(shè)備號的位寬有限制,可以將兩個(gè)設(shè)備號合并,之后我們會看到相應(yīng)的代碼。

在代碼raw_inode->i_mode = cpu_to_le16(inode->i_mode);我們可以看到,文件的類型也被保存在了ext2_ inode 結(jié)構(gòu)中,并且寫在了磁盤上。

剛才還談到需要在文件所在目錄下添加目錄項(xiàng),這又是怎樣完成的呢?

文件目錄下添加目錄項(xiàng)

linux系統(tǒng)中,目錄本身也是一個(gè)文件,其中保存的數(shù)據(jù)是若干個(gè)目錄項(xiàng),目錄項(xiàng)的主要內(nèi)容就是剛才分配的節(jié)點(diǎn)號和文件或子目錄的名字。

在ext2中,寫入磁盤的目錄項(xiàng)數(shù)據(jù)結(jié)構(gòu)如下:
字符設(shè)備驅(qū)動
上面的inode就是節(jié)點(diǎn)號,name成員就是文件或者子目錄的名字。

具體代碼實(shí)現(xiàn)可以參考fs/ext2/namei.c”的ext2_mknod函數(shù).

下圖說明了mknod命令在ext2文件系統(tǒng)上完成的工作

字符設(shè)備驅(qū)動
上面的整個(gè)過程,就是mknod命令將文件名、文件類型、和主次設(shè)備號等信息保存在磁盤上

接下來我們來討論如何打開一個(gè)文件,這是理解上層應(yīng)用程序和底層驅(qū)動程序如何建立聯(lián)系的關(guān)鍵,也是理解字符設(shè)備驅(qū)動編寫方式的關(guān)鍵。整個(gè)過程非常煩瑣,涉及的數(shù)據(jù)結(jié)構(gòu)和相關(guān)的內(nèi)核知識非常多。為了便于大家理解,下面將該過程進(jìn)行大量簡化,并以圖3.2和調(diào)用流程來進(jìn)行說明。

字符設(shè)備驅(qū)動
字符設(shè)備驅(qū)動
字符設(shè)備驅(qū)動
理解以下流程:

在內(nèi)核中,一個(gè)進(jìn)程用一個(gè)task_ struct 結(jié)構(gòu)對象來表示,其中的files成員指向了一個(gè)files_ struct 結(jié)構(gòu)變量,該結(jié)構(gòu)中有一個(gè)fd_ array 的指針數(shù)組(用于維護(hù)打開文件的信息), 數(shù)組的每一個(gè)元素是指向file 結(jié)構(gòu)的一個(gè)指針。
open 系統(tǒng)調(diào)用函數(shù)在內(nèi)核中對應(yīng)的函數(shù)是sys_ _open, sys_ open調(diào)用了do_ sys_ open,在do_ sys_ open中首先調(diào)用了getname 函數(shù)將文件名從用戶空間復(fù)制到了內(nèi)核空間。接著調(diào)用get__unused fd_ flags 來獲取一個(gè)未使用的文件描述符,要獲得該描述符,其實(shí)就是搜索files_ _struct 中的fd_ arrary 數(shù)組,查看哪一個(gè)元素沒有被使用,然后返回其下標(biāo)即可。接下來調(diào)用do_ filp. _open 函數(shù)來構(gòu)造一-個(gè) file結(jié)構(gòu),并初始化里面的成員。其中最重要的是將它的f _op成員指向和設(shè)備對應(yīng)的驅(qū)動程序的操作方法集合的結(jié)構(gòu)file_ operations , 這個(gè)結(jié)構(gòu)中的絕大多數(shù)成員都是函數(shù)指針,通過file_operations 中的open函數(shù)指針可以調(diào)用驅(qū)動中實(shí)現(xiàn)的特定于設(shè)備的打開函數(shù),從而完成打開的操作。do_ filp_open 函數(shù)執(zhí)行成功后,調(diào)用fd_ install 函數(shù),該函數(shù)將剛才得到的文件描述符作為訪問fd_ array 數(shù)組的下標(biāo),讓下標(biāo)對應(yīng)的元素指向新構(gòu)造的file 結(jié)構(gòu)。

最后系統(tǒng)調(diào)用返回到應(yīng)用層,將剛才的數(shù)組下標(biāo)作為打開文件的文件描述符返回。

do_ filp_ open函數(shù)包含的內(nèi)容很多,是這個(gè)過程中最復(fù)雜的一部分,下面進(jìn)行一下非
常簡化的介紹。
do_ filp_ open 函數(shù)調(diào)用path _openat 來進(jìn)行實(shí)際的打開操作,path_ openat調(diào)用get_empty_ filp 快速得到一一個(gè)file結(jié)構(gòu),再調(diào)用link. path walk來處理文件路徑中除最后一個(gè)分量的前面部分。
舉個(gè)例子來說,如果要打開/dev/vser0這個(gè)文件,那么link_path_ walk 需要處理/dev這部分,包含根目錄和dev目錄。
接下來path _openat 調(diào)用do_ last 來處理最后一個(gè)分量,do_ last 首先調(diào)用lookup_fast 在RCU模式下來嘗試快速查找,如果第一次這么做會失敗,所以繼續(xù)調(diào)用lookup_ open, 而lookup_ open 首先調(diào)用lookup_dcache在目錄項(xiàng)高速緩存中進(jìn)行查找,第一次這么做也會失敗,所以轉(zhuǎn)而調(diào)用lookup_ real, lookup_ real則在磁盤上真正開始查找最后一個(gè)分量所對應(yīng)的節(jié)點(diǎn),如果是ext2文件系統(tǒng),則會調(diào)用ext2_ lookup, 得到inode 的編號后,ext2_ lookup 又會調(diào)用ext2_ iget 從磁盤上獲取之前使用mknod保存的節(jié)點(diǎn)信息對字符設(shè)備驅(qū)動來說,這里最重要的就是將文件類型和設(shè)備號取出并填充到了內(nèi)存中的inode 結(jié)構(gòu)的相關(guān)成員中
另外,通過判斷文件的類型,還將inode中的f_op指針指向了def _chr_ fops, 這個(gè)結(jié)構(gòu)中的
open函數(shù)指針指向了chrdev_ open, 那么自然chrdev_ open 緊接著會被調(diào)用。chrdev_ open完成的主要工作是:首先根據(jù)設(shè)備號找到添加在內(nèi)核中代表字符設(shè)備的cdev (cdev 是放在cdev_ map 散列表中的,驅(qū)動加載時(shí)會構(gòu)造相應(yīng)的cdev并添加到這個(gè)散列表中,并且在構(gòu)造這個(gè)cdev時(shí)還實(shí)現(xiàn)了一個(gè)操作方法集合,由cdev的ops成員指向它),找到對應(yīng)的cdev對象后,用cdev關(guān)聯(lián)的操作方法集合替代之前構(gòu)造的file結(jié)構(gòu)中的操作方法集合,然后調(diào)用edev所關(guān)聯(lián)的操作方法集合中的打開函數(shù),完成設(shè)備真正的打開操作,這也標(biāo)志著do_ filp_ open 函數(shù)基本結(jié)束。

為了下一次能夠快速打開文件,內(nèi)核在第一次打開一個(gè)文件或 目錄時(shí)都會創(chuàng)建一個(gè)dentry的目錄項(xiàng),它保存了文件名和所對應(yīng)的inode 信息,所有的dentry使用散列的方式存儲在目錄項(xiàng)高速緩存中,內(nèi)核在打開文件時(shí)會先在這個(gè)高速緩存中查找相應(yīng)的dentry,如果找到,則可以立即獲取文件所對應(yīng)的inode,否則就會在磁盤上獲取。對于字符設(shè)備驅(qū)動來說,設(shè)備號、cdev 和操作方法集合至關(guān)重要,內(nèi)核找到路徑名所對應(yīng)的inode后,要和驅(qū)動建立連接,首先要做的就是根據(jù)inode中的設(shè)備號找到cdev,然后根據(jù)cdev找到關(guān)聯(lián)的操作方法集合,從而調(diào)用驅(qū)動所提供的操作方法來完成對設(shè)備的具體操作。可以說,字符設(shè)備驅(qū)動的框架就是圍繞著設(shè)備號、cdev 和操作方法集合來實(shí)現(xiàn)的。

雖然設(shè)備的打開操作很煩瑣,但是其他系統(tǒng)的調(diào)用過程就要簡單很多。因?yàn)榇蜷_操
作返回了一個(gè)文件描述符,其他系統(tǒng)調(diào)用時(shí)都會以這個(gè)文件描述符作為參數(shù)傳遞給內(nèi)核,
內(nèi)核得到這個(gè)文件描述符后可以直接索引fd_ array,找到對應(yīng)的file結(jié)構(gòu),然后調(diào)用相應(yīng)
的方法。

字符設(shè)備驅(qū)動框架

cdev結(jié)構(gòu)

視頻:
描述字符設(shè)備使用cdev結(jié)構(gòu)

struct cdev {
struct kobject kobj; //父類。
struct module *owner; //當(dāng)前結(jié)構(gòu)所屬模塊,THIS_ MODULE (當(dāng)前模塊)。
const struct file_ operations *ops; //設(shè)備對應(yīng)操作。
struct list_head list; //內(nèi)核鏈表,內(nèi)核用來管理字符設(shè)備。
dev_ t dev;//設(shè)備編號(dev_ t) ,高12主設(shè)備號,低20位次設(shè)備號。
unsigned int count;//次設(shè)備號個(gè)數(shù)。
};

內(nèi)核鏈表就是一個(gè)struct
字符設(shè)備驅(qū)動
設(shè)備編號 高12主設(shè)備號,低20位次設(shè)備號。
字符設(shè)備驅(qū)動
字符設(shè)備驅(qū)動
如果是-crw 以-開頭是普通文件

l開頭是鏈接文件

d開頭是目錄文件

b開頭是塊設(shè)備

還得用次設(shè)備號區(qū)分不同的設(shè)備
用主設(shè)備號區(qū)分不同的程序(文件不同)

struct file_ operations *ops; //設(shè)備對應(yīng)操作

struct file_ operations {
struct module *owner; //THIS MODULE.
ssize_ t (*read) (struct file *, char__ user*, size_t, loff_t *); //對應(yīng)系統(tǒng)調(diào)用read.
ssize_ t (*write) (struct file *, const char_user*, size_ t, loff_t *); //對應(yīng)系統(tǒng)調(diào)用write.
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); //對應(yīng)系統(tǒng)調(diào)用ioctl.
int (*open) (struct inode *, struct file *); //對應(yīng)系統(tǒng)調(diào)用open.
int (*release) (struct inode *, struct file *); //對應(yīng)系統(tǒng)調(diào)用close.
[..]
};

下面介紹一下 字符設(shè)備函數(shù)

字符設(shè)備函數(shù)

分配字符設(shè)備: struct cdev *cdev_ alloc(void); //已經(jīng)初始化,內(nèi)存自動釋放(不用free)
初始化字符設(shè)備: void cdev_ init(struct cdev *cdev, const struct file_ operations *fops); 。
注意:除使用cdev_ alloc 函數(shù)分配的字符設(shè)備以外,都可以調(diào)用該函數(shù)初始化。如初始化靜態(tài)分配的字符設(shè)備。
也就是說 init初始化與調(diào)用cdev_ alloc 不能同時(shí)使用

添加設(shè)備:int cdev_ aldd(struct cdev *p, dev_ t dev, unsigned count)。

參數(shù):
p,字符設(shè)備指針。
dev,設(shè)備編號,
count,次設(shè)備號數(shù)。 子設(shè)備
返回值:成功返回0,失敗返回錯(cuò)誤碼。

刪除設(shè)備: void cdev_ del(struct cdev *p)。

設(shè)備編號: 設(shè)備編號是一個(gè)32無符號整數(shù)(dev _t), 高12主設(shè)備號,低20位次設(shè)備號。

主設(shè)備號用來識別驅(qū)動,次設(shè)備號用來區(qū)分不同的設(shè)備。

字符設(shè)備驅(qū)動

MKDEV(major, minor); //構(gòu)造設(shè)備編號,轉(zhuǎn)換為設(shè)備編號類型dev_t
//MKDEV是將主設(shè)備號和次設(shè)備號轉(zhuǎn)換成dev_t類型的一個(gè)內(nèi)核函數(shù)。
MAJOR(dev_ t); //取主設(shè)備號. 
MINOR(dev_ t); //取次設(shè)備號.

下面我們來試試如何實(shí)現(xiàn)驅(qū)動

實(shí)現(xiàn)字符設(shè)備驅(qū)動

通過上面我們知道了cdev結(jié)構(gòu),但是我們要將cdev構(gòu)造就要將cdev架構(gòu)對象添加到內(nèi)核的cdev_map散列表中

注冊設(shè)備號

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

#include <linux/fs.h>

#define VSER_MAJOR	256
#define VSER_MINOR	0
#define VSER_DEV_CNT	1   //數(shù)量1 設(shè)備號1個(gè)
#define VSER_DEV_NAME	"vser"

static int __init vser_init(void)
{
	int ret;
	dev_t dev;

	dev = MKDEV(VSER_MAJOR, VSER_MINOR);
	ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
	if (ret)
		goto reg_err;
	return 0;

reg_err:
	return ret;
}

static void __exit vser_exit(void)
{
	
	dev_t dev;

	dev = MKDEV(VSER_MAJOR, VSER_MINOR);
	unregister_chrdev_region(dev,VSER_DEV_CNT);
}

module_init(vser_init);
module_exit(vser_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kevin Jiang <jiangxg@farsight.com.cn>");
MODULE_DESCRIPTION("A simple character device driver");
MODULE_ALIAS("virtual-serial");

在模塊的初始化函數(shù)中:

#define VSER_MAJOR	256
#define VSER_MINOR	0
#define VSER_DEV_CNT	1 //數(shù)量1 設(shè)備號1個(gè)
#define VSER_DEV_NAME	"vser"

dev = MKDEV(VSER_MAJOR, VSER_MINOR);

這里用MKDEV宏將主設(shè)備號和次設(shè)備號合并成一個(gè)一個(gè)設(shè)備號dev

在內(nèi)核源碼中相關(guān)宏定義如下:

#define MINORBITS 	20
#define MINORMASK((1U << MINORBITS) -1)

#define MAJOR (dev) ((unsigned int)((dev)>> MINORBITS))
#define MINOR (dev)((unsigned int)((dev)&MINORMASK) )
#define MKDEV (ma,mi) (((ma) << MINORBITS)|(mi))

不難發(fā)現(xiàn),該宏的作用是將主設(shè)備號左移20位和次設(shè)備號相或(12高主設(shè)備號+20低次設(shè)備號=32位設(shè)備號)

構(gòu)造好設(shè)備號后

代碼ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);

它調(diào)用了register_chrdev_region將構(gòu)造的設(shè)備號注冊到內(nèi)核中,表明該設(shè)備號已經(jīng)被占用,如果有其他驅(qū)動隨后要注冊該設(shè)備號不行。

其函數(shù)原型:
int register chrdev_region(dev_t from, unsigned count, const char *name);

該函數(shù)一次可以注冊多個(gè)連續(xù)的號,由count形參指定個(gè)數(shù),由from指定起始的設(shè)備號,name用于標(biāo)記主設(shè)備號的名稱。該函數(shù)成功則返回0,不成功則返回負(fù)數(shù)
返回負(fù)數(shù)通常是因?yàn)橐缘脑O(shè)備號已經(jīng)被其他的驅(qū)動搶先注冊了。如果注冊出錯(cuò),則使用goto語句跳轉(zhuǎn)到錯(cuò)誤處理代碼處執(zhí)行,否則初始化函數(shù)返回0。

在卸載模塊時(shí)候,已注冊的號應(yīng)該從內(nèi)核中注銷,否則再次加載該驅(qū)動時(shí)候,注冊設(shè)備號操作會失敗
。代碼中調(diào)用unregister_chrdev_region(dev,VSER_DEV_CNT);

上面的代碼再一次印證了前面所說的內(nèi)容,即在模塊初始化的函數(shù)中負(fù)責(zé)注冊、分配內(nèi)存等操作,而在模塊清除函數(shù)中負(fù)責(zé)相反的操作,即注銷、釋放內(nèi)存等操作。

以上的代碼可以編譯并進(jìn)行測試,在Ubuntu主機(jī)上測試的步驟如下( 在ARM目標(biāo)板上的測試和前面所講的模塊在ARM目標(biāo)板上測試的過程類似)。

下面試試編譯

make
make modules_install
sudo insmod vser.ko
或者
modprobe vser

然后要查看,先建立個(gè)文件proc,再在里面建立devices目錄

主要是為了使用cat /proc/devices查看設(shè)備號

character devices如下
字符設(shè)備驅(qū)動
256號是vser,這樣就相當(dāng)于注冊好了

使用register _chrdev_ region 注冊設(shè)備號的方式稱為靜態(tài)注冊設(shè)備號,但是該方式有一個(gè)明顯的缺點(diǎn),就是如果兩個(gè)驅(qū)動都使用了同樣的設(shè)備號,那么后加載的驅(qū)動將會失敗,因?yàn)樵O(shè)備號沖突了。為了解決這個(gè)問題,可以使用動態(tài)分配設(shè)備號的函數(shù),其原型如下:

int alloc_chrdev_region(dev_t*dev, unsigned baseminor, unsigned count, const char*name) ;

其中,count和name形參同register_ chrdev_ region函數(shù)中相應(yīng)的形參一致 。baseminor是動態(tài)分配的設(shè)備號的起始次設(shè)備號,而dev 則是分配得到的第一個(gè)設(shè)備號。 該函數(shù)成功則返回0,失敗則返回負(fù)數(shù)。這樣就避免了各個(gè)驅(qū)動使用相同的設(shè)備號而帶來的沖突,

但是會存在另外-一個(gè)問題,那就是不能事先知道主次設(shè)備號,在使用mknod命令創(chuàng)建設(shè)備節(jié)點(diǎn)時(shí),必須先查看/proc/devices文件才能確定主設(shè)備號(次設(shè)備號在代碼中確定),也就是要求mknod命令要后于驅(qū)動加載執(zhí)行,不過這個(gè)問題在新的Linux設(shè)備模型中已經(jīng)得到了比較好的解決,設(shè)備節(jié)點(diǎn)會自動地創(chuàng)建和銷毀,這在后面的章節(jié)會詳細(xì)描述。

成功注冊了設(shè)備號后,接下來應(yīng)該構(gòu)造并添加cdev結(jié)構(gòu)對象

構(gòu)造并添加cdev結(jié)構(gòu)對象

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

#include <linux/fs.h>
#include <linux/cdev.h>

#define VSER_MAJOR	256
#define VSER_MINOR	0
#define VSER_DEV_CNT	1
#define VSER_DEV_NAME	"vser"

static struct cdev vsdev;

static struct file_operations vser_ops = {
	.owner = THIS_MODULE,
};

static int __init vser_init(void)
{
	int ret;
	dev_t dev;

	dev = MKDEV(VSER_MAJOR, VSER_MINOR);
	ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
	if (ret)
		goto reg_err;

	cdev_init(&vsdev, &vser_ops);
	vsdev.owner = THIS_MODULE;

	ret = cdev_add(&vsdev, dev, VSER_DEV_CNT);
	if (ret)
		goto add_err;

	return 0;

add_err:
	unregister_chrdev_region(dev, VSER_DEV_CNT);
reg_err:
	return ret;
}

static void __exit vser_exit(void)
{
	
	dev_t dev;

	dev = MKDEV(VSER_MAJOR, VSER_MINOR);

	cdev_del(&vsdev);
	unregister_chrdev_region(dev, VSER_DEV_CNT);
}

module_init(vser_init);
module_exit(vser_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kevin Jiang <jiangxg@farsight.com.cn>");
MODULE_DESCRIPTION("A simple character device driver");
MODULE_ALIAS("virtual-serial");

代碼static struct cdev vsdev;定義了一個(gè)struct cdev類型的全局變量vsdev。

下面

static struct file_operations vser_ops = {
	.owner = THIS_MODULE,
};

定義了一個(gè)struct file_operations類型的全局變量vser_ops

字符設(shè)備操作結(jié)構(gòu)很關(guān)鍵

其中,vsdev表示一個(gè)具體的字符設(shè)備,而vser_ops是操作該設(shè)備的一些方法。

代碼cdev_init(&vsdev, &vser_ops);調(diào)用cdev_init 初始化了vsdev中的部分成員。
另外一個(gè)最重要的操作就是將vsdev中的ops指針指向了vser_ ops,這樣通過
設(shè)備號找到vsdev對象后,就能找到相關(guān)的操作方法集合,并調(diào)用其中的方法。

cdev_ init函數(shù)的原型如下,第一個(gè)參數(shù)是要初始化的cdev地址,第二個(gè)參數(shù)是設(shè)備操作方法集合的結(jié)構(gòu)地址。

void cdev_init(struct cdev*cdev,const struct file_operations*fops) ;

繼續(xù)代碼

static struct file_operations vser_ops = {
	.owner = THIS_MODULE,
};

static int __init vser_init(void)
{
	··············
	vsdev.owner = THIS_MODULE;

將owner成員賦值為THIS_MODULE,owner是一個(gè)指向struct module類型變量的指針。
THIS_MODULE是包含驅(qū)動的模塊中的struct module類型對象的地址,類似于c++中的this指針。
這樣就能通過vsdev或者vser_fops找到對應(yīng)的模塊

在對前面兩個(gè)對象進(jìn)行訪問時(shí)都要調(diào)用類似于try_module_get的函數(shù)增加模塊的引用計(jì)數(shù),因?yàn)樵谶@兩個(gè)對象使用的過程中,模塊是不能被卸載的,模塊被卸載的前提條件是引用計(jì)數(shù)為0。

cdev對象初始化以后,就應(yīng)該添加到內(nèi)核中的cdev_ map散列表中,調(diào)用的函數(shù)是cdev_ add,其函數(shù)原型如下
int cdev add(struct cdev *p, dev_ t dev, unsigned count) ;

cdev_ add 函數(shù)的主要工作是將主設(shè)備號通過對255取余,將余數(shù)作為cdev_ map數(shù)組的下標(biāo)索引,然后構(gòu)造一個(gè)probe對象,并讓data指向要添加的cdev結(jié)構(gòu)地址,然后加入到鏈表當(dāng)中

該函數(shù)的最后一個(gè)參數(shù)count指定了被添加的cdev可以管理多少個(gè)設(shè)備。這里需要特別注意的是,參數(shù)p只指向一個(gè)cdev對象,但該對象可以同時(shí)管理多個(gè)設(shè)備,由count 的值來決定具體有多少個(gè)設(shè)備,那么cdev和設(shè)備就不是一 一對應(yīng)的關(guān)系。

這樣,對于一個(gè)驅(qū)動支持多個(gè)設(shè)備的情況,我們可以采用兩種方法來實(shí)現(xiàn),

  1. 第一種方法是為每一個(gè)設(shè)備分配一個(gè)cdev對象,每次調(diào)用cdev_add 添加一個(gè)cdev對象,直到多個(gè)cdev對象全部被添加到內(nèi)核中
  2. 第二種方法是只構(gòu)造一個(gè)cdev對象,但在調(diào)用cdev. add 時(shí),指定添加的
    cdev可以管理多個(gè)設(shè)備。

這兩種方法我們在后面的例子中都會看到。以上是簡化的討論,
實(shí)際的實(shí)現(xiàn)要復(fù)雜一些,如果要詳細(xì)了解,請參考cdev_ add 的內(nèi)核源碼。

在初始化函數(shù)中添加了cdev對象,那么在清除函數(shù)中自然就應(yīng)該刪除該cdev對象,代碼 cdev_del(&vsdev);演示了這一操作, 實(shí)現(xiàn)的函數(shù)是cdev_del, 其函數(shù)原型如下。

void edev_ del (struct cdev *p);

該函數(shù)的作用就是根據(jù)cdev找到散列表中的probe,并進(jìn)行刪除。

在上面的例子中,cdev是靜態(tài)的,我們也可以動態(tài)分配,對應(yīng)的函數(shù)是cdev_alloc。

struct cdev *cdev_ alloc (void);

該函數(shù)成功則返回動態(tài)分配的cdev 對象地址,失敗則返回NULL。

下面試試編譯加載

字符設(shè)備驅(qū)動
從上面的操作可以看到,在未加載驅(qū)動之前,使用cat命令讀取/dev/vser0設(shè)備,錯(cuò)誤信息是設(shè)備找不到,這是因?yàn)檎也坏胶驮O(shè)備號對應(yīng)的cdev對象。

在加載驅(qū)動后,cat命令的錯(cuò)誤信息變成了參數(shù)無效,說明驅(qū)動工作了,只是還未實(shí)現(xiàn)具體的設(shè)備操作的方法。文章來源地址http://www.zghlxwxcb.cn/news/detail-404567.html

到了這里,關(guān)于字符設(shè)備驅(qū)動的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • Linux設(shè)備驅(qū)動——第三章字符驅(qū)動

    當(dāng)對幸福的憧憬過于急切,那痛苦就在人的心靈深處升起?!涌?本章的目的是編寫一個(gè)完整的字符設(shè)備驅(qū)動。我們開發(fā)一個(gè)字符驅(qū)動是因?yàn)檫@一類適合大部分簡單的硬件設(shè)備。字符驅(qū)動也比塊驅(qū)動易于理解。本章的最終目的是編寫一個(gè)模塊化的字符驅(qū)動,但是我們不會在

    2024年02月08日
    瀏覽(24)
  • Linux 驅(qū)動學(xué)習(xí)筆記 ——(1)字符設(shè)備驅(qū)動

    Linux 驅(qū)動學(xué)習(xí)筆記 ——(1)字符設(shè)備驅(qū)動

    《【正點(diǎn)原子】I.MX6U嵌入式Linux驅(qū)動開發(fā)指南》學(xué)習(xí)筆記 字符設(shè)備是 Linux 驅(qū)動中最基本的一類設(shè)備驅(qū)動,字節(jié)設(shè)備就是按照字節(jié)流來讀寫的設(shè)備,常見的字符設(shè)備包括:LED、蜂鳴器、按鍵、I2C 以及 SPI 等。 Linux 中一切皆文件,字符設(shè)備驅(qū)動加載成功后會在 /dev 目錄下生成相

    2024年02月08日
    瀏覽(26)
  • Linux 驅(qū)動之字符設(shè)備

    Linux 驅(qū)動之字符設(shè)備

    什么是設(shè)備號 Linux 規(guī)定每一個(gè)字符設(shè)備或者塊設(shè)備都必須有一個(gè)專屬的設(shè)備號。一個(gè)設(shè)備號由主設(shè)備號和次設(shè)備號組成。主設(shè)備號用來表示某一類驅(qū)動,如鼠標(biāo),鍵盤都可以歸類到 USB 驅(qū)動中。而次設(shè)備號是用來表示這個(gè)驅(qū)動下的各個(gè)設(shè)備。比如第幾個(gè)鼠標(biāo),第幾個(gè)鍵盤等。

    2024年02月16日
    瀏覽(21)
  • Linux 驅(qū)動之高級字符設(shè)備

    Linux 驅(qū)動之高級字符設(shè)備

    什么是IO呢? IO 的英文全稱是 input 和output,翻譯過來就是輸入和輸出。 在馮.諾依曼結(jié)構(gòu)中,將計(jì)算機(jī)分成分為5個(gè)部分: 運(yùn)算器、控制器、存儲器、輸入設(shè)備、輸出設(shè)備 。其中輸入設(shè)備指的是向計(jì)算機(jī)輸入數(shù)據(jù)或者信息,如鼠標(biāo),鍵盤都是輸入設(shè)備。輸出設(shè)備指的是用于接收

    2023年04月14日
    瀏覽(31)
  • Linux -- 字符設(shè)備驅(qū)動--LED的驅(qū)動開發(fā)(初級框架)

    Linux -- 字符設(shè)備驅(qū)動--LED的驅(qū)動開發(fā)(初級框架)

    看原理圖確定引腳,確定引腳輸出什么電平才能點(diǎn)亮 / 熄滅 LED 看主芯片手冊,確定寄存器操作方法:哪些寄存器?哪些位?地址是? 編寫驅(qū)動:先寫框架,再寫硬件操作的代碼 注意 :在芯片手冊中確定的寄存器地址被稱為 物理地址 ,在 Linux 內(nèi)核中無法直接使用。 需要使

    2024年04月28日
    瀏覽(27)
  • LDD學(xué)習(xí)筆記 -- Linux字符設(shè)備驅(qū)動

    LDD學(xué)習(xí)筆記 -- Linux字符設(shè)備驅(qū)動

    字符驅(qū)動程序用于與Linux內(nèi)核中的設(shè)備進(jìn)行交互; 字符設(shè)備指的是像內(nèi)存區(qū)域這樣的硬件組件,通常稱為偽設(shè)備; 用戶空間應(yīng)用程序通常使用 open read write 等系統(tǒng)調(diào)用與這些設(shè)備通信; 把用戶空間的系統(tǒng)調(diào)用連接到設(shè)備驅(qū)動的系統(tǒng)調(diào)用實(shí)現(xiàn)方法上。 內(nèi)核的虛擬文件系統(tǒng) vir

    2024年02月02日
    瀏覽(26)
  • Linux下字符設(shè)備驅(qū)動開發(fā)以及流程介紹

    Linux下字符設(shè)備驅(qū)動開發(fā)以及流程介紹

    首先我們介紹一下什么是字符設(shè)備,然后講解一下字符設(shè)備開發(fā)的具體的流程,分別詳細(xì)介紹每一個(gè)流程中涉及到的結(jié)構(gòu)體以及知識點(diǎn),最后我們編寫代碼實(shí)現(xiàn)字符設(shè)備的開發(fā)以及測試。 Linux內(nèi)核設(shè)計(jì)哲學(xué)是把所有的東西都抽象成文件進(jìn)行訪問,這樣對設(shè)備的訪問都是通過文

    2024年02月01日
    瀏覽(37)
  • Linux設(shè)備驅(qū)動開發(fā)學(xué)習(xí)筆記(等待隊(duì)列,鎖,字符驅(qū)動程序,設(shè)備樹,i2C...)

    container_of函數(shù)可以通過結(jié)構(gòu)體的成員變量檢索出整個(gè)結(jié)構(gòu)體 函數(shù)原型: 內(nèi)核開發(fā)者只實(shí)現(xiàn)了循環(huán)雙鏈表,因?yàn)檫@個(gè)結(jié)構(gòu)能夠?qū)崿F(xiàn)FIFO和LIFO,并且內(nèi)核開發(fā)者要保持最少代碼。 為了支持鏈表,代碼中要添加的頭文件是linux/list.h。內(nèi)核中鏈表實(shí)現(xiàn)核心部分的數(shù)據(jù)結(jié)構(gòu) 是struct li

    2024年01月22日
    瀏覽(19)
  • 【嵌入式Linux內(nèi)核驅(qū)動】04_Jetson nano GPIO應(yīng)用 | 驅(qū)動開發(fā) | 官方gpiolib、設(shè)備樹與chip_driver

    【嵌入式Linux內(nèi)核驅(qū)動】04_Jetson nano GPIO應(yīng)用 | 驅(qū)動開發(fā) | 官方gpiolib、設(shè)備樹與chip_driver

    0.暴露給應(yīng)用層 應(yīng)用 解決調(diào)試目錄為空的問題 調(diào)試信息 1.最簡讀寫文件(在/SYS下) 設(shè)備樹 驗(yàn)證測試 編譯文件 驅(qū)動 of_get_named_gpio_flags //獲取設(shè)備樹節(jié)點(diǎn)的屬性 gpio_is_valid //判斷是否合法 devm_gpio_request //申請使用gpio,并調(diào)用設(shè)置pinctrl device_create_file //根據(jù)設(shè)備樹節(jié)點(diǎn)屬性,創(chuàng)建

    2024年02月07日
    瀏覽(54)
  • linux|操作系統(tǒng)|centos7物理機(jī)安裝網(wǎng)卡驅(qū)動8188gu(內(nèi)核升級,firmware固件,USB設(shè)備管理,module管理)

    linux|操作系統(tǒng)|centos7物理機(jī)安裝網(wǎng)卡驅(qū)動8188gu(內(nèi)核升級,firmware固件,USB設(shè)備管理,module管理)

    目前服務(wù)器領(lǐng)域centos7基本是主流的操作系統(tǒng),而linux相對于Windows來說,軟硬件方面的支持是差很多的,在硬件方面來說,以一個(gè)免驅(qū)的網(wǎng)卡為例,window xp可能不會自動識別到,但Windows10基本都會自動識別到,簡簡單單的即插即用。根本原因在Windows隨著版本的升級,內(nèi)置的各

    2024年01月20日
    瀏覽(37)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包