linux2.6.12 下s3c2440 camera接口 源码分析和个人思考之 read方法篇

来源:互联网 发布:学尤克里里用什么软件 编辑:程序博客网 时间:2024/05/24 00:52

 Read方法的主要作用就是实现内核与用户空间之间的数据拷贝。因为Linux的内核空间和用户空间隔离的,所以要实现数据拷贝就必须使用在<asm/uaccess.h>中定义的:

 

 

unsigned long copy_to_user(void __user *to,

                           const void *from,

                           unsigned long count);

unsigned long copy_from_user(void *to,

                             const void __user *from,

                             unsigned long count);

   那么当驱动程序无法立即满足请求,该如何响应? 比如:当数据不可用时调用read时怎么办。解决问题:驱动程序应该(默认)该阻塞进程,将其置入休眠状态直到请求可继续。

在这里就要插入阻塞和非阻塞模型的概念。阻塞操作是指在执行I/O操作时,若不能获得资源,则进程挂起直到满足可操作的条件在进行操作。非阻塞操作是指在执行I/O操作时,如果设备没有准备好,并不挂起,立即返回。显示的非阻塞I/Ofile->f_flags中的O_NONBLOCK

决定。

那么如何利用阻塞操作实现 read方法 。不管什么先贴代码先

static ssize_t v4l_cam_read(struct file *file, char *buf, size_t count, loff_t *ppos)

{

 

unsigned long flags;

int retval = 0;

struct s3c2440_camif *dev = file->private_data;

DPRINTK("%s():\n", __FUNCTION__);

 

if(file->f_flags&O_NONBLOCK)

{

if(down_trylock(&dev->change))

return -EAGAIN;

else 

{

if(down_interruptible(&dev->change))

return -EINTR;  }

 

local_irq_save(flags);

if(!dev->rdy)

{

if(file->f_flags&O_NONBLOCK)

retval = -EAGAIN;

else 

{

if(wait_event_interruptible(dev->wait,dev->rdy))

retval= -ERESTARTSYS;

/*interruptible_sleep_on(&dev->wait);

if(!dev->rdy)

retval = -EINTR;

*/

}

}

local_irq_restore(flags);

 

if(!retval)

{ //now if retval==0, dev->rdy must be 1

dev->rdy = 0; //clear ready flag

retval = min(count, dev->size);

if(copy_to_user(buf, (void *)camif_yuv_buf, retval)) //can copy 0 byte

retval = -EFAULT;

}

 

up(&dev->change);

 

return retval;

}

首先可以看出在file->f_flags中判断是否是非阻塞操作。然后不管什么先获取内核信号量down_trylock(&dev->change)和down_interruptible(&dev->change)就是在获取内核信号量的操作。说到这里要扯一扯什么是信号量,以及信号量怎么操作的问题。

    信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)

所拥有。信号量的值为正的时候,说明它空闲。所测试的线程可以锁定而使用它。若为 0,说明它被占用,测试的线程要进入睡眠队列中,等待被唤醒。

Linux 下提供两种信号量:

1) 内核信号量,由内核控制路径使用

2) 用户态进程使用的信号量,这种信号量又分为 POSIX 信号量和 SYSTEM 

       V 信号量。

POSIX 信号量又分为有名信号量和无名信号量。

有名信号量,其值保存在文件中所以它可以用于线程也可以用于进程间的同步。无名信号量,其值保存在内存中。

   在上文中我们提到了内核信号量,内核信号量类似于自旋锁,因为当锁关闭着时,它不允许内核控制路径继续进行。 然而,当内核控制路径试图获取内核信号量锁保护的忙资源时,相应的进程就被挂起。 只有在资源被释放时,进程才再次变为可运行。只有可以睡眠的函数才能获取内核信号量;中断处理程序和可延迟函数都不能使用内核信号量。内核信号量是 

struct semaphore 类型的对象,它在<asm/semaphore.h>中定义:

struct semaphore {

   atomic_t count;

   int sleepers;

   wait_queue_head_t wait;

  }

count:相当于信号量的值,大于 0,资源空闲;等于 0,资源忙,但没有进程等待这

个保护的资源;小于 0,资源不可用,并至少有一个进程等待资源。

wait:存放等待队列链表的地址,当前等待资源的所有睡眠进程都会放在这个链表中。

sleepers:存放一个标志,表示是否有一些进程在信号量上睡眠。

那么我们为什么要在read 方法中使用内核信号量呢?原因在于在驱动程序中,当多个线程同时访问相同的资源时(驱动中的全局变量时一种典型的共享资源),可能会引发“竞态“,因此我们必须对共享资源进行并发控制。Linux 内核中解决并发控制的最常用方法是自旋锁与信号量(绝大多数时候作为互斥锁使用)。

明白了以上的问题我们就可以继续往下解析 s3c2440camera 代码中的read 方法。

当我们获取了对资源的保护后,就调用local_irq_save(flags);禁止本地中断。在 多CPU下这个函数可能会失效。。。。(这个很纠结。但是由于我们使用的是单CPU所以,使用local_irq_save(flags);既可以禁止本地中断。在这里重要的是禁止dam中断。

   if(!dev->rdy)

{

if(file->f_flags&O_NONBLOCK)

retval = -EAGAIN;

else 

{

if(wait_event_interruptible(dev->wait,dev->rdy))

retval= -ERESTARTSYS;

}

}

   接下去这部分代码就是根据自定义设备结构体中的标志位rdy判断是否有数据喽。。(rdy在DMA中断被置位。这也就是为什么在read方法中要禁止本地中断的原因)。上文的代码表示没有数据的情况下,非阻塞操作立刻返回,同时返回错误代码EAGAIN。而阻塞操作就将进程加入等待队列,等满足条件后来取数据。

local_irq_restore(flags);

 

if(!retval)

{ //now if retval==0, dev->rdy must be 1

dev->rdy = 0; //clear ready flag

retval = min(count, dev->size);

if(copy_to_user(buf, (void *)camif_yuv_buf, retval)) //can copy 0 byte

retval = -EFAULT;

}

 

up(&dev->change);

   如果有数据,则是先释放本地中断,然后利用linux内核函数copy_to_user将数据拷贝给用户层。这样就完成了数据的读操作。最后一步up(&dev->change);这个是释放信号量。。至于为什么没有看到唤醒等待队列中的进程的函数呢?因为这个在C通道中断函数中做了。会有专门的篇幅介绍C通道中断函数的