字符設備模板

linux驅動


makefile寫法:

ifneq
($*KERNELRELEASE),)
obj-m
:=hello.o
else
KERNELDIR?=/lib/modules/$(shell
uname -r) /build
PWD
:=$(shell pwd)
default:
$(MAKE)
-C $(KERNELDIR) M=$(PWD) modules
endif


初始化和關閉:

static
int __init initialization_funciton(void)
{
*
}
module_init(initialization_function);
static
void __exit cleanup_function(void)
{
*
}
module_exit(cleanup_function);

__init意爲在初始化時調用,調用完後會被清除出內存

__exit意爲只在清除時調用,調用後即清除



參數傳遞:

static char *whom =”world”;

static int howmany=1;

module_param(howmany,int,S_IRUGO);

module_param(whom,charp,S_IRUGO);


內核支持的模塊參數類型如下:

bool、invbool、charp、int、long、uint、ulong、ushort


聲明數組參數用以下形式:
module_param_array(name,type,num,perm);

perm爲訪問許可值,S_IRUGO爲任何人可讀,S_IWUSR爲root可寫。



主設備號和次設備號

通常而言,主設備號指示設備對應的驅動程序,次設備號用於正確確定設備文件所指的設備。內核中用dev_t類型保存設備號

獲得主次設備號:

MAJOR(dev_t,dev);

MINOR(dev_t,dev);

生成dev_t類型:

MKDEV(int major,int minor);

分配和釋放設備編號:

靜態分配:

<linux/fs.h>

int register_chrdev_region(dev_t first, unsigned int count, char *name);

first是要分配的設備編號的起始值,count是要請求連續設備編號的個數,name是與範圍相關聯的名稱。成功返回0,錯誤返回負的錯誤碼。

動態分配:

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

釋放:

void unregister_chrdev_region(dev_t first, unsigned int count);



重要的數據結構:

設備號與驅動程序操作的關聯:

file_operations,指向該結構的指針一般名叫fops,包含指向對文件各個操作的實現函數的指針。

有個owner字段需要被初始化爲THIS_MODULE


file結構:

file結構代表一個打開的文件,系統中每一個打開的文件在內核空間都有一個對應file結構

需要用到的file字段:

mode_t

f_mode

文本模式,標識文件是可讀可寫,但不需要在驅動中檢測

loff_t

f_ops

當前讀寫位置

unsigned int

f_flags

文件標誌,定義在<linux/fcntl.h>

Struct file_operations

* f_op

與文件相關的操作

void

* private_data

私有數據,必須在file結構被銷燬前在relrease方法中釋放內存

struct dentry

* f_dentry

文件對應的目錄項結構


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);


字符設備的註冊:

內核使用struct cdev結構表示字符設備,定義在<linux/cdev.h>

分配和初始化cdev的兩種方式:

獲得獨立的結構:

struct cdev *my_code=cdev_alloc();

my_cdev->ops=&my_fops;

或者嵌入到自己的設備:(不知道爲什麼,,,)

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

還有一個struct cdev的owner字段需要被初始化爲THIS_MODULE

告知內核該結構的信息:

int cdev_add(struct cdev *dev, dev_t num, unsigned int count);

num是設備對應的第一個設備號,count應該是和該設備關聯的設備編號的數量。

在驅動程序還沒有完全準備好處理設備上的操作時,不能調用cdev_add。

從系統中移除一個字符設備:

void cdev_del(struct cdev *dev);



open和release

open應該完成以下操作:
檢查設備特定的錯誤

如果設備是首次打開,則對其進行初始化

如有必要,更新f_op指針

分配並填寫置於filp-private_data裏的數據結構。

open方法原型:

int (*open) (struct inode *inode, struct file *filep);

inode參數中i_cdev字段包含註冊設備時的cdev的指針

struct cdev結構體在裝在模塊時被裝如內存,全局只有一個。

個人理解:cdev結構體代表着字符設備,自定義的字符設備需要將cdev結構嵌入到自定義的結構體中,當需要訪問這個結構體時,需要通過inode結構獲得cdev指針,然後通過container_of宏獲得自定義結構體的指針。然後將這個指針通過寫入file結構的private_data傳遞給其他文件操作函數。


release應該完成以下操作:

釋放由open分配的保存在private_data中的所有內容

在最後一次關閉操作時關閉設備。

並不是每個close方法都調用release方法,只在file中維護的計數器爲0時,才調用release。



實例scull的內存結構:

用到定義在<linux/slab.h>的兩個函數

void *kmalloc(size_t size, int flags);

void kfree(void *ptr); //將kmalloc返回的指針傳給kfree,但傳NULL是合法的


定義在<asm/access.h>的兩個函數

unsigned long copy_to_user(void __user *to,const void *from,unsigned long count);

unsigned long copy_from_user(void *to,const void __user *from,unsigned long count);

這兩個函數還會檢測用戶空間指針的有效性,遇到無效地址不拷貝或僅拷貝部分內容,返回值爲還需拷貝的數量。如果確定不需要檢測,可使用__copy_to_user和__copy_from_user。

read和write:

ssize_t read(struct file *filp, char __user *buf, size_t count, loff_t *offp);

ssize_t write(struct file *filp, const char __user *buff, size_t count, loff_t *offp);


ssize_t類型爲“signed size type(有符號的尺寸類型)” loff_t爲“long offset type(長偏移量類型)”,指明用戶在文件中進行存取操作的位置。

出錯時,read和write方法都返回一個負值,大於等於0的返回值告訴的調用程序成功傳遞輸了多少個字節。如果在正確傳輸部分數據之後發生了錯誤,則返回值必須是成功傳輸的字節數,但這個錯誤只能在下一次函數調用時纔會得到報告。這種實現慣例要求驅動程序必須記住錯誤的發生,這樣才能在將來把錯誤狀態返回給應用程序。

內核函數通過返回負值來表示錯誤,而且該返回值表明了錯誤的類型,但運行在用戶空間的程序看到的始終是作爲返回值的-1。爲了找出出錯的原因,用戶空間的程序必須訪問errno變量。用戶空間這種行爲源於POSIX標準,但該標準未對內核內部的操作做任何要求。

read方法:
返回值是等於read系統調用傳遞的count參數,則說明所請求的字節數成功完成了。

返回值小於count,則說明只傳輸了部分數據。需要程序繼續讀取,如果用fread函數,則會一直讀

返回值爲0,表示已經到文件尾

負值表示出現錯誤。

還一種情況是,現在沒有數據,以後可能會有


write方法:

返回值等於count則完成傳輸

是正的但小於count,則完成部分

爲0表示什麼也沒寫入。但這不是錯誤,可能是沒有理由返回一個錯誤碼,例如阻塞

負值表示錯誤。錯誤碼定義在<linux/errno.h>


readv和writev

向量操作,這些向量型函數具有一個結構體數組,每個結構包含一個指向緩衝區的指針和一個長度值。如果驅動程序沒有提供用於處理向量操作的方法,readv和writev會通過read和write方法的多次調用實現。

原型:ssize_t (*readv) (struct file *filp, const struct iovec *iov, unsigned long count, loff_t *ppos);

ssize_t (*writev)(struct file *filp, const struct iovec *iov, unsigned long count, loff_t  *ppos);


struct iovec

{

void __user *iov_base;

__kernel_size_t iov_len;

}

iovec應由用戶程序創建。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章