Linux 字元裝置驅動結構(一)—— cdev 結構體、裝置號相關知識解析
一、字元裝置基礎知識
1、裝置驅動分類
linux系統將裝置分為3類:字元裝置、塊裝置、網路裝置。使用驅動程式:
字元裝置:是指只能一個位元組一個位元組讀寫的裝置,不能隨機讀取裝置記憶體中的某一資料,讀取資料需要按照先後資料。字元裝置是面向流的裝置,常見的字元裝置有滑鼠、鍵盤、串列埠、控制檯和LED裝置等。
塊裝置:是指可以從裝置的任意位置讀取一定長度資料的裝置。塊裝置包括硬碟、磁碟、U盤和SD卡等。
每一個字元裝置或塊裝置都在/dev目錄下對應一個裝置檔案。linux使用者程式通過裝置檔案(或稱裝置節點)來使用驅動程式操作字元裝置和塊裝置
2、字元裝置、字元裝置驅動與使用者空間訪問該裝置的程式三者之間的關係
如圖,在Linux核心中:
a -- 使用cdev結構體來描述字元裝置;
b -- 通過其成員dev_t來定義裝置號(分為主、次裝置號)以確定字元裝置的唯一性;
c -- 通過其成員file_operations來定義字元裝置驅動提供給VFS的介面函式,如常見的open()、read()、write()等;
在Linux字元裝置驅動中:
a -- 模組載入函式通過 register_chrdev_region( ) 或 alloc_chrdev_region( )來靜態或者動態獲取裝置號;
b -- 通過 cdev_init( ) 建立cdev與 file_operations之間的連線,通過 cdev_add( ) 向系統新增一個cdev以完成註冊;
c -- 模組解除安裝函式通過cdev_del( )來登出cdev,通過 unregister_chrdev_region( )來釋放裝置號;
使用者空間訪問該裝置的程式:
a -- 通過Linux系統呼叫,如open( )、read( )、write( ),來“呼叫”file_operations來定義字元裝置驅動提供給VFS的介面函式;
3、字元裝置驅動模型
二、cdev 結構體解析
在Linux核心中,使用cdev結構體來描述一個字元裝置,cdev結構體的定義如下:
-
<include/linux/cdev.h>
-
-
struct cdev {
-
struct kobject kobj;
//內嵌的核心物件.
-
struct module *owner;
//該字元裝置所在的核心模組的物件指標.
-
const
struct file_operations *ops;
//該結構描述了字元裝置所能實現的方法,是極為關鍵的一個結構體.
-
struct list_head list;
//用來將已經向核心註冊的所有字元裝置形成連結串列.
-
dev_t dev;
//字元裝置的裝置號,由主裝置號和次裝置號構成.
-
unsigned
int count;
//隸屬於同一主裝置號的次裝置號的個數.
-
};
核心給出的操作struct cdev結構的介面主要有以下幾個:
a -- void cdev_init(struct cdev *, const struct file_operations *);
其原始碼如程式碼清單如下:
-
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
-
{
-
memset(cdev,
0,
sizeof *cdev);
-
INIT_LIST_HEAD(&cdev->
list);
-
kobject_init(&cdev->kobj, &ktype_cdev_default);
-
cdev->ops = fops;
-
}
該函式主要對struct cdev結構體做初始化,
最重要的就是建立cdev 和 file_operations之間的連線:
(1) 將整個結構體清零;
(2) 初始化list成員使其指向自身;
(3) 初始化kobj成員;
(4) 初始化ops成員;
b --struct cdev *cdev_alloc(void);
該函式主要分配一個struct cdev結構,動態申請一個cdev記憶體,並做了cdev_init中所做的前面3步初始化工作(第四步初始化工作需要在呼叫cdev_alloc後,顯式的做初始化即: .ops=xxx_ops).
其原始碼清單如下:
-
struct cdev *cdev_alloc(void)
-
{
-
struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
-
if (p) {
-
INIT_LIST_HEAD(&p->
list);
-
kobject_init(&p->kobj, &ktype_cdev_dynamic);
-
}
-
return p;
-
}
在上面的兩個初始化的函式中,我們沒有看到關於owner成員、dev成員、count成員的初始化;其實,owner成員的存在體現了驅動程式與核心模組間的親密關係,struct module是核心對於一個模組的抽象,該成員在字元裝置中可以體現該裝置隸屬於哪個模組,在驅動程式的編寫中一般由使用者顯式的初始化 .owner = THIS_MODULE, 該成員可以防止裝置的方法正在被使用時,裝置所在模組被解除安裝。而dev成員和count成員則在cdev_add中才會賦上有效的值。
c -- int cdev_add(struct cdev *p, dev_t dev, unsigned count);
該函式向核心註冊一個struct cdev結構,即正式通知核心由struct cdev *p代表的字元裝置已經可以使用了。
當然這裡還需提供兩個引數:
(1)第一個裝置號 dev,
(2)和該裝置關聯的裝置編號的數量。
這兩個引數直接賦值給struct cdev 的dev成員和count成員。
d -- void cdev_del(struct cdev *p);
該函式向核心登出一個struct cdev結構,即正式通知核心由struct cdev *p代表的字元裝置已經不可以使用了。
從上述的介面討論中,我們發現對於struct cdev的初始化和註冊的過程中,我們需要提供幾個東西
(1) struct file_operations結構指標;
(2) dev裝置號;
(3) count次裝置號個數。
但是我們依舊不明白這幾個值到底代表著什麼,而我們又該如何去構造這些值!
三、裝置號相應操作
1 -- 主裝置號和次裝置號(二者一起為裝置號):
一個字元裝置或塊裝置都有一個主裝置號和一個次裝置號。主裝置號用來標識與裝置檔案相連的驅動程式,用來反映裝置型別。次裝置號被驅動程式用來辨別操作的是哪個裝置,用來區分同類型的裝置。
linux核心中,裝置號用dev_t來描述,2.6.28中定義如下:
typedef u_long dev_t;
在32位機中是4個位元組,高12位表示主裝置號,低20位表示次裝置號。
核心也為我們提供了幾個方便操作的巨集實現dev_t:
1) -- 從裝置號中提取major和minor
MAJOR(dev_t dev);
MINOR(dev_t dev);
2) -- 通過major和minor構建裝置號
MKDEV(int major,int minor);
注:這只是構建裝置號。並未註冊,需要呼叫 register_chrdev_region 靜態申請;
-
//巨集定義:
-
#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))</span>
2、分配裝置號(兩種方法):
a -- 靜態申請:
int register_chrdev_region(dev_t from, unsigned count, const char *name);
其原始碼清單如下:
-
int register_chrdev_region(dev_t from, unsigned count, const char *name)
-
{
-
struct char_device_struct *cd;
-
dev_t to = from + count;
-
dev_t n, next;
-
-
for (n = from; n < to; n = next) {
-
next = MKDEV(MAJOR(n)+
1,
0);
-
if (next > to)
-
next = to;
-
cd = __register_chrdev_region(MAJOR(n), MINOR(n),
-
next - n, name);
-
if (IS_ERR(cd))
-
goto fail;
-
}
-
return
0;
-
fail:
-
to = n;
-
for (n = from; n < to; n = next) {
-
next = MKDEV(MAJOR(n)+
1,
0);
-
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
-
}
-
return PTR_ERR(cd);
-
}
b -- 動態分配:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
其原始碼清單如下:
-
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
-
const
char *name)
-
{
-
struct char_device_struct *cd;
-
cd = __register_chrdev_region(
0, baseminor, count, name);
-
if (IS_ERR(cd))
-
return PTR_ERR(cd);
-
*dev = MKDEV(cd->major, cd->baseminor);
-
return
0;
-
}
可以看到二者都是呼叫了__register_chrdev_region 函式,其原始碼如下:
-
static
struct char_device_struct *
-
__register_chrdev_region(unsigned int major, unsigned int baseminor,
-
int minorct, const char *name)
-
{
-
struct char_device_struct *cd, **cp;
-
int ret =
0;
-
int i;
-
-
cd = kzalloc(
sizeof(struct char_device_struct), GFP_KERNEL);
-
if (cd ==
NULL)
-
return ERR_PTR(-ENOMEM);
-
-
mutex_lock(&chrdevs_lock);
-
-
/* temporary */
-
if (major ==
0) {
-
for (i = ARRAY_SIZE(chrdevs)
-1; i >
0; i--) {
-
if (chrdevs[i] ==
NULL)
-
break;
-
}
-
-
if (i ==
0) {
-
ret = -EBUSY;
-
goto out;
-
}
-
major = i;
-
ret = major;
-
}
-
-
cd->major = major;
-
cd->baseminor = baseminor;
-
cd->minorct = minorct;
-
strlcpy(cd->name, name,
sizeof(cd->name));
-
-
i = major_to_index(major);
-
-
for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
-
if ((*cp)->major > major ||
-
((*cp)->major == major &&
-
(((*cp)->baseminor >= baseminor) ||
-
((*cp)->baseminor + (*cp)->minorct > baseminor))))
-
break;
-
-
/* Check for overlapping minor ranges. */
-
if (*cp && (*cp)->major == major) {
-
int old_min = (*cp)->baseminor;
-
int old_max = (*cp)->baseminor + (*cp)->minorct -
1;
-
int new_min = baseminor;
-
int new_max = baseminor + minorct -
1;
-
-
/* New driver overlaps from the left. */
-
if (new_max >= old_min && new_max <= old_max) {
-
ret = -EBUSY;
-
goto out;
-
}
-
-
/* New driver overlaps from the right. */
-
if (new_min <= old_max && new_min >= old_min) {
-
ret = -EBUSY;
-
goto out;
-
}
-
}
-
-
cd->next = *cp;
-
*cp = cd;
-
mutex_unlock(&chrdevs_lock);
-
return cd;
-
out:
-
mutex_unlock(&chrdevs_lock);
-
kfree(cd);
-
return ERR_PTR(ret);
-
}
通過這個函式可以看出
register_chrdev_region和
alloc_chrdev_region
的區別,register_chrdev_region直接將Major 註冊進入,而 alloc_chrdev_region從Major = 0 開始,逐個查詢裝置號,直到找到一個閒置的裝置號,並將其註冊進去;
二者應用可以簡單總結如下:
register_chrdev_region alloc_chrdev_region
devno = MKDEV(major,minor); ret = register_chrdev_region(devno, 1, "hello"); cdev_init(&cdev,&hello_ops); ret = cdev_add(&cdev,devno,1); | alloc_chrdev_region(&devno, minor, 1, "hello"); major = MAJOR(devno); cdev_init(&cdev,&hello_ops); ret = cdev_add(&cdev,devno,1) | register_chrdev(major,"hello",&hello |
可以看到,除了前面兩個函式,還加了一個register_chrdev 函式,可以發現這個函式的應用非常簡單,只要一句就可以搞定前面函式所做之事;
下面分析一下register_chrdev 函式,其原始碼定義如下:
-
static inline int register_chrdev(unsigned int major, const char *name,
-
const struct file_operations *fops)
-
{
-
return __register_chrdev(major,
0,
256, name, fops);
-
}
呼叫了 __register_chrdev(major, 0, 256, name, fops) 函式:
-
int __register_chrdev(
unsigned
int major,
unsigned
int baseminor,
-
unsigned
int count,
const
char *name,
-
const struct file_operations *fops)
-
{
-
struct char_device_struct *cd;
-
struct cdev *cdev;
-
int err = -ENOMEM;
-
-
cd = __register_chrdev_region(major, baseminor, count, name);
-
if (IS_ERR(cd))
-
return PTR_ERR(cd);
-
-
cdev = cdev_alloc();
-
if (!cdev)
-
goto out2;
-
-
cdev->owner = fops->owner;
-
cdev->ops = fops;
-
kobject_set_name(&cdev->kobj,
"%s", name);
-
-
err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
-
if (err)
-
goto out;
-
-
cd->cdev = cdev;
-
-
return major ?
0 : cd->major;
-
out:
-
kobject_put(&cdev->kobj);
-
out2:
-
kfree(__unregister_chrdev_region(cd->major, baseminor, count));
-
return err;
-
}
可以看到這個函式不只幫我們註冊了裝置號,還幫我們做了cdev 的初始化以及cdev 的註冊;
3、登出裝置號:
void unregister_chrdev_region(dev_t from, unsigned count);
4、建立裝置檔案:
利用cat /proc/devices檢視申請到的裝置名,裝置號。
1)使用mknod手工建立:mknod filename type major minor
2)自動建立裝置節點:
利用udev(mdev)來實現裝置檔案的自動建立,首先應保證支援udev(mdev),由busybox配置。在驅動初始化程式碼裡呼叫class_create為該裝置建立一個class,再為每個裝置呼叫device_create建立對應的裝置。
詳細解析見:Linux 字元裝置驅動開發 (二)—— 自動建立裝置節點
下面看一個例項,練習一下上面的操作:
hello.c
-
#include <linux/module.h>
-
#include <linux/fs.h>
-
#include <linux/cdev.h>
-
static
int major =
250;
-
static
int minor =
0;
-
static
dev_t devno;
-
static
struct cdev cdev;
-
static int hello_open (struct inode *inode, struct file *filep)
-
{
-
printk(
"hello_open \n");
-
return
0;
-
}
-
static
struct file_operations hello_ops=
-
{
-
.open = hello_open,
-
相關推薦
Linux 字元裝置驅動結構(一)—— cdev 結構體、裝置號相關知識解析
一、字元裝置基礎知識
1、裝置驅動分類
linux系統將裝置分為3類:字元裝置、塊裝置、網路裝置。使用驅動程式:
字元裝置:是指只能一個位元組一個位元組讀寫的裝置,不能隨機讀取裝置記憶體中的某一資料,讀取資料需要按照先後資料。
Linux 字元裝置驅動結構(一)—— cdev 結構體、裝置號相關知識解析[轉載]
一、字元裝置基礎知識1、裝置驅動分類 linux系統將裝置分為3類:字元裝置、塊裝置、網路裝置。使用驅動程式:字元裝置:是指只能一個位元組一個位元組讀寫的裝置,不能隨機讀取裝置記憶體中的某一資料,讀取資料需要按照先後資料。字元裝置是面向流的裝置,常見的字元裝置有滑鼠
Linux I2C裝置驅動編寫(一)
在Linux驅動中I2C系統中主要包含以下幾個成員:
I2C adapter 即I2C介面卡
I2C driver 某個I2C裝置的裝置驅動,可以以driver理解。
I2C client 某個I2C裝置的裝置宣告,可以以device理解。
I2C adapter
是
嵌入式Linux裝置驅動開發(一)
裝置驅動開發是Linux開發領域一個非常重要的部分,在Linux原始碼的85%都是驅動程式的程式碼。裝置驅動開發不僅需要了解硬體底層的知識,還需要擁有作業系統的背景。驅動程式追求的是高效,穩定,驅動程式發生的問題有可能直接導致整個系統的崩潰。
驅動程式不主動執
I/O體系結構和裝置驅動程式(一)
1、I/O體系結構
為確保計算機能夠正常工作,必須提供資料通路,讓資訊在連線到計算機的CPU、RAM、和I/O裝置之間流動,這些資料通路總稱為匯流排,擔當計算機內部主通訊通道的作用。
所有計算機都擁有一條系統匯流排,它連線大部分內部硬體裝置,一種典型的系統匯流排是PCI(
Linux設備驅動程序(一)設備驅動程序簡介
包括 收集 字符設備 調度器 計算機 啟動 驅動程序 str 單個
機制or策略:
驅動提供機制(what),而不是提供策略(how);
內核功能劃分:
根據內核完成任務的不同,可分為如下幾個部分:
1. 進程管理
負責進程的的創建和銷毀,並
如何寫DOS下的裝置驅動程式(一)
基本上我寫的文章中的程式例項都是32位的,需要執行在保護模式下,但是不要祈求在DOS下可以寫32位的裝置驅動程式,因為DOS本身是16位真實模式下的作業系統,當然其驅動程式的機制也只能是真實模式下的,儘管在DOS下可以編防寫模式的程式,但這些程式亦可以通過DPMI呼叫真實模式
Linux 下wifi 驅動開發(一)—— WiFi基礎知識解析
一、WiFi相關基礎概念
1、什麼是wifi
我們看一下百度百科是如何定義的:
Wi-Fi是一種可以將個人電腦、手持裝置(如pad、手機)等終端以無線方式互相連線的技術,事實上它是一個高頻無線電訊號。[1] 無線保真是一個無線網路通訊技術的品牌
Linux下I2C驅動分析(一)
最近在做一個基於全志A33晶片的android移植時發現嵌入式裝置很多都用到了I2C匯流排通訊,比如說攝像頭,G-sensor,觸控式螢幕等,為此我覺得很好的理解I2C裝置驅動在今後的嵌入式開發中是非常有好處的,而目前我也是處於學習階段,便將這些學習的過程給
嵌入式Linux——nand flash 驅動開發(一):硬體介紹
本文章講nand flash的驅動開發,而在介紹驅動程式之前我想先介紹一下我所用的硬體。這樣對程式的開發更為方便。本文所使用的nand flash晶片為K9F2G08U0C,下面是他的一些必要的特性:
數據結構(一)線性表循環鏈表相關補充
width hide cli 機器 都是 實時 思路 在外 for循環 (一)合並兩個循環鏈表
p = rearA->next; //A的頭結點,一會還要使用
rearA->next = rearB->next->next
資料結構(一)——資料結構初識及演算法複雜度介紹
資料結構,毋庸置疑,在程式設計中是極其重要的存在,在電腦科學中,不只是一般程式設計的基礎,而且是設計和實現編譯程式、作業系統、資料庫系統及其他系統程式和大型應用程式的重要基礎。
因此,CSDN的第一篇文章就選擇了資料結構(也是打算再次補習可
資料結構(一):資料結構的基本概念和演算法的時間和空間複雜度
資料結構討論的範疇
計算機技術的兩大支柱:1是資料結構,2是演算法。在某種程度上講,程式設計等同於資料結構+演算法。
程式設計是為計算機設計一組指令集,演算法是解決問題的策略,資料結構是模型。
問
PE檔案結構(一) 基本結構
PE檔案結構(一)
參考
書:《加密與解密》
視訊:小甲魚 解密系列 視訊
exe,dll都是PE(Portable Execute)檔案結構。PE檔案使用的是一個平面地址空間,所有程式碼和資料都被合併在一起,組成一個很大的結構。先看2張圖,來大
Linux 字元裝置驅動結構(四)—— file_operations 結構體知識解析
前面在 Linux 字元裝置驅動開發基礎 (三)—— 字元裝置驅動結構(中) ,我們已經介紹了兩種重要的資料結構 struct inode{...}與
struct file{...} ,下面來介紹另一個比較重要資料結構
struct _file_oper
Linux USB 驅動開發(一)—— USB裝置基礎概念
Linux USB 驅動開發(一)—— USB裝置基礎概念
在終端使用者看來,USB裝置為主機提供了多種多樣的附加功能,如檔案傳輸,聲音播放等,但對USB主機來說,它與所有USB裝置的介面都是一致的。一個USB裝置由3個功
linux裝置驅動之USB主機控制器驅動分析 (一)
一:前言
Usb是一個很複雜的系統.在usb2.0規範中,將其定義成了一個分層模型.linux中的程式碼也是按照這個分層模型來設計的.具體的分為 usb裝置,hub和主機控制器三部份.在閱讀程式碼的時候,必須要參考相應的規範.最基本的就是USB2.0的spec.
Linux I2C設備驅動編寫(一)
ive AC ner 解決 args nali smb man lin http://blog.csdn.net/airk000/article/details/21345457
在Linux驅動中I2C系統中主要包含以下幾個成員:
I2C adapter 即I2C適配
Linux驅動開發(3)——以module方式註冊裝置
通過 s3c_device_leds_ctl->*smdk4x12_devices[]->platform_add_devices()->platform_device_register() 可以直接使用“platform_device_register()”來註冊裝置
嵌入式Linux裝置驅動開發(二)
上一篇中介紹到裝置驅動如何匹配裝置以及繫結裝置的,在Linux系統下進行註冊,這裡將繼續介紹probe函式的功能。
5、probe函式
Probe()函式必須驗證指定裝置的硬體是否真的存在,probe()可以使用裝置的資源,包括時鐘,platform_dat