10.6、驅動開發 -- IO模型、阻塞與非阻塞、同步與異步

  • IO模型: 一種通信的模型
    IO, input output 其實就是 ###通信####爲了解決#通信#過程中,出現的問題.

  • IO模型: 網絡編程

  • 阻塞
    一個運行的進程 去訪問資源,發現資源不滿足,於是進程 ## 主動睡眠## 等待資源滿足,被喚醒.當資源(一般伴隨中斷)到來的時候, (中斷)去喚醒等待的進程.##睡眠###,不消耗

  1. 定義並初始化一個##等待隊列###
  2. 運行的進程發現資源不滿足, 主動將自己掛載到等待隊列上去. 然後讓內核重新調度.
  3. 某個執行單元將數據送達, 然後喚醒等待隊列上的進程.
    a = {b=3;c=4; b+c};
  • 非阻塞
    進程去訪問資源,發現資源不滿足, 進程立馬去做其他任務, 通過輪訓的方式,不斷地檢查資源.內核如何支持 非阻塞呢???
struct file {
unsigned 
int f_flags; //有32個bit, 每一個bit表示一種功能. 可讀 可寫 可執行 權限 ....其中,有一個bit 表示改文件 當前是 阻塞(0)還是非阻塞(1).一個文件默認是阻塞.
};
  • 異步通知
    linux系統中,異步通知使用 #信號#來實現.當內核 有數據之後, 主動向用戶程序發送一個信號, app得到信號之後,再去內核訪問數據.
    在這裏插入圖片描述

故事1:老王燒開水。
出場人物:老王,水壺兩把(普通水壺,簡稱水壺;會響的水壺,簡稱響水壺)。老王想了想,有好幾種等待方式
1.老王用水壺煮水,並且站在那裏,不管水開沒開,每隔一定時間看看水開了沒。 -同步阻塞
2.老王還是用水壺煮水,不再傻傻的站在那裏看水開,跑去寢室上網,但是還是會每隔一段時間過來看看水開了沒有,水沒有開就走人。 -同步非阻塞
3.老王這次使用高大上的響水壺來煮水,站在那裏,但是不會再每隔一段時間去看水開,而是等水開了,水壺會自動的通知他。 -異步阻塞
4.老王還是使用響水壺煮水,跑到客廳上網去,等着響水壺自己把水煮熟了以後通知他。 -異步非阻塞

故事2::在網上買了個東西,然後就等東西送到快遞去取了。就有這4種情況:
你立刻傻站在快遞門口等東西來
你繼續做你要做的事,不會因爲會有包裹影響正常生活該做的
東西郵到給你發短信
東西郵到沒人吭聲通知你

阻塞(因爲會有包裹,你就專門等包裹來,包裹不到你就在快遞門口罷工!絕食!)
非阻塞(不會因爲有包裹你就影響你正常生活和該做的工作)
異步(人家很敬業的通知你)
同步(暴走大世界裏黑ems郵件堆成山卻不通知人取就是這種情況啦)

然後排列組合,又有了這四種情況:
1.同步阻塞:包裹來了不通知你,你就傻站在快遞門口等包裹來了
2.同步非阻塞(輪詢):你該幹啥幹啥,雖然人家不通知你,但你時不時的跑去快遞問問我包裹到了沒.至於你是每天中午問呢還是每隔一小時就跑去問一次,那就看你有多能墨跡了
3.異步阻塞:雖然人家會短信通知你,但你偏要在快遞門口傻站着等包裹到.有時間,就是這麼任性!但我估計程序猿應該沒誰會寫出這麼任性的代碼吧?
4.異步非阻塞(回調):其實第一次聽說要在快遞外面等郵件我是拒絕的,因爲,你不能讓我擱下所有工作,我就馬上去快遞外面等快遞……等了一陣時間,手機,DUANGDUANGDUANG~~我的快遞到快遞裏了

  • 非阻塞IO中可以使用自旋鎖、原子變量
  • 阻塞IO中可以使用互斥體

簡單實例

  • chdev.c
/*****************************************************************
*   Copyright (C) 2019 Sangfor Ltd. All rights reserved.
*   
*   文件名稱:chdev.c
*   創 建 者:yinfei-hu
*   創建日期:2019年03月07日  星期4 20時22分05秒
*   功能描述:操作LED  GPIO
*
*****************************************************************/

#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>

#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/sched.h>

#include "comm.h"

