字符設備驅動

字符設備驅動程序是由一個cdev結構描述的,其定義爲:

struct cdev {
struct kobject kobj;//內嵌的kobject
struct module *owner;
const struct file_operations *ops;
struct list_head list;//與字符設備文件對應的索引節點鏈表的頭,該鏈表用於收集相同字符設備驅動程序
//所對應的設備文件的索引節點。
dev_t dev;//初始主設備號和次設備號
unsigned int count;//設備號的範圍大小
};
內核提供了cdev_alloc()函數,用於動態的分配cdev描述符,並初始化內嵌的kobject數據結構,因此引用計數器的值變爲0
時會自動釋放該描述符。
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進行初始化。
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;
} 
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);
}
使用alloc_chrdev_region()和register_chrdev_region()函數爲驅動分配任意範圍內的設備號。
使用register_chrdev()函數分配一個固定的設備號範圍.在這種情形下,設備驅動程序不必調用cdev_add()函數。
unregister_chrdev_region(dev_t from,unsigned count)
< b>2.常見函數</b>
copy_to_user(buf, &global_var, sizeof(int));
copy_from_user(&global_var, buf, sizeof(int));
可以通過filp->private_data傳遞私有數據。
3.驅動中的併發操作
(1)屏蔽中斷
local_irq_disable() ; //屏蔽中斷 
local_irq_enabale(); //打開中斷
只能禁止和使能本CPU的中斷,所以並不能解決SMP引發的競爭。 
(2)原子操作 
// 設置原子變量的值
void atomic_set(atomic_t *v, int i); // 設置原子變量的值爲i
atomic_t v = ATOMIC_INIT(0); // 定義原子變量v,並初始化爲0
// 獲取原子變量的值
atomic_read(atomic_t *v); // 返回原子變量的值
// 原子變量加/減
void atomic_add(int i, atomic_t *v); // 原子變量加i
void atomic_sub(int i, atomic_t *v); // 原子變量減i 
// 原子變量自增/自減
void atomic_inc(atomic_t *v); // 原子變量增加1
void atomic_dec(atomic_t *v); // 原子變量減少1
// 操作並測試:對原子變量進行自增、自減和減操作後(沒有加)測試其是否爲0,爲0則返回true,否則返回false
int atomic_inc_and_test(atomic_t *v);
int atomic_dec_and_test(atomic_t *v);
int atomic_sub_and_test(int i, atomic_t *v);
// 操作並返回: 對原子變量進行加/減和自增/自減操作,並返回新的值
int atomic_add_return(int i, atomic_t *v);
int atomic_sub_return(int i, atomic_t *v);
int atomic_inc_return(atomic_t *v);
int atomic_dec_return(atomic_t *v);
位原子操作:
// 設置位
void set_bit(nr, void *addr); // 設置addr地址的第nr位,即將位寫1
// 清除位
void clear_bit(nr, void *addr); // 清除addr地址的第nr位,即將位寫0 
// 改變位
void change_bit(nr, void *addr); // 對addr地址的第nr位取反
// 測試位
test_bit(nr, void *addr); // 返回addr地址的第nr位
// 測試並操作:等同於執行test_bit(nr, void *addr)後再執行xxx_bit(nr, void *addr)
int test_and_set_bit(nr, void *addr);
int test_and_clear_bit(nr, void *addr);
int test_and_change_bit(nr, void *addr);
(3)自旋鎖(spin lock)——“在原地打轉”
// 定義自旋鎖 
spinlock_t spin; 
// 初始化自旋鎖
spin_lock_init(lock);
// 獲得自旋鎖:若能立即獲得鎖,它獲得鎖並返回,否則,自旋,直到該鎖持有者釋放
spin_lock(lock); 
// 嘗試獲得自旋鎖:若能立即獲得鎖,它獲得並返回真,否則立即返回假,不再自旋
spin_trylock(lock); 
// 釋放自旋鎖: 與spin_lock(lock)和spin_trylock(lock)配對使用
spin_unlock(lock); 
自旋鎖可以保證臨界區不受別的CPU和本CPU內的搶佔進程打擾,但是得到鎖的代碼路徑在執行臨界區的時候還可能受到中斷和底半部(BH)的影響。
爲防止這種影響,需要用到自旋鎖的衍生:
spin_lock_irq() = spin_lock() + local_irq_disable()
spin_unlock_irq() = spin_unlock() + local_irq_enable()
spin_lock_irqsave() = spin_lock() + local_irq_save()
spin_unlock_irqrestore() = spin_unlock() + local_irq_restore()
spin_lock_bh() = spin_lock() + local_bh_disable()
spin_unlock_bh() = spin_unlock() + local_bh_enable()
(4)信號量
當獲取不到信號量時,進程不會原地打轉而是進入休眠等待狀態。
// 定義信號量 
struct semaphore sem; 
// 初始化信號量:
// 初始化信號量,並設置sem的值爲val
void sema_init(struct semaphore *sem, int val); 
// 初始化一個用於互斥的信號量,sem的值設置爲1。等同於sema_init(struct semaphore *sem, 1) 
void init_MUTEX(struct semaphore *sem); 
// 等同於sema_init(struct semaphore *sem, 0) 
void init_MUTEX_LOCKED(struct semaphore *sem);
// 下面兩個宏是定義並初始化信號量的“快捷方式”:
DECLEAR_MUTEX(name) 
DECLEAR_MUTEX_LOCKED(name) 
// 獲得信號量:
// 用於獲得信號量,它會導致睡眠,不能在中斷上下文使用
void down(struct semaphore *sem); 
// 類似down(),因爲down()而進入休眠的進程不能被信號打斷,而因爲down_interruptible()而進入休眠的進程能被信號打斷, 
// 信號也會導致該函數返回,此時返回值非0
void down_interruptible(struct semaphore *sem); 
// 嘗試獲得信號量sem,若立即獲得,它就獲得該信號量並返回0,否則,返回非0.它不會導致調用者睡眠,可在中斷上下文使用
int down_trylock(struct semaphore *sem); 
// 使用down_interruptible()獲取信號量時,對返回值一般會進行檢查,若非0,通常立即返回-ERESTARTSYS
// 釋放信號量
// 釋放信號量sem, 喚醒等待者
void up(struct semaphore *sem); 
(5)完成量(completion)提供了一種比信號量更好的同步機制,它用於一個執行單元等待另一個執行單元執行完某事。
completion相關操作:
// 定義完成量
struct completion my_completion;
// 初始化completion
init_completion(&my_completion);
// 定義和初始化快捷方式:
DECLEAR_COMPLETION(my_completion);
// 等待一個completion被喚醒
void wait_for_completion(struct completion *c);
// 喚醒完成量
void cmplete(struct completion *c);
void cmplete_all(struct completion *c);
3.阻塞和非阻塞
(1)阻塞
使用等待隊列實現阻塞I/O。信號量在內核中就是依賴等待隊列來實現的。
/定義初始化‘等待隊列頭’
Wait_queue_head_t my_queue;
Init_wait_queue_head(&my_queue);
DECLARE_WAIT_QUEUE_HEAD (name);
//定義並初始化一個等待隊列
DECLARE_WAITQUEUE(name, tsk);
//將等待隊列添加到等待隊列頭Q指向的等待隊列鏈表中
Void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
//將等待隊列從等待隊列頭q指向的等待隊列鏈表中移除
Void fastcall remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait);
//等待事件,queue爲等待隊列頭,timeout以jiffy爲單位
Wait_event(queue, condition);
Wait_event_interruptible(queue, condition);
Wait_event_timeout(queue, condition, timeout);
Wait_event_interruptible_timeout(queue, condition, timeout);
//喚醒隊列
Void wake_up(wait_queue_head_t *q);
Void wake_up_interruptible (wait_queu_head_t *q);
注意:wake_up可喚醒wait_event或者wait_event_interruptible,但是 wake_up_interruptible只能喚醒wait_event_interruptible。
//在等待隊列上睡眠
Sleep_on(wait_queue_head_t *q);
Interruptible_sleep_on(wait_queue_head_t *q);
注意:sleep_on或者interruptible_sleep_on函數的作用就是將目前進程的狀態設置成TASK_UNINTERRUPTIBLE或者INTERRUPTIBLE。並定義
一個等待隊列,之後把它附屬到等待隊列頭q,直到資源可獲得,q引導的隊列別喚醒或者接到信號。Sleep_on函數應該與wake_up成對使用,
interruptible_sleep_on應該與wake_up_interruptble 成對使用。
set_current_state()(設置當前進程的運行狀態)和signal_pending();current代表當前進程。
(2)非阻塞使用select()輪詢
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
struct fd_set爲一個集合,存放的是文件描述符(filedescriptor),毫無疑問一個socket就是一個文件描述符。 
fd_set操作,比如:
FD_ZERO(fd_set *); 清空集合
FD_SET(int, fd_set *); 將一個給定的文件描述符加入集合之中
FD_CLR(int, fd_set*); 將一個給定的文件描述符從集合中刪除
檢查集合中指定的文件描述符是否可以讀寫FD_ISSET(int ,fd_set* )。

