1、主設備號和次設備號(二者一起為設備號):
一個字符設備或塊設備都有一個主設備號和一個次設備號。主設備號用來標識與設備文件相連的驅動程序,用來反 映設備類型。次設備號被驅動程序用來辨別操作的是哪個設備,用來區分同類型的設備。
linux內核中,設備號用dev_t來描述,2.6.28中定義如下:
typedef u_long dev_t;
在32位機中是4個字節,高12位表示主設備號,低12位表示次設備號。
可以使用下列宏從dev_t中獲得主次設備號:
也可以使用下列宏通過主次設備號生成dev_t:
MAJOR(dev_t dev);
MKDEV(int major,int minor);
MINOR(dev_t dev);
//宏定義:
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1) //1U后面的U表示1是unsigned int (如果是在VS編譯器下的話,就是32位),把1U左移(<<,這是位運算符號,按位左移)20位就相當于1 * 2^20,然后-1,也就是mask替代了(2^20-1)。
//得到的結果就是高12位全為0,低20位全為1
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
2.分配設備號(兩種方法)
(1靜態申請
int register_chrdev_region(dev_t from,unsigned count,const char *name);
(2)動態分配
int alloc_chardev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name);
注銷設備號:
void unregister_chrdev_region(dev_t from, unsigned count);
創建設備文件:
利用cat /proc/devices查看申請到的設備名,設備號。
(1)使用mknod手工創建:mknod filename type major minor
(2) 自動創建:
利用udev(mdev)來實現設備文件的自動創建,首先應保證支持udev(mdev),由busybox
配置,在驅動初始化代碼里調用class_create為該設備創建一個class,再為每個設備調用device_create
創建對應的設備。
3、字符設備驅動程序重要的數據結構:
(1)struct file:
代表一個打開的文件描述符,系統中每一個打開的文件在內核中都有一個關聯的struct file。它由內核在open時創建,并傳遞給在文件上操作的任何函數,直到最后關閉。當文件的所有實例都關閉之后,內核釋放這個數據結構。
//重要成員
const struct file_operations *f_op; //該操作是定義文件關聯操作的。內核在執行open時對這個指針賦值。
off_t f_pos; //該文件讀寫位置。
void *private_data; //該成員是系統調用時保存狀態信息非常有用的資源。
2)struct inode:
用來記錄文件的物理信息。它和代表打開的file結構是不同的。一個文件可以對應多個file結構,但只有一個inode結構。inode一般作為file_operations結構中函數的參數傳遞過來。
inode譯成中文就是索引節點。每個存儲設備或存儲設備的分區(存儲設備是硬盤、軟盤、U盤 ... ... )被格式化為文件系統后,應該有兩部份,一部份是inode,另一部份是Block,Block是用來存儲數據用的。而inode呢,就是用來存儲這些數 據的信息,這些信息包括文件大小、屬主、歸屬的用戶組、讀寫權限等。inode為每個文件進行信息索引,所以就有了inode的數值。操作系統根據指令, 能通過inode值最快的找到相對應的文件。
dev_t i_rdev; //對表示設備文件的inode結構,該字段包含了真正的設備編號。
struct cdev *i_cdev; //是表示字符設備的內核的內部結構,當inode指向一個字符設備文件時,該字段包含了指向struct_cdev結構的指針。
我們也可以使用下邊兩個宏從inode中獲得主設備號和次設備號
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);
(3)struct file_operations
struct file_operation ***_ops={
.owner=THIS_MODULE,
.llseek=***_llseek,
.read=***_read,
.write=***_write,
.ioctl=***_ioctl,
.open=***_open,
.release=***_release,
.....
}; //strude file_operations 是一個函數指針的集合
struct module *owner;
字符設備驅動程序注意事項
1)open()調用可能由于幾個原因而失敗。
(2)成功運行的read()和write()返回的字節數可能是1至請求的字節數之間的任意值,因此應用程序必須能處理這些情況。
(3)即使1字節的數據讀或寫就緒,select()也會返回成功。
(4)很多字符驅動程序方法是可選的,并不是所有的方法都提供。
另外,字符驅動程序不僅在drivers/char/目錄下。下面是一些“超級”字符驅動程序:
(1)串行驅動程序,放在drivers/serial/目錄下。
(2)輸入驅動程序,放在drivers/input/目錄下。
(3)幀緩存區(/dev/fb/*)提供對顯存的訪問,/dev/mem提供對系統內存的訪問途徑。
(4)一些設備類支持少量采用字符接口的硬件。
(5)一些子系統提供額外的字符接口,以向用戶空間提供原始的設備模型。例如MTD子系統。
(6)一些內核層提供鉤子,通過導出相應的字符接口實現用戶空間的設備驅動程序。 例如ioctl。
在drivers/目錄下的register_chrdev上運行grep-r可了解內核中字符驅動程序的大致情況。
設備驅動程序是內核的一部分,它完成以下的功能
1、對設備初始化和釋放;
(1)字符設備cdev結構體初始化:
***********不是每個字符設備驅動都需要,cdev是為了構建設備模型,便于設備文件的管理所產生的。如果你的字符設備比較簡單或者你不需要構建設備模型,是可以不需要cdev.
file_operation結構是虛擬層上的東西,這樣使得驅動程序可以操作設備。*******************
內核中每個字符設備都對應一個 cdev 結構的變量,下面是它的定義: linux-2.6.22/include/linux/cdev.h
struct cdev {
struct kobject kobj; // 每個 cdev 都是一個 kobject
struct module *owner; // 指向實現驅動的模塊
const struct file_operations *ops; // 操縱這個字符設備文件的方法
struct list_head list; // 與 cdev 對應的字符設備文件的 inode->i_devices 的鏈表頭
dev_t dev; // 起始設備編號
unsigned int count; // 設備范圍號大小
};
靜態內存定義初始化:
struct cdev my_cdev;
cdev_init(&my_cdev, &fops);
my_cdev.owner = THIS_MODULE;
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 *my_cdev = cdev_alloc();
my_cdev->ops = &fops;
my_cdev->owner = THIS_MODULE;
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;
}
由此可見,兩個函數完成都功能基本一致,只是 cdev_init() 還多賦了一個 cdev->ops 的值。
初始化 cdev 后,需要把它添加到系統中去。為此可以調用 cdev_add() 函數。傳入 cdev 結構的指針,起始設備編號,以及設備編號范圍。
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
p->dev = dev;
p->count = count;
return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}
關 于 kobj_map() 函數就不展開了,我只是大致講一下它的原理。內核中所有都字符設備都會記錄在一個 kobj_map 結構的 cdev_map 變量中。這個結構的變量中包含一個散列表用來快速存取所有的對象。kobj_map() 函數就是用來把字符設備編號和 cdev 結構變量一起保存到 cdev_map 這個散列表里。當后續要打開一個字符設備文件時,通過調用 kobj_lookup() 函數,根據設備編號就可以找到 cdev 結構變量,從而取出其中的 ops 字段。
當一個字符設備驅動不再需要的時候(比如模塊卸載),就可以用 cdev_del() 函數來釋放 cdev 占用的內存。
void cdev_del(struct cdev *p)
{
cdev_unmap(p->dev, p->count);
kobject_put(&p->kobj);
}
其中 cdev_unmap() 調用 kobj_unmap() 來釋放 cdev_map 散列表中的對象。kobject_put() 釋放 cdev 結構本身。
下面這個是必須要有的:
設備初始化
static int first_drv_init(void)
{
major=register_chrdev(0,'first_drv',&first_drv_fops); //注冊,告訴內核。
firstdrv_class=class_create(THIS_MODULE,'firstdrv'); //創建一個類
if(IS_ERR(firstdrv_class))
return PTR_ERR(firstdrv_class);
firstdrv_class_dev=class_device_create(firstdrv_class,NULL,MKDEV(major,0),NULL,'xyz');//xyz為設備名字
gpfcon=(volatile unsigned long *)ioremap(0x56000050,16);
gpfdat=gpfcon+1;
if(unlikely(IS_ERR(firstdrv_class_dev)))
return PTR_ERR(firstdev_class_drv);
return 0;
}
設備卸載
static vodi first_drv_exit(void)
{
unregister_chrdev(major,'first_drv');//卸載
class_device_unregister(firstdrv_class_dev);
class_destroy(firstdrv_class);
iounremap();
}
2、把數據從內核傳送到硬件(copy_to_user)和從硬件讀取數據(copy_form_user);
結構體file_operations在頭文件 linux/fs.h中定義,用來存儲驅動內核模塊提供的對設備進行各種操作的函數的指針。該結構體的每個域都對應著驅動內核模塊用來處理某個被請求的 事務的函數的地址。
fileoprations結構體上定義的函數進行具體的數據操作。
static struct file_operations second_drv fops={
.owner=THIS_MODULE;
.write=second_drv_write;
.open=second_drv_open;
.read=second_drv_read;
}
int major;
static int first_drv_open(struct inode *inode,struct file *file)
{
gpfcon &= ~((0x3<<(4*2))|(0x3<<(5*2))|(0x3<<(6*2))); //GPF4,5,6為輸出
gpfcon |= ((0x1<<(4*2))|(0x1<<(5*2))|(0x1<<(6*2))); //
return 0;
}
static ssize_t first_drv_read(struct inode *inode,struct file *file)
{
return 0;
}
static ssize_t first_drv_write(struct file *file,const char _user *buf,size_t count,loff_t *ppos)
{
int val;
上一篇:s3c2440串口裸板驅動(使用fifo)
下一篇:Linux設備驅動中的異步通知與異步I/O
推薦閱讀最新更新時間:2025-03-26 12:15


