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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章