/*
	#############	io 模型  #####################

阻塞
	1. 定義並初始化 一個等待隊列
		wait_queue_head_t   waitqhead;
		void init_waitqueue_head(wait_queue_head_t *q)
	2. 運行的進程 發現資源不滿足,  主動將自己 掛載到 等待隊列上去.  
				然後讓內核重新調度.

		進程主動調用該 宏,   該進程 在 宏內部 阻塞/睡眠
		不在返回.		 當被喚醒的時候,該函數/進程 返回.
		wait_event_interruptible(wait_queue_head_t wq,
											int condition)
	3.   某個執行單元  將數據送達,  然後喚醒等待隊列上的 進程.
		void wake_up_interruptible(wait_queue_head_t  *wq)
非阻塞
   實現邏輯
	如果用戶要求 文件時非阻塞的,	並且當前沒有數據,則 立馬返回-EAGAIN.
異步通知
	1.定義 struct fasync_struct *fapp;	
	2.實現  fops.fasync  方法
	3.發信號   
		kill_fasync(&pt_gstruct->fapp,SIGIO,POLL_IN)
*/

#define	GPX2CON	0x11000c40
#define	GPX2DAT	0x11000c44

struct global_struct {
	struct class *cls;
	int major,minor;
	/*	字符設備驅動對象	*/
	struct cdev chdevobj;			//inode->i_cdev
	void *config;
	void *data;
	wait_queue_head_t	waitqhead;		//定義一個等待隊列
	int data_flags;						//數據有和無的標誌, 0-無 1-有
	char buf[128];
	struct fasync_struct *fapp;		//異步通知的實現
};

/*		全局變量		*/
struct global_struct gstruct;

int led_init(void)
{
	int val;
	gstruct.config = ioremap(GPX2CON,4);
	gstruct.data   = ioremap(GPX2DAT,4);

	val = readl(gstruct.config);
	val = val & (~0xF<<28);
	val = val | 1<<28;
	writel(val ,gstruct.config);

	//關燈
	val  = readl(gstruct.data);
	val = val & ~(1<<7);
	writel(val,gstruct.data);

	return 0;
}

int led_ctrl(int on )
{
	int val;

	if(on){
		val  = readl(gstruct.data);
		val = val| (1<<7);
		writel(val,gstruct.data);
	}else {
		//關燈
			val  = readl(gstruct.data);
			val = val & ~(1<<7);
			writel(val,gstruct.data);
	}

	return 0;
}

int led_deinit(void)
{
	int val;
	
	//關燈
	val  = readl(gstruct.data);
	val = val & ~(1<<7);
	writel(val,gstruct.data);

	return 0;
}

/*
	open, 一般執行對硬件進行初始化的動作

*/
int chdev_open(struct inode *inode, struct file *file)
{
	struct global_struct *pt_gstruct ;

	printk("%s->%d\n",__FUNCTION__,__LINE__);


	/*inode->i_cdev 記錄了 該文件節點所對應的 字符設備對象的地址

		一個大結構體變量gstruct,   如果已知其中一個 成員的地址&chdevobj,
		如何求取大結構體變量 gstruct 的地址呢.....    成員地址減去 成員前面的大小
		
		bigstruct *pt_gstruct =
				container_of(little_member_addr,大結構體類型,小成員的變量名)
	*/

	pt_gstruct = container_of(inode->i_cdev ,struct global_struct,chdevobj);
	//  pt_gstruct ====== &gstruct

	file->private_data = pt_gstruct;

	led_init();
	/*成功,   返回0 
		失敗, 返回一個負數, 記錄 數值不能亂寫,要根據 內核的錯誤宏定義
	*/
	return 0;
}

/*
close, 一般執行對硬件進行 去初始化的動作

*/
int chdev_close (struct inode *inode, struct file *file)
{

	struct  global_struct *pt_gstruct  = file->private_data;

	printk("%s->%d  major%d minor%d\n",__FUNCTION__,__LINE__,pt_gstruct->major,pt_gstruct->minor);

	led_deinit();

	/*成功,   返回0 
		失敗, 返回一個負數, 記錄 數值不能亂寫,要根據 內核的錯誤宏定義
	*/
	return 0;
}


ssize_t chdev_write(struct file *file, const char __user *usr, size_t size, loff_t *loff)
{
	int ret;

	struct  global_struct *pt_gstruct  = file->private_data;

	printk("%s->%d  major%d minor%d\n",__FUNCTION__,__LINE__,
		pt_gstruct->major,pt_gstruct->minor);


	/*  *usr來自用戶空間, 可能是null,或者 非法地址
		如果不加判斷,內核直接使用,  會造成內核 異常

		我們一般使用 安全版本的 memcpy   ,copy_from_user
		返回 實際尚未拷貝的字節數,   你要求拷貝100,實際拷貝89,返回11
	*/
	ret = copy_from_user(pt_gstruct->buf,usr,size);
	if(ret ){
		printk("%s->%d copy_from_user err\n",__func__,__LINE__);
		return -16;
	}

	printk("%s->%d get buf:%s\n",__func__,__LINE__,pt_gstruct->buf);

	pt_gstruct->data_flags = 1;
	wake_up_interruptible(&pt_gstruct->waitqhead);

	//異步通知,發送信號
	kill_fasync(&pt_gstruct->fapp,SIGIO,POLL_IN);
	/*成功,  返回實際拷貝的大小
		失敗, 返回一個負數, 記錄 數值不能亂寫,要根據 內核的錯誤宏定義
	*/
	return  size ;
}

