linux驱动IO模型

1.非阻塞

当应用层读取驱动中的数据时,无论数据是否准备号,都需要立即返回。

open("/dev/mycdev",O_RDWR|O_NOBLOCK);

// 非阻塞方式打开

// 默认打开方式为阻塞方式打开

// O_NOBLIOCK在file结构体中file->f_flage中


2.阻塞

当应用层读取驱动中的数据时,如果硬件中的数据没有准备好,进程需要进入到休眠的状态。(可中断的等待态,不可被中断的等待态(信号))。当硬件的是数据准备好的时候,会产生一个中断,在中断的处理函数中唤醒休眠的进程即可。休眠的进程被唤醒之后,读取硬件中的数据,将这个数据拷贝到用户空间即可。

1.阻塞:

        1.定义等待队列头

                wait_queue_head_t wq;

        2.初始化等待队列头

                init_waitqueue_head(&wq);

        3.如果condition为假就休眠

                wait_event(wq,condition)

                //让进程进入不可中断的等待态

                wait_event_interruptible(wq, condition)

                //让进程进入可中断的等待态

         4.唤醒

                condition = 1

                //如果condition不置为1,唤醒之后会继续休眠

                wake_up(&wq)  //唤醒的函数(不可中断的等待态)

                wake_up_interruptible(&wq)  //唤醒的函数(可中断的等待态)

//wait_event_interruptible 宏
#define wait_event_interruptible(wq, condition)				
({									
	int __ret = 0;							
	if (!(condition))						
		__wait_event_interruptible(wq, condition, __ret);	
	__ret;								
})


//wait_event_interruptible 中__wait_event_interruptible 的宏
#define __wait_event_interruptible(wq, condition, ret)						
    do {									
    	DEFINE_WAIT(__wait);		
				
    	for (;;) {							
    		prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);	
	    	if (condition)			
                //如果条件为假,if不成立			
	    		break;						
	    if (!signal_pending(current)) {		
                //如果这个进程没有收到信号,它就会调用schedule函数完成休眠
                //如果收到信号就返回	ERESTARTSYS	错误码
			schedule();					
			continue;					
		}							
		ret = -ERESTARTSYS;					
		break;							
	}		

  DEFINE_WAIT(__wait);    

//定义一个wait_queue_t的结构体变量,将当前的进程放入到这个结构体变量中

//__wait_event中DEFINE_WAIT(__wait)的宏	
#define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)

//DEFINE_WAIT_FUNC的宏
#define DEFINE_WAIT_FUNC(name, function)				
	wait_queue_t name = {						
		.private	= current,				
		.func		= function,				
		.task_list	= LIST_HEAD_INIT((name).task_list),	
	}

//current的宏
#define current get_current()

//get_current()的作用获取进程task
#define get_current() (current_thread_info()->task)

//current_thread_info()的函数实现->返回一个struct thread_info 的指针ti
static inline struct thread_info *current_thread_info(void)
{
	struct thread_info *ti;
	unsigned long mask = THREAD_SIZE - 1;
	void *p;
	asm volatile ("" : "=r" (p) : "0" (&ti));
	ti = (struct thread_info *) (((unsigned long)p) & ~mask);
	return ti;
}


struct thread_info {
	struct task_struct	*task;		/* main task structure */
	struct exec_domain	*exec_domain;	/* execution domain */
	unsigned long		flags;		/* low level flags */
	__u32			cpu;		/* current CPU */
	int			preempt_count;  /* 0 => preemptable,
						   <0 => BUG */
	mm_segment_t		addr_limit;	/* thread address space:
					 	   0-0xBFFFFFFF for user
						   0-0xFFFFFFFF for kernel */
	struct restart_block    restart_block;
	struct thread_info	*real_thread;    /* Points to non-IRQ stack */
};

prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);     

//将等待队列项放入到队列头中

prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)
{
	unsigned long flags;

	wait->flags &= ~WQ_FLAG_EXCLUSIVE;
	spin_lock_irqsave(&q->lock, flags);
	if (list_empty(&wait->task_list))
        //判断队列是否为空
		__add_wait_queue(q, wait);
        //将等待队列项放到队列头的后面
	    set_current_state(state);

	    spin_unlock_irqrestore(&q->lock, flags);
}

#define set_current_state(state_value)	set_mb(current->state, (state_value))

3.IO多路复用

        在同一个APP应用中想同时读取多个硬件的数据,此时就需要使用select/poll/epoll去监听多个文件的描述符,如果 select/poll/epoll返回了,使用read从准备好的文件描述符中读取数据即可。

 select

 select(maxfd+1,&rfds,NULL,NULL,NULL);

-------(swi 软中断号)-------

kernel-3.4.39/arch/arm/kernel/calls.S

/*142*/         CALL(sys_select)

 VFS:sys_select

                        core_sys_select(n,inp,oup,exp,to);

        1,在内核空间分配内存,将文件描述符的集合从用户空间拷贝到内核空间

                do_select(n,&fds,end_time)       

        2.遍历文件描述符中哪个bit被置为1,这个bit的下标就是文件描述符

                fd---->fd_array[fd]---->struct file----->fops ---->poll(file,wait)

 select/poll/epoll区别?

        select:

                1.select监听的文件描述符最大时1024个

                2.select表会被清空,需要反复从用户空间向内核空间拷贝文件描述的表,效率比较低。

                3.当select对应的进程从休眠的状态被唤醒后需要通过遍历的方式找到准备好的描述符,这个过程效率比较低。

        poll:

                1.poll监听的文件描述符没有个数限制。

                2.poll没有清空表的过程,不需要反复拷贝文件描述符的表,效率高。

                3.当poll对应的进程从休眠的状态被唤醒后需要通过遍历的方式找到准备好的描述符,这个过程效率比较低。

        epoll:   

                1.poll监听的文件描述符没有个数限制。

                2.poll没有清空表的过程,不需要反复拷贝文件描述符的表,效率高。

                3.当epoll对应的进程从休眠的状态被唤醒后,不需要遍历文件描述符,直接将这些准备好的文件描述符拷贝到用户空间。(它内部是通过红黑树实现,它的最左侧就是用来存准备好的文件描述符,所以不需要去遍历)。

                

        


4.异步通知

        当硬件中的数据准备好的时候,进入到中断处理函数中,在中断处理函数中发送信号给应用程序,在应用程序中注册信号处理函数(signal),在信号处理函数中读取数据即可。

应用层:

        信号处理函数

        {

        }

        1. fd = open("/dev/**",O_RDWR);

        2.signal(SIGIO,信号处理函数);

        3.fcntl(fd,F_SETOWN,getpid());

        //设置进程号

        4.unsigned int flags = fcntl(fd,F_GETFL);

        fcntl(fd,F_SETFL,flags|FASYNC);

        //通过此步调用内核中的fasync();

       

驱动层:

        1.int (*fasync) (int,struct file * int);

        2.int fasync_helper(int fd, struct file * filp,int on, struct fasync_struct **fapp)
        功能:fasync_struct结构体的初始化
        3.发信号
         void kill_fasync(struct fasync_struct **fp, int sig, int band)    
         kill_fasync(struct fasync_struct **fp, SIGIO, POLL_IN)  

内核层:

            vi   -t   sys_fcntl    //系统调用
              filp = fget_raw(fd);
             err = do_fcntl(fd, cmd, arg, filp);
             switch (cmd) {
                  case F_GETFL:
                      err = filp->f_flags;     
                 case F_SETFL:
                      err = setfl(fd, filp, arg);  
    
                if (((arg ^ filp->f_flags) & FASYNC) && filp->f_op &&filp->f_op->fasync)

                {
                    error = filp->f_op->fasync(fd, filp, 
                        (arg & FASYNC) != 0);

                }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值