字元裝置驅動程式之同步互斥阻塞
原子操作
目的:同一時刻隻能有一個應用程式app打開/dev/buttons。
讀出、修改、寫回 并不是很快就結束的,有可能被打斷。因為linux是多任務,在A執行過程中,有可能換成B程式來執行。
原子操作函數:(原子:不可再分)
原子變量操作是Linux的一種簡單的同步機制,在操作過程中不會被打斷的操作。
原子變量操作不會隻執行一半,又去執行其他代碼。原子變量操作要麼執行完畢,要麼一點也不執行。
在驅動程式中修改:
1、定義一個原子變量canopen :
static atomic_t canopen = ATOMIC_INIT(1); //定義原子變量并初始化為1
2、在open函數:
static int sixth_drv_open(struct inode *inode, struct file *file)
{
...
//自減。如果等于0,傳回true,不會執行到這裡
//假設,已經被調用了一次後,canopen值為0,自減為-1,atomic_dec_and_test傳回false,就會執行這裡
if(!atomic_dec_and_test(&canopen) != 0)
{
atomic_inc(&canopen);//剛才自減,應該把之前加回去,加1,為了讓其他程序能夠打開裝置
return -EBUSY;//已經打開
}
...
return 0;//成功
}
3、在close函數:
int sixth_drv_close(struct inode *inode, struct file *file)
{
atomic_inc(&canopen); //釋放裝置,加1,為了讓其他程序能夠打開裝置
...
return 0;
}
在測試應用程式修改:
int main(int argc, char **argv)
if (fd < 0)
{
printf("can't open!\n");
return -1; //不能打開,傳回-1
}
讀出、修改、寫回,atomic_dec_and_test(&canopen)都是在這裡一次性完成,中間不會被打斷。
結果:
在背景第一次打開後,第二次打開不成功,直接傳回。同一時刻,隻能打開一個驅動程式。
信号量
目的:同一時刻,隻能有一個應用程式打開驅動程式。
信号量:
信号量(semaphore)是用于保護臨界區的一種常用方法,隻有得到信号量的程序才能執行臨界區代碼。
當擷取不到信号量,程序進入休眠等待狀态。
操作之前,先申請信号量,如果申請不了信号量,要麼傳回,要麼休眠。如果申請到了,就可以往下操作。操作完畢,就要釋放信号量。如果有其他程式在等待信号量,就喚醒那麼應用程式。
信号量的操作函數:
獲得信号量:
down_trylock:試圖擷取信号量,如果擷取不到信号量,就會立刻傳回非0值,不會導緻調用者休眠。
down_interruptible:進入睡眠狀态的程序能被信号打斷。
在驅動程式中修改:
1、定義信号量:
static DECLARE_MUTEX(button_lock); //定義互斥鎖 (這個宏,定義和初始化信号量)
2、獲得信号量:
static int sixth_drv_open(struct inode *inode, struct file *file)
{
down(&button_lock);//擷取不到信号量,就會休眠
}
3、釋放信号量:
int sixth_drv_close(struct inode *inode, struct file *file)
{
....
up(&button_lock);
return 0;
}
先把之前的程式給解除安裝:
執行結果:(在背景執行兩次應用程式)
第一次在背景運作程式,PID是867,狀态是S(休眠);第二次運作程式,PID是868,狀态是D(不可中斷的睡眠狀态)。
1、D狀态:“僵死”,不可中斷的睡眠狀态。
因為在open函數裡,第二次無法擷取信号量,就會在down(&button_lock)陷入休眠。
2、休眠什麼時候才會喚醒?
隻有第一個應用程式close函數的up(&button_lock)釋放信号量後,才會喚醒。
殺死第一個程式,而第二個程式就會被喚醒後,原來PID868屬于D狀态,現在PID868屬于S休眠狀态(正常))
阻塞
阻塞操作:
讀一個按鍵值,如果目前沒有按鍵按下,一直等待,知道有按鍵按下才會傳回。(阻塞 是指在執行裝置操作時,若不能獲得資源則挂起程序,直到滿足可操作的條件後再進行操作。被挂起的程序進入休眠狀态,被從排程器的運作隊列移走,直到等待的條件被滿足)
非阻塞操作:
讀一個按鍵值,如果目前沒有按鍵按下,立刻傳回,傳回一個錯誤。(非阻塞 是指程序在不能進行裝置操作時并不挂起,它或者放棄,或者不停地查詢,直至可以進行操作為止)
怎麼區分阻塞和非阻塞呢?
測試程式,open傳入的參數,如果傳入一個标記位NONBLOCK,就是非阻塞,否則,預設情況下是阻塞操作。
阻塞操作:
修改驅動程式:
1、
static int sixth_drv_open(struct inode *inode, struct file *file)
{
if (file->f_flags & O_NONBLOCK)//标記位擷取,如果flags是O_NONBLOCK
{
//非阻塞
if( down_trylock(&button_lock))//如果open打不開就傳回錯誤,
{
//down_trylock獲得信号量,傳回0,否則傳回非0值
return -EBUSY;
}
}
else
{
//阻塞
down(&button_lock);//無法擷取信号量,休眠
}
}
2、
ssize_t sixth_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
if( down_trylock(&button_lock))
{
//非阻塞
if (!ev_press) //如果沒有按鍵發生
return EAGAIN; //再次來執行
}
else//阻塞
{
wait_event_interruptible(button_waitq, ev_press);//ev_press=0,休眠,讓我們的測試程式休眠;ev_press!=0,直接往下運作
}
return 1;
}
修改測試應用程式:
while (1)
{
read(fd, &key_val, 1); //讀取按鍵值,這就是為什麼把fd作為全局變量的原因
printf("key_val:0x%x\n", key_val);
}
結果如下:(對于阻塞方式,如果沒有按鍵按下,一直休眠。如果有傳回值,是一個正确的值)
非阻塞操作:(一讀就會傳回)
驅動程式要對“O_NONBLOCK”這個标記進行處理。
修改測試應用程式:
int main(int argc, char **argv)
{
fd = open("/dev/buttons", O_RDWR | O_NONBLOCK);
while (1)
{
ret = read(fd, &key_val, 1); //讀取按鍵值,這就是為什麼把fd作為全局變量的原因
printf("key_val:0x%x, ret = %d\n", key_val, ret);
sleep(5); //機關是秒,5秒。非阻塞,一讀就會傳回,會列印一大堆東西
}
}
結果如下:
非阻塞,如果沒有按鍵按下,立刻傳回,傳回值為-1。如果有按鍵按下,會傳回1。
完整的驅動程式:sixth_drv.c
/*
一、驅動架構:
1.先定義file_operations結構體,其中有對裝置的打開,讀和寫的操作函數。
2.分别定義相關的操作函數
3.定義好對裝置的操作函數的結構體(file_operations)後,将其注冊到核心的file_operations結構數組中。
此設定的主裝置号為此結構在數組中的下标。
4.定義出口函數:解除安裝注冊到核心中的裝置相關資源
5.修飾 入口 和 出口函數
6.給系統提供更多的核心消息,在sys目錄下提供裝置的相關資訊。應用程式udev可以據此自動建立裝置節點,
建立一個class裝置類,在此類下建立裝置
*/
#include <linux/module.h> //内涵頭檔案,含有一些核心常用函數的原形定義。
#include <linux/kernel.h> //最基本的檔案,支援動态添加和解除安裝子產品。Hello World驅動要這一個檔案就可以。
#include <linux/fs.h> //包含了檔案操作相關的struct的定義,例如struct file_operations
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h> //包含了copy_to_user、copy_from_user等核心通路使用者程序記憶體位址的函數定義
#include <asm/irq.h>
#include <asm/io.h> //包含了ioremap、ioread等核心通路IO記憶體等函數的定義
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/poll.h>
static struct class *sixthdrv_class; //一個類
static struct class_device *sixthdrv_class_dev; //一個類裡面再建立一個裝置
volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat;
volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat;
/* 下面兩個是定義休眠函數的參數 */
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
/* 中斷時間标志,中斷服務程式将它置1,sixth_drv_read将它清0 */
static volatile int ev_press=0;
static struct fasync_struct *button_async;
/* 引腳描述的結構體 */
struct pin_desc{
unsigned int pin;
unsigned int key_val;
};
/* 鍵值:按下時,0x01,0x02,0x03,0x04 */
/* 鍵值:松開時,0x81,0x82,0x83,0x84 */
static unsigned char keyval; //鍵值
/* 在request_irq函數中把結構體傳進去 */
struct pin_desc pins_desc[4] = { //鍵值先賦初始值0x01,0x02,0x03,0x04
{S3C2410_GPF0, 0x01}, //pin=S3C2410_GPF0, key_val(按鍵值)=0x01
{S3C2410_GPF2, 0x02}, //pin=S3C2410_GPF2, key_val(按鍵值)=0x02
{S3C2410_GPG3, 0x03}, //pin=S3C2410_GPF3, key_val(按鍵值)=0x03
{S3C2410_GPG11, 0x04}, //pin=S3C2410_GPF11, key_val(按鍵值)=0x04
};
//static atomic_t canopen = ATOMIC_INIT(1); //定義原子變量并初始化為1
static DECLARE_MUTEX(button_lock); //定義互斥鎖
/*
* 确定按鍵值
*/
static irqreturn_t button_irq(int irq, void *dev_id) //中斷處理函數
{
/* irq = IRQ_EINT0 …… */
/* dev_id = 結構體struct pins_desc */
struct pin_desc * pindesc = (struct pin_desc *)dev_id;
unsigned int pinval;
/* 讀取引腳PIN值 */
pinval = s3c2410_gpio_getpin(pindesc->pin);
/* 确定按鍵值,按下管腳低電平,松開管腳高電平 */
if(pinval)
{
/* 松開 */
keyval = 0x80 | pindesc->key_val; //規定的:0x8X
}
else
{
/* 按下 */
keyval = pindesc->key_val; //0x0X
}
/* 喚醒 */
ev_press = 1; /* 表示中斷發生了 */
wake_up_interruptible(&button_waitq); /* 喚醒休眠的程序,去button_wq隊列,把挂在隊列下的程序喚醒 */
//發送信号SIGIO信号給fasync_struct結構體所描述的PID,觸發應用程式的SIGIO信号處理函數
kill_fasync(&button_async, SIGIO, POLL_IN); //有按鍵按下,就發出信号給應用程式(發給誰,是在button_async結構中定義的)
return IRQ_RETVAL(IRQ_HANDLED);
}
static int sixth_drv_open(struct inode *inode, struct file *file)
{
#if 0
//自減。如果等于0,還沒有打開它
if(!atomic_dec_and_test(&canopen) != 0)
{
atomic_inc(&canopen);
return -EBUSY;
}
#endif
if (file->f_flags & O_NONBLOCK)//标記位擷取,如果flags是O_NONBLOCK
{
//非阻塞
if( down_trylock(&button_lock))//如果open打不開就傳回錯誤,
{
//down_trylock獲得信号量,傳回0,否則傳回非0值
return -EBUSY;
}
}
else
{
//阻塞
/* 擷取信号量 */
down(&button_lock);//無法擷取信号量,休眠
}
/* 配置GPF0,2為輸入引腳 */
/* 配置GPF3,11為輸入引腳 */
/* request_irq函數的第五個參數是void *,為無類型指針,可以指向任何資料類型 */
request_irq(IRQ_EINT0, button_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);
request_irq(IRQ_EINT2, button_irq, IRQT_BOTHEDGE, "S3", &pins_desc[1]);
request_irq(IRQ_EINT11, button_irq, IRQT_BOTHEDGE, "S4", &pins_desc[2]);
request_irq(IRQ_EINT19, button_irq, IRQT_BOTHEDGE, "S5", &pins_desc[3]);
return 0;
}
ssize_t sixth_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
if (size != 1)
return -EINVAL;
if( file->f_flags & O_NONBLOCK)//如果open打不開就傳回錯誤,
{
//非阻塞
if (!ev_press)
return -EAGAIN;
}
else//阻塞
{
/* 如果沒有按鍵動作,休眠,休眠:讓出CPU */
/* 休眠時,把程序挂在button_wq 隊列裡 */
/* 如果休眠後被喚醒,就會從這裡繼續往下執行 */
/* 一開始沒有按鍵按下,ev_press = 0 */
wait_event_interruptible(button_waitq, ev_press);//ev_press=0,休眠,讓我們的測試程式休眠;ev_press!=0,直接往下運作
}
/* 如果有按鍵動作,傳回鍵值 */
copy_to_user(buf, &keyval, 1); //把鍵值 拷回去
ev_press = 0; //清零,如果不清零,下次再讀,立馬往下執行,傳回原來的值
return 1;
}
int sixth_drv_close(struct inode *inode, struct file *file)
{
//atomic_inc(&canopen);
free_irq(IRQ_EINT0, &pins_desc[0]);
free_irq(IRQ_EINT2, &pins_desc[1]);
free_irq(IRQ_EINT11, &pins_desc[2]);
free_irq(IRQ_EINT19, &pins_desc[3]);
up(&button_lock);
return 0;
}
static unsigned sixth_drv_poll(struct file *file, poll_table *wait)
{
unsigned int mask = 0;
//poll_wait會調用sys_poll的__pollwait函數
poll_wait(file, &button_waitq, wait); //不會立即休眠,這隻是讓程序挂到隊列裡面去。
//休眠是在"do_poll"中的"schedule_timeout()"
//ev_press=0,休眠,ev_press=1,喚醒
if (ev_press) //如果目前有資料可以傳回應用程式,否則mask=0
mask |= POLLIN | POLLRDNORM;
return mask;//如果傳回0,do_poll的count++就不會執行,往下就會休眠schedule_timeout()
}
static int sixth_drv_fasync(int fd, struct file * filp, int on) //設定信号發給誰
{
//測試,是否應用程式調用fcntl(fd, F_SETFL, Oflags | FASYNC)會執行到這裡去
printk("driver: sixth_drv_fasync\n");
//fasync_helper初始化或者釋放button_async
return fasync_helper(fd, filp, on, &button_async); //初始化button_async結構體後,中斷服務程式才能使用kill_fasync發信号
}
static struct file_operations sixth_drv_fops = {
.owner = THIS_MODULE, /* 這是一個宏,推向編譯子產品時自動建立的__this_module變量 */
.open = sixth_drv_open,
.read = sixth_drv_read,
.release = sixth_drv_close,
.poll = sixth_drv_poll,
.fasync = sixth_drv_fasync,
};
int major;
static int sixth_drv_init(void)
{
major = register_chrdev(0, "sixth_drv", &sixth_drv_fops);
//建立一個類
sixthdrv_class = class_create(THIS_MODULE, "firstdrv");
//在這個類下面再建立一個裝置
//mdev是udev的一個簡化版本
//mdev應用程式,就會被核心調用,會根據類和類下面的裝置這些資訊
sixthdrv_class_dev = class_device_create(sixthdrv_class, NULL, MKDEV(major, 0), NULL, "buttons");/* /dev/buttons */
//建立位址映射:實體位址->虛拟位址
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); //指向的是虛拟位址,第一個參數是實體開始位址,第二個是長度(位元組)
gpfdat = gpfcon + 1; //加1,實際加4個位元組
gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16); //指向的是虛拟位址,第一個參數是實體開始位址,第二個是長度(位元組)
gpgdat = gpgcon + 1; //加1,實際加4個位元組
return 0;
}
static void sixth_drv_exit(void)
{
unregister_chrdev(major, "sixth_drv");
class_device_unregister(sixthdrv_class_dev);
class_destroy(sixthdrv_class);
iounmap(gpfcon);
iounmap(gpgcon);
return 0;
}
module_init(sixth_drv_init);
module_exit(sixth_drv_exit);
MODULE_LICENSE("GPL");
完整的測試應用程式:sixthdrvtest.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <signal.h> //信号處理需要這個頭檔案
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
/* sixthdrvtest
*/
int fd;
//信号處理函數會在驅動中斷服務程式裡,如果有按鍵按下,通過kill_fasync發送信号
void my_signal_fun(int signum)
{
unsigned char key_val;
read(fd, &key_val, 1); //讀取按鍵值,這就是為什麼把fd作為全局變量的原因
printf("key_val:0x%x\n", key_val);
}
int main(int argc, char **argv)
{
unsigned char key_val;
int ret;
int Oflags;
//做什麼事情,需要一個信号處理函數
//在應用程式中捕捉SIGIO信号(由驅動程式發送),接受到SIGIO信号時,執行my_signal_fun函數
//signal(SIGIO, my_signal_fun); //SIGIO:表示IO口有資料讓你讀和寫
fd = open("/dev/buttons", O_RDWR | O_NONBLOCK);
if (fd < 0)
{
printf("can't open!\n");
return -1;
}
//把程序ID号告訴給驅動程式,否則驅動不知道發給誰
//fcntl(fd, F_SETOWN, getpid()); // 告訴核心,發給誰
//擷取fd的打開方式
//Oflags = fcntl(fd, F_GETFL);//通過F_GETFL讀出flags,在flags上置上"FASYNC"位
//支援F_SETFL指令的處理,每當FASYNC标志改變時, ※※驅動程式中的fasync()函數将得以執行
//将fd的打開方式設定為FASYNC---即 支援異步通知
//該行代碼執行會觸發 驅動程式中 file_operation->fasync函數 ---函數
//調用fasync_helper初始化一個fasync_struct結構體,該結構體描述了将要發送信号的程序PID(fasync_struct->fa_file->f_fowner->p_id)
//fcntl(fd, F_SETFL, Oflags | FASYNC); // 改變fasync标記,最終會調用到驅動的fasync > fasync_helper:初始化/釋放fasync_struct
/* 主函數隻用sleep,所有工作在"信号處理函數"中實作 */
while (1)
{
ret = read(fd, &key_val, 1); //讀取按鍵值,這就是為什麼把fd作為全局變量的原因
printf("key_val:0x%x, ret = %d\n", key_val, ret);
sleep(5);
}
return 0;
}