ssize_t ch_read(struct file *file, char __user *usr, size_t size, loff_t *loff)
{
	int ret;
	struct  global_struct *pt_gstruct  = file->private_data;

	printk("%s->%d  major%d minor%d\n",__FUNCTION__,__LINE__,pt_gstruct->major,pt_gstruct->minor);

	/*
		非阻塞,    實現邏輯
			如果用戶要求 文件時非阻塞的,	並且當前沒有數據,則 立馬返回.
	*/
	if(file->f_flags & O_NONBLOCK){
		if(pt_gstruct->data_flags == 0){
			return -EAGAIN;
		}
	}
	/*注意,  如果 沒有數據,則當前進程 阻塞在該函數內部
			不在返回  .		當數據到來,被喚醒,該函數 返回

			該函數,再帶 判斷, 當有數據的時候,該函數 不會睡眠, 沒有數據則會睡眠
	*/
	wait_event_interruptible(pt_gstruct->waitqhead,pt_gstruct->data_flags);
	
	/* usr 來自用戶空間, 有可能 是個非法地址
		需要使用 安全版本 memcpy
	*/
	ret = copy_to_user(usr,pt_gstruct->buf,size);
	if(ret ){
		printk("%s->%d copy_to_user err\n",__func__,__LINE__);
		return -16;
	}

	memset(pt_gstruct->buf,0,sizeof(pt_gstruct->buf));
	pt_gstruct->data_flags = 0;		//取走之後,清空數據標誌位

	/*成功,  返回實際拷貝的大小
		失敗, 返回一個負數, 記錄 數值不能亂寫,要根據 內核的錯誤宏定義
	*/

	return size;
}

long chdev_ioctl(struct file *file, unsigned int cmd, unsigned long args)
{
	int ret;
	struct tv_switch swtich;
	struct tv_chnl   chnl;
	struct tv_vol	vol;
	struct tv_stat  stat;
	struct led_ctrl ctl;

	struct  global_struct *pt_gstruct  = file->private_data;

	printk("%s->%d  major%d minor%d\n",__FUNCTION__,__LINE__,pt_gstruct->major,pt_gstruct->minor);

	
	switch(cmd){
	case  TV_CMD_SWITCH:
		ret = copy_from_user(&swtich,(void *)args,sizeof(swtich) );
		printk("%s->%d  TV_CMD_SWITCH get on%d\n",__func__,__LINE__,swtich.on);
		break;
	case TV_CMD_CHNL:
		ret = copy_from_user(&chnl,(void *)args,sizeof(chnl));
		printk("%s->%d  TV_CMD_CHNL get up%d num%d\n",__func__,__LINE__,chnl.up,chnl.num);
		break;

	case TV_CMD_VOL:
		ret = copy_from_user(&vol,(void *)args,sizeof(vol));
		printk("%s->%d  TV_CMD_VOL get up%d step%d\n",__func__,__LINE__,vol.up,vol.step);
		break;

	case TV_CMD_STAT:
		stat.chnl = 129;
		stat.vol = 56;
		stat.light = 57;
		ret = copy_to_user((void *)args,&stat,sizeof(stat));

		break;

	case LED_CMD_SW:
		ret = copy_from_user(&ctl,(void *)args,sizeof(ctl));
		led_ctrl(ctl.on);
		break;
		
	}
	return 0;
}


int chdev_fasync(int  fd, struct file *file, int on)
{
	struct  global_struct *pt_gstruct  = file->private_data;

	return fasync_helper(fd,file ,on,&pt_gstruct->fapp);
}

struct file_operations chdev_ops = {
	.open = chdev_open,
	.release = chdev_close,

	.write = chdev_write,
	.read = ch_read,
	
	.unlocked_ioctl = chdev_ioctl,

	.fasync = chdev_fasync,
};

