字元裝置驅動——申請裝置號、註冊字元裝置
1. 裝置號
主裝置號:用來標識與裝置檔案相關的驅動程式, ——反應裝置型別 次裝置號:為核心所用,被驅動程式用來辨別操作那個裝置檔案 ——區分同類型的具體某個裝置 1.1 裝置號的內部表達 在核心中,儲存裝置號(包括主裝置號和此裝置好)使用型別 dev_t (<linux/types.h>) 這是一個unsigned int 是一個32位的無符號整型。。 主裝置號——高12位 此裝置號——低12位 我們可以使用巨集來取一個裝置號(dev)的主裝置號和此裝置號 定義在 <linux/kdev_t.h> MAJOR(dev_t dev) 取主裝置號 MINOR(dev_t dev) 取次裝置號 也可以將主次裝置號合成一個完整的dev_t型別的裝置號 MKDEV(int major, int minor) 將主次裝置號轉換成dev_t 1.2 分配主次裝置號 linux可以採用靜態申請和動態申請兩種方法來分配主次裝置號 靜態申請 1. 根據Documentation/devices.txt, 確定一個沒有使用的主裝置號 2. 使用register_chrdev_region(dev_t first, unsigned int count, char *name) 定義在<linux/fs.h> count 為所請求的連續裝置編號個數, 如果count過大,可以會各下一個主裝置號重疊。 name 為裝置名, 註冊後 出現在/proc/devices和sysfs中 動態分配 作為一個新的驅動程式,應該使用動態分配機制獲取主裝置號 alloc_chrdev_region(dev_t *dev, unsigned int first, unsigned int count, char *name) 不管用何種方法分配, 不用時都要釋放掉 void unregister_chrdev_region(dev_t first, unsigned int count); 靜態申請與動態申請的優缺點: 靜態申請——簡單(優); 一旦驅動程式被廣泛命使用, 隨機選定的主裝置號可以造成衝突,使驅動程式無法註冊。(劣) 動態申請——簡單,易於驅動推廣(優);無法在驅動安裝前建立裝置檔案, 因為不能保證分配的主裝置號始終一致。(劣)2 建立裝置檔案
2.1 mknod手工建立
mknod 用法: mknod filename type major minor filename : 裝置檔名 type : 裝置檔案型別 major : 主裝置號 minor : 次裝置號2.2 自動建立
如果我們在驅動裡面動態建立的話需要這樣做struct class *cdev_class; cdev_class = class_create(owner,name) device_create(_cls,_parent,_devt,_device,_fmt)
2.3 模組退出時要銷燬裝置檔案
device_destroy(_cls,_device)
class_destroy(struct class * cls)
3. 一些重要的結構體
大部分的基礎性的驅動操作包括 3 個重要的核心資料結構, 稱為 file_operations, file, 和 inode.- 檔案結構 struct file
代表一個開啟的檔案。系統中每個找開的檔案在核心空間一個關聯的 struct file, 它由核心在開啟檔案時建立, 在檔案關閉後釋放 重要成員loff_t f_ops /* 檔案讀寫位置 */ struct file_operations *f_op /* 檔案關聯的操作 */ mode_t f_mode /* 模式確定檔案可讀或者可寫 */ unsigned int f_flags /* 檔案標誌,一般用來判斷是否請求非阻塞操 作, 標誌定義<linux/fcntl.h> */ void *private_data;
open 系統呼叫設定這個指標為 NULL, 在為驅動呼叫 open 方法之前. 你可自由使用這個成員或者忽略它; 你可以使用這個成員來指向分配的資料, 但是接著你必須記住在核心銷燬檔案結構之前, 在 release 方法中釋放那個記憶體. private_data是一個有用的資源, 在系統呼叫間保留狀態資訊, 我們大部分例子模組都使用它.
- 檔案操作 struct file_operation
file_operation 結構是一個字元驅動如何建立這個連線. 這個結構, 定 義在 <linux/fs.h>, 是一個函式指標的集合. 每個開啟檔案(內部用一個 file 結構來代 表, 稍後我們會檢視)與它自身的函式集合相關連( 通過包含一個稱為 f_op 的成員, 它指 向一個 file_operations 結構). 這些操作大部分負責實現系統呼叫, 因此, 命名為 open, read, 等等. 我們可以認為檔案是一個"物件"並且其上的函式操作稱為它的"方法", 使用 面向物件程式設計的術語來表示一個物件宣告的用來操作物件的動作. 下面是一個file_operationd的宣告:int (*open) (struct inode *, struct file *);struct file_operations my_fops = { .owner = THIS_MODULE, .llseek = my_llseek, .read = my_read, .write = my_write, .ioctl = my_ioctl, .open = my_open, .release = my_release, };
該宣告使用標準的 C 標記式結構初始化語法. 這個語法是首選的, 因為它使驅動在結構定義的改變之間更加可移植 。 下面列出file_operationd部分成員的含義:(其他成員自行百度) struct module *owner 第一個 file_operations 成員根本不是一個操作; 它是一個指向擁有這個結構的模組的指標. 這個成員用來在它的操作還在被使用時阻止模組被解除安裝. 幾乎所有時間中, 它被簡單初始化為 THIS_MODULE, 一個在 <linux/module.h> 中定義的巨集. loff_t (*llseek) (struct file *, loff_t, int); llseek 方法用作改變檔案中的當前讀/寫位置, 並且新位置作為(正的)返回值. loff_t 引數是一個"long offset", 並且就算在 32 位平臺上也至少 64 位寬. 錯誤由一個負返回值指示. 如果這個函式指標是 NULL, seek 呼叫會以潛在地無法預知的方式修改 file 結構中的位置計數器( 在"file 結構" 一節中描述). ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); 用來從裝置中獲取資料. 在這個位置的一個空指標導致 read 系統呼叫以 - EINVAL("Invalid argument") 失敗. 一個非負返回值代表了成功讀取的位元組數( 返回值是一個 "signed size" 型別, 常常是目標平臺本地的整數型別). ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); 傳送資料給裝置. 如果 NULL, -EINVAL 返回給呼叫 write 系統呼叫的程式. 如果非負, 返回值代表成功寫的位元組數.
儘管這常常是對裝置檔案進行的第一個操作, 不要求驅動宣告一個對應的方法. 如果這個項是 NULL, 裝置開啟一直成功, 但是你的驅動不會得到通知 int (*release) (struct inode *, struct file *); 在檔案結構被釋放時引用這個操作. 相當於close
- struct inode
重要成員 dev_t i_rdev: / * 對於代表裝置檔案的節點, 這個成員包含實際的裝置編號 */
struct cdev *i_cdev; /* struct cdev 是核心的內部結構, 代表字元裝置; 這個成員包含一個指標, 指向這個結構, 當節點指的是一個字元裝置檔案時.*/
4. 字元裝置的註冊
Linux2.6核心中,字元裝置使用struct cdev來描述字元裝置驅動的註冊。 字元裝置驅動的註冊主要有三個步驟 (1) 分配cdev (2)初始化cdev (3)新增cdev 分配 struct cdev *my_cdev = cdev_alloc(); 初始化 void cdev_init(struct cdev *cdev, const struct file_operations *fops); cdev: 待初始化的cdev結構 fops: 裝置對應的操作函式集 註冊, 告訴核心 int cdev_add(struct cdev *dev, dev_t num, unsigned int count); dev: 新增到核心的字元裝置結構 num: 裝置響應的第一個裝置號 count: 關聯到裝置的裝置號數目,通常為1 去除字元裝置 void cdev_del(struct cdev *dev); 使用 cdev_add 是有幾個重要事情要記住 1.第一個是這個呼叫可能失敗. 如果它返回一個負的錯誤碼, 你的裝置沒有增加到系統中. 2. cdev_add 一返回成功, 你的裝置就是"活的"並且核心可以呼叫它的操作. 所以除非你的驅動完全準備好處理裝置上的操作, 你不應當呼叫 cdev_add.5 註冊字元裝置的一個例子
1. 還是線上原始碼://memdev.h
#ifndef _MEMDEV_H_
#define _MEMDEV_H_
#ifndef MEMDEV_MAJOR
#define MEMDEV_MAJOR 200
#endif
#ifndef MEMDEV_NR_DEVS
#define MEMDEV_NR_DEVS 2
#endif
#ifndef MEMDEV_SIZE
#define MEMDEV_SIZE 4096
#endif
struct mem_dev{
char* data;
unsigned long size;
};
#endif
//memdev.c
# include < linux / module.h >
# include < linux / types.h >
# include < linux / fs.h >
# include < linux / errno.h >
# include < linux / mm.h >
# include < linux / sched.h >
# include < linux / init.h >
# include < linux / cdev.h >
# include < asm / io.h >
# include < asm / system.h >
# include < asm / uaccess.h >
# include < linux / wait.h >
# include < linux / completion.h >
# include "memdev.h"
MODULE_LICENSE( "Dual BSD/GPL" );
static int mem_major = MEMDEV_MAJOR;
struct mem_dev * mem_devp; /*裝置結構體指標*/
struct cdev cdev;
/*檔案開啟函式*/
int mem_open( struct inode * inode, struct file * filp)
{
printk( "open own file\n" );
return 0 ;
}
/*檔案操作結構體*/
static const struct file_operations mem_fops =
{
.owner = THIS_MODULE,
.open = mem_open,
};
/*裝置驅動模組載入函式*/
static int memdev_init( void )
{
int result;
int i;
dev_t devno = MKDEV(mem_major, 0 );
/* 靜態申請裝置號*/
result = register_chrdev_region(devno, 2 , "memdev" );
if (result < 0 )
return result;
/*初始化cdev結構*/
cdev_init( & cdev, & mem_fops);
/* 註冊字元裝置 */
cdev_add( & cdev, MKDEV(mem_major, 0 ), MEMDEV_NR_DEVS);
return result;
}
/*模組解除安裝函式*/
static void memdev_exit( void )
{
cdev_del( & cdev); /*登出裝置*/
unregister_chrdev_region(MKDEV(mem_major, 0 ), 2 ); /*釋放裝置號*/
}
module_init(memdev_init);
module_exit(memdev_exit);
#Makefile
ifneq ($(KERNELRELEASE),)
obj-m := memdev.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD = $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm memdev.mod* module* memdev.o memdev.ko Module.*
endif
2. 測試
首先先make下,生成memdev.ko
然後insmod memdev.ko生成memdev模組
建立裝置節點:sudo mknod /dev/memdev_t c 200 0
接下開使用裝置檔案
下面是一個測試程式
// memusr.c
#include <stdio.h>
#include <string.h>
int main()
{
FILE *fp0;
/*開啟裝置檔案*/
fp0 = fopen("/dev/memdev_t","r+");
if (fp0 == NULL) {
printf("Open Memdev0 Error!\n");
return -1;
}
}
編譯執行,然後使用dmesg可以看到日誌檔案裡輸出
[38439.741816] Hello World!
[38657.654345] Goodbye
[40393.039520] open own file
記得要使用sudo 執行memusr 否則會顯示裝置開啟失敗。
相關推薦
字元裝置驅動——申請裝置號、註冊字元裝置
1. 裝置號 主裝置號:用來標識與裝置檔案相關的驅動程式, ——反應裝置型別 次裝置號:為核心所用,被驅動程式用來辨別操作那個裝置檔案 ——區分同類型的具體某個裝置 1.1 裝置號的內部表達 在核心中,儲存裝置號(包括主裝置號和
Linux驅動開發(9)——註冊字元裝置
static int scdev_init(void) { int ret = 0,i; dev_t num_dev; printk(KERN_EMERG "numdev_major is %d!\n",numdev_major); printk(KERN_EMERG "
linux裝置驅動模型之Kobject、kobj_type、kset
一、sysfs檔案系統簡介: 1、sysfs概述 sysfs檔案系統是核心物件(kobject)、屬性(kobj_type)、及它們相互關係的一種表現。 sysfs非常重要的特徵:使用者可以從sysfs中讀出核心資料,也可以將使用者資料寫入核心。 2、核心結構與sysfs對應關係:
Linux 字元裝置驅動結構(二)—— 自動建立裝置節點
上一篇我們介紹到建立裝置檔案的方法,利用cat /proc/devices檢視申請到的裝置名,裝置號。 第一種是使用mknod手工建立:mknod filename type major minor 第二種是自動建立裝置節點:利用u
物聯網框架ServerSuperIO.Core(.netcore)跨平臺,一套裝置驅動通吃嵌入式、上位機、雲服務
1. 概述... 2 2. ServerSuperIO.Core跨平臺開發環境... 2 3. ServerSuperIO.Core特點... 2 4. ServerSuperIO.Core與ServerSuperIO區別... 2 5. 嵌入式應用.
linux裝置驅動模型之匯流排、裝置、驅動三者的關係
匯流排、裝置、驅動,也就是bus、device、driver,在核心裡都有對應的結構體,在include/linux/device.h 裡定義。Device.h (linux-3.4.2\inclu
Linux裝置驅動第七篇:高階字元驅動操作之阻塞IO
我們之前介紹過簡單的read,write操作,那麼會有一個問題:當驅動無法立即響應請求該怎麼辦?比如一個程序呼叫read讀取資料,當沒有資料可讀時該怎麼辦,是立即返回還是等到有資料的時候;另一種情況是程序呼叫write向裝置寫資料,如果緩衝區滿了或者裝置正忙的時候怎麼辦,
Linux裝置驅動程式學習(12) -Linux裝置模型(底層原理簡介)
以《LDD3》的說法:Linux裝置模型這部分內容可以認為是高階教材,對於多數程式作者來說是不必要的。但是我個人認為:對於一個嵌入式Linux的底層程式設計師來說,這部分內容是很重要的。 以我學習的ARM9為例,有很多匯流排(如SPI、IIC、IIS等等)在Linux下已經被
Linux裝置驅動模型之platform匯流排深入淺出(加入裝置樹)
在Linux2.6以後的裝置驅動模型中,需關心匯流排,裝置和驅動這三種實體,匯流排將裝置和驅動繫結。在系統每註冊一個裝置的時候,會尋找與之匹配的驅動;相反,在系統每註冊一個驅動的時候,會尋找與之匹配的裝置,而匹配由匯流排完成。 對於依附在USB、PCI、I2C
Linux 字元裝置驅動結構(一)—— cdev 結構體、裝置號相關知識解析
一、字元裝置基礎知識 1、裝置驅動分類 linux系統將裝置分為3類:字元裝置、塊裝置、網路裝置。使用驅動程式: 字元裝置:是指只能一個位元組一個位元組讀寫的裝置,不能隨機讀取裝置記憶體中的某一資料,讀取資料需要按照先後資料。
Linux 字元裝置驅動結構(一)—— cdev 結構體、裝置號相關知識解析[轉載]
一、字元裝置基礎知識1、裝置驅動分類 linux系統將裝置分為3類:字元裝置、塊裝置、網路裝置。使用驅動程式:字元裝置:是指只能一個位元組一個位元組讀寫的裝置,不能隨機讀取裝置記憶體中的某一資料,讀取資料需要按照先後資料。字元裝置是面向流的裝置,常見的字元裝置有滑鼠
【驅動】第1課、字元裝置驅動
=====節一、字元裝置驅動程式之概念介紹=====1、模組(即某單一驅動)是如何構建的?答:構建一個最基本的驅動模組,只需要4函式+1標頭檔案:模組裝載函式xx_init(), 模組解除安裝函式xx_exit(), module_init(), module_exit(), <linux/init.h
Linux字元裝置驅動模型--字元裝置的註冊
當我們編寫字元裝置驅動程式的時候,在進行字元裝置物件cdev的分配、初始化,裝置號的註冊這些初始化階段之後,就可以將它加入到系統中,這樣才能使用這個字元裝置。將一個字元裝置加入到系統中呼叫的函式時cdev_add,核心原始碼如下: int cdev_add(struct cdev *
【迅為iTop4412學習筆記】關於註冊裝置、註冊驅動、生成裝置節點小結
宣告 以下都是我剛開始看驅動視訊的個人強行解讀,如果有誤請指出,共同進步。 本節目標 瞭解註冊裝置、註冊驅動、生成裝置節點這些概念。 其實學到現在,很多東西我們都似懂非懂,而我也是剛學,也感覺很多東西似是而非,但好在程式碼過程和結果都是對
Linux字元裝置驅動註冊三種方法以及核心分析
Linux驅動是使用者訪問底層硬體的橋樑,驅動有可以簡單分成三類:字元裝置、塊裝置、網路裝置。其中最多的是字元裝置,其中字元裝置的註冊方法主要有三種:雜項設備註冊、早期字元設備註冊、標準字元設備註冊。以及詳細介紹各類方法註冊。 開發環境: PC:WMwork
註冊字元裝置 >>Linux裝置驅動程式
文章目錄 [0x100] 字元裝置相關規則 [0x110]裝置檔案特徵 [0x120]裝置編號特徵 [0x200]操作函式介面核心實現 [0x210]轉換裝置編號 [0x220]分配裝置編號 [0x2
字元裝置驅動與塊裝置驅動、網路裝置驅動的區別
在Linux作業系統下有3類主要的裝置檔案型別:塊裝置、字元裝置和網路裝置。這種分類方法可以將控制輸入/輸出裝置的驅動程式與其他作業系統軟體分離開來。字元裝置是指存取時沒有快取的裝置。典型的字元裝置包括滑鼠、鍵盤、序列口等。字元裝置與塊裝置的主要區別是:在對字元裝置發出讀/
Linux 字元裝置驅動結構(三)—— file、inode結構體及chardevs陣列等相關知識解析
先看下面這張圖,這是Linux 中虛擬檔案系統、一般的裝置檔案與裝置驅動程式值間的函式呼叫關係; 上面這張圖展現了一個應用程式呼叫字元裝置驅動的過程, 在裝置驅動程式的設計中,一般而言,會關心 file 和 inode 這兩個結構體
字元裝置驅動註冊與裝置節點建立----函式講解與程式碼示例
1、字元裝置編號註冊 核心提供了三個函式來註冊一組字元裝置編號,這三個函式分別是 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()。 (1)register_chrdev 比較老的核心註冊的形式,
裝置驅動歸納總結(八):1.匯流排、裝置和驅動 —— 匯流排的註冊
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 這幾天一直在看裝置模型,核心的程式碼看得我越來越沮喪,特別是kboject、kset和ktype之間的關係。但是,裝置