天天看點

S3C2440 字元裝置驅動程式之同步互斥阻塞(十)

字元裝置驅動程式之同步互斥阻塞

原子操作

目的:同一時刻隻能有一個應用程式app打開/dev/buttons。

讀出、修改、寫回 并不是很快就結束的,有可能被打斷。因為linux是多任務,在A執行過程中,有可能換成B程式來執行。

S3C2440 字元裝置驅動程式之同步互斥阻塞(十)

原子操作函數:(原子:不可再分)

原子變量操作是Linux的一種簡單的同步機制,在操作過程中不會被打斷的操作。

原子變量操作不會隻執行一半,又去執行其他代碼。原子變量操作要麼執行完畢,要麼一點也不執行。

S3C2440 字元裝置驅動程式之同步互斥阻塞(十)

在驅動程式中修改:

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)都是在這裡一次性完成,中間不會被打斷。

結果:

S3C2440 字元裝置驅動程式之同步互斥阻塞(十)

在背景第一次打開後,第二次打開不成功,直接傳回。同一時刻,隻能打開一個驅動程式。

信号量

目的:同一時刻,隻能有一個應用程式打開驅動程式。

信号量:

信号量(semaphore)是用于保護臨界區的一種常用方法,隻有得到信号量的程序才能執行臨界區代碼。

當擷取不到信号量,程序進入休眠等待狀态。

操作之前,先申請信号量,如果申請不了信号量,要麼傳回,要麼休眠。如果申請到了,就可以往下操作。操作完畢,就要釋放信号量。如果有其他程式在等待信号量,就喚醒那麼應用程式。

信号量的操作函數:

S3C2440 字元裝置驅動程式之同步互斥阻塞(十)

獲得信号量:

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;

}

先把之前的程式給解除安裝:

S3C2440 字元裝置驅動程式之同步互斥阻塞(十)

執行結果:(在背景執行兩次應用程式)

S3C2440 字元裝置驅動程式之同步互斥阻塞(十)

第一次在背景運作程式,PID是867,狀态是S(休眠);第二次運作程式,PID是868,狀态是D(不可中斷的睡眠狀态)。

1、D狀态:“僵死”,不可中斷的睡眠狀态。

因為在open函數裡,第二次無法擷取信号量,就會在down(&button_lock)陷入休眠。

2、休眠什麼時候才會喚醒?

隻有第一個應用程式close函數的up(&button_lock)釋放信号量後,才會喚醒。

S3C2440 字元裝置驅動程式之同步互斥阻塞(十)

殺死第一個程式,而第二個程式就會被喚醒後,原來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);

  }

結果如下:(對于阻塞方式,如果沒有按鍵按下,一直休眠。如果有傳回值,是一個正确的值)

S3C2440 字元裝置驅動程式之同步互斥阻塞(十)

非阻塞操作:(一讀就會傳回)

驅動程式要對“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秒。非阻塞,一讀就會傳回,會列印一大堆東西

  }

}

結果如下:

S3C2440 字元裝置驅動程式之同步互斥阻塞(十)

非阻塞,如果沒有按鍵按下,立刻傳回,傳回值為-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;
}

           

繼續閱讀