/**
* Author:hasen
* 參考 :《linux裝置驅動開發詳解》
* 簡介:android小菜鳥的linux
* 裝置驅動開發學習之旅
* 主題:支援阻塞的裝置驅動
* Date:2014-11-05
*/
/*重點已在注釋前加上了@@,使用搜尋"@@"可以檢視重點代碼部分*/
/**
* 現在實作這樣一個裝置,globalfifo,隻有當FIFO中有資料的時候(即有程序把資料寫入了FIFO
* 而且沒有被讀程序讀空),讀程序才能把資料讀出,而且讀出的資料慧聰globalfifo的全局記憶體中
* 拿掉;隻有當FIFO非滿時(即還有一些空間未被寫,或寫滿後被讀程序從這個FIFO中讀出了資料),
* 寫程序才能往這個FIFO中寫入資料。
*/
/*globalfifo中,讀FIFO将會喚醒寫FIFO,寫FIFO将會喚醒讀FIFO,
是以需要兩個等待隊列頭, 分别對應讀和寫*/
struct globalfifo_dev{
struct cdev cdev ;/*cdev結構體*/
unsigned int current_len ;/*目前fifo的有效長度*/
unsigned char mem[GLOBALFIFO_SIZE] ;/*全局記憶體*/
struct semaphore sem ;/*并發控制用的信号量*/
wait_queue_head_t r_wait ;/*@@阻塞讀用的等待隊列頭*/
wait_queue_head_t w_wait ;/*@@阻塞寫用的等待隊列頭*/
}
globalfifo_dev *globalfifo_devp ;
int gobalfifo_init(void)
{
int ret ;
dev_t devno = MKDEV(globalfifo_major,0) ;
/*申請裝置号*/
if(globalfifo_major)
ret = register_chrdev_region(devno,1,"globalfifo") ;
else{
ret = alloc_chrdev_region(&devno,0,1,"globalfifo") ;
globalfifo_major = MAJOR(devno) ;
}
if(dev < 0)
return ret ;
/*動态申請裝置結構體記憶體*/
globalfifo_devp = kmalloc(sizeof(struct globalfifo_dev),GFP_KERNEL) ;
if(globalfifo_devp){ /*申請失敗*/
ret = -ENOMEM ;
goto fail_malloc ;
}
memset(globalfifo_devp,0,sizeof(struct globalfifo_dev)) ;
globalfifo_setup_dev(globalfifo_devp ,0) ;
init_MUTEX(&globalfifo_devp->sem) ;/*初始化信号量*/
init_waitqueue_head(&globalfifo_devp->r_wait) ;/*@@初始化讀等待隊列頭*/
init_waitqueue_head(&globalfifo_devp->w_wait) ;/*@@初始化寫等待隊列頭*/
return 0 ;
fail_malloc :unregister_chrdev_region(devno,1) ;
return ret ;
}
/*globalfifo讀函數*/
static ssize_t globalfifo_read(struct file *filp,char __user *buf,
size_t count ,loff_t *ppos)
{
int ret ;
struct globalfifo_dev *dev = filp->private_data ;/*獲得裝置結構體指針*/
DECLARE_WAITQUEUE(wait,current) ; /*@@定義等待隊列*/
down(&dev->sem) ; /*獲得信号量*/
add_wait_queue(&dev->r_wait,&wait) ;/*@@進入讀等待隊列頭*/
/*@@等待FIFO非空*/
while(dev->current_len == 0){
if(filp->f_flags & O_NONBLOCK){
ret = -EAGAIN ;
goto out ;
}
__set_current_state(TASK_INTERRUPTIBLE) ;/*@@改變程序狀态為睡眠*/
up(&dev->sem) ;
schedule() ;/*@@排程其他程序執行*/
if(signal_pending(current)){ /*如果是信号喚醒的*/
ret = -ERESTARTSYS ;
goto out2 ;
}
down(&dev->sem) ;
}
/*拷貝到使用者空間*/
if(count >dev->current_len)
count = dev->current_len ;
if(copy_to_user(buf,dev->mem,count)){
ret = -EFAULT ;
goto out ;
}else{
memcpy(dev->mem,dev->mem + count,dev->current_len - count);/*fifo資料前移*/
dev->current_len -= count ; /*有效資料長度減少*/
printk(KERN_INFO "read %d bytes,current_len:%d\n",count,dev->current_len) ;
wake_up_interruptible(&dev->w_wait) ;/*@@喚醒寫等待程式*/
ret = count ;
}
out :up(&dev->sem) ; /*釋放信号量*/
out2: remove_wait_queue(&dev->r_wait,&wait) ; /*@@移除等待隊列*/
set_current_state(TASK_RUNNING) ; /*@@切換目前程序狀态*/
return ret ;
}
/*globalfifo寫函數*/
static ssize_t globalfifo_write(static file *filp,const char __user *buf,
size_t count,loff_t *ppos)
{
struct globalfifo_dev *dev = filp->private_data ; /*獲得裝置結構體指針*/
int ret ;
DECLARE_WAITQUEUE(wait,current) ; /*@@定義等待隊列*/
down(&dev->sem) ;/*擷取信号量*/
add_wait_queue(&dev->w_wait,&wait) ;/*@@進入寫等待隊列頭*/
/*@@等待FIFO非滿*/
while(dev->current_len == GLOBALFIFO_SIZE){
if(filp->f_flags & O_NONBLOCK){
/*如果是非阻塞通路*/
ret = -EAGAIN ;
goto out ;
}
__set_current_state(TASK_INTERRUPTIBLE) ;/*@@改變程序狀态為睡眠*/
up(&dev->sem) ;
schedule() ;/*@@排程其他程序執行*/
if(signal_pending(current)){
/*如果是信号喚醒*/
ret = -ERESTARTSYS ;
goto out2;
}
down(&dev->sem) ; /*獲得信号量*/
}
/*從使用者空間拷貝到核心空間*/
if(count > GLOBALFIFO_SIZE - dev->current_len)
count = GLOBALFIFO_SIZE - dev->current_len ;
if(copy_from_user(dev->mem + dev->current_len,buf,count)){
ret = -EFAULT ;
goto out ;
}else{
dev->current_len += count ;
printk(KERN_INFO "write %d bytes ,current_len:%d\n",count,dev->current_len) ;
wake_up_interruptible(&dev->r_wait) ; /*@@喚醒讀等待隊列*/
ret = count ;
}
out : up(&dev->sem) ; /*釋放信号量*/
out2: remove_wait_queue(&dev->w_wait,&wait) ;/*@@移除等待隊列*/
set_current_state(TASK_RUNNING) ;/*改變程序狀态*/
return ret ;
}
在使用者空間驗證globalfifo的讀寫
1、在globalfifo所在檔案夾下運作make指令編譯得到globalfifo.ko。接着insmod子產品:
[email protected]$ sudo su
[email protected]# insmod globalfifo.ko
2、建立裝置檔案節點"/dev/globalfifo"
[email protected]# mknod /dev/globalfifo c 249 0
3、啟動兩個程序,一個程序"cat /dev/globalfifo &"在背景執行,一個程序"echo 字元串 /dev/globalfifo"在前台執行
[email protected]# cat /dev/globalfifo &
[email protected]# echo 'hello world!' /dev/globalfifo
會看到:每當echo程序向/dev/globalfifo寫入一串資料,cat程序就立即将該串資料顯示出來。