int module_fun_init(void)
{
	int ret;
	dev_t devno;

	/*	等待 隊列初始化*/
	init_waitqueue_head(&gstruct.waitqhead);
	memset(gstruct.buf,0,sizeof(gstruct.buf));
	gstruct.data_flags = 0;

	/*  1.向內核申請設備號*/
	ret = alloc_chrdev_region(&devno,0,1,"chdev test");
	if(ret <0){
		printk("alloc_chrdev_region err %s->%d\n",__FUNCTION__,__LINE__);
		return -12;
	}

	gstruct.major =  MAJOR(devno);  			//major = devno >> 20;
	gstruct.minor =  MINOR(devno);				//devno <<12>>12;
	devno =  MKDEV(gstruct.major,gstruct.minor); 			//major<<20 | minor;

	printk("%s->%d get major%d  minor%d\n",__FUNCTION__,__LINE__,gstruct.major,gstruct.minor);

	/*創建並初始化 字符設備驅動 對象*/
	cdev_init(&gstruct.chdevobj,&chdev_ops);
	ret = cdev_add(&gstruct.chdevobj,devno,1);
	if(ret <0){
		printk("cdev_add err %s->%d\n",__FUNCTION__,__LINE__);
		return -14;
	}


	/*第三步, 1.創建一個類*/
	gstruct.cls = class_create(THIS_MODULE,"chdev class");
	if(!gstruct.cls){
		printk("class_create err %s->%d\n",__FUNCTION__,__LINE__);
		return -16;
	}

	/*2.創建一個節點/文件  ,在類下

		printf(const char * fmt,...);	printf("%s %d %c %lf ",........);

		在 /dev/目錄下,會創建  /dev/chdev0 文件節點
	*/
	device_create(gstruct.cls,NULL,devno,NULL,"chdev%d",0);
	
	return 0;
}

void  module_fun_exit(void)
{
	//釋放 init裏面的動作,注意  和init是完全的逆序

	//釋放文件節點
	device_destroy(gstruct.cls,MKDEV(gstruct.major,gstruct.minor));
	//釋放類
	class_destroy( gstruct.cls);
	//刪除 對象
	cdev_del(&gstruct.chdevobj);
	//釋放設備號 
	unregister_chrdev_region(MKDEV(gstruct.major,gstruct.minor),1);

	printk("%s->%d\n",__FUNCTION__,__LINE__);
}

/*    內核模塊入口    module_init 裏面的參數,指向誰,誰就是模塊入口函數*/
module_init(module_fun_init);
/*    內核模塊出口    module_exit 裏面的參數,指向誰,誰就是模塊出口函數*/
module_exit(module_fun_exit);

/*模塊作者信息*/
MODULE_AUTHOR("YinFei.Hu <[email protected]>");
/*模塊信息*/
MODULE_DESCRIPTION("Kernel module driver");
/*告訴內核願意遵守gpl協議*/
MODULE_LICENSE("GPL");
  • appwrite.c
/*****************************************************************
*   Copyright (C) 2019 Sangfor Ltd. All rights reserved.
*   
*   文件名稱:appwrite.c
*   創 建 者:yinfei-hu
*   創建日期:2019年03月07日  星期4 20時22分05秒
*   功能描述:通過之後設備的write控制led
*
*****************************************************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include "comm.h"
int main(void)
{
	int ret;
	int fd;
	char buf[128];
	fd = open("/dev/chdev0",O_RDWR);
	if(fd <0){
		perror("open err");
		return -1;
	}

	strcpy(buf,"usr msg comes from write app");
	ret = write(fd, buf,sizeof(buf));
	if(ret <0){
		perror("write err");
		return -2;
	}
	sleep(1);
	close(fd);
	
	return 0;
}

  • appread-signal.c
/*****************************************************************
*   Copyright (C) 2019 Sangfor Ltd. All rights reserved.
*   
*   文件名稱:appread-signal.c
*   創 建 者:yinfei-hu
*   創建日期:2019年03月07日  星期4 20時22分05秒
*   功能描述:異步通知讀
*
*****************************************************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include "comm.h"

int fd;

void sigio_fun(int signo)
{
	int ret;
	char buf[128];
	memset(buf,0,sizeof(buf));
	ret = read(fd,buf,sizeof(buf));
	if(ret <0){
		perror("read err");
		return ;
	}
	printf("int signal fun ,usr get :%s\n",buf);
}

int main(void)
{
	int ret;
	int val;
	fd = open("/dev/chdev0",O_RDWR);
	if(fd <0){
		perror("open err");
		return -1;
	}
	
	/*	註冊信號	*/
	signal(SIGIO, sigio_fun);
	/*	設置 fd的  文件屬主,   告訴文件,你的主人進程是哪一個
		當文件有了信號之後,轉發給 屬主進程
	*/
	fcntl(fd,F_SETOWN,getpid());

	/*讓文件支持 異步通知模式
		file裏面 f_flags, 其中某一個bit表示該文件支持 異步模式
	*/
	val = fcntl(fd,F_GETFL,0);
	val |= FASYNC;
	fcntl(fd,F_SETFL,val);

	while(1){
		sleep(1);
		void do_something(void);  //延時
	}

	close(fd);

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