struct timeval 是一個大家常用的結構,用來代表時間值,有兩個成員,一個是秒數,另一個是毫秒。 具體解釋select的參數:
int maxfdp:是一個整數值,是指集合中所有文件描述符的範圍,即所有文件描述符的最大值加1,不能錯!
fd_set* readfds:是指向fd_set結構的指針,監視這些文件描述符的讀變化的,可以傳入NULL值,表示不關心任何文件的讀變化。 
fd_set* writefds:是指向fd_set結構的指針,監視這些文件描述符的寫變化的。可以傳入NULL值,表示不關心任何文件的寫變化。 
fd_set * errorfds:同上面兩個參數的意圖,用來監視文件錯誤異常。 
struct timeval* timeout:是select的超時時間,這個參數至關重要,它可以使select處於三種狀態:
若將NULL以形參傳入,就將select置於阻塞狀態,一定等到監視文件描述符集合中某個文件描述符發生變化爲止;
若將時間值設爲0秒0毫秒,就變成一個純粹的非阻塞函數,不管文件描述符是否有變化,都立刻返回繼續執行,文件無變化返回0,有變化返回一個正值;
timeout的值大於0,這就是等待的超時時間,即select在timeout時間內阻塞,超時時間之內有事件到來就返回了。 
select 返回值: 
負值:select錯誤 
正值:某些文件可讀寫或出錯 
0:等待超時,沒有可讀寫或錯誤的文件 

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