驱动第五天

驱动第五天
【自旋锁】
    1. 原理
             
       PV操作原理
       记录一个锁定状态(就是一个共享资源,基于原子操作)
       
    2. 适用
       1. 解决多cpu之间的竞态
       2. 可以解决中断程序和普通程序之间的竞态(自旋锁可以用于中断上下文)
       3. 加锁时间不宜过长
       4. 获得自旋锁期间,不能进行调度(sleep)
       例:
       假设AB进程运行于同一CPU
       A进程 B进程
       获得自旋锁
       ...
       sleep(1);(假设调度到B进程)
                                         请求获得同一自旋锁(永远自旋)
                                         其实就是本CPU死锁
                                         
    3. 使用方法
       #include <linux/spinlock.h>
       
       spinlock_t lock; // 定义
       spin_lock_init(&lock); // 初始化
       
       spin_lock(&lock); // 获得自旋锁,才返回 spin_lock_xxx(关闭中断和关闭后半部)
       或:
       spin_trylock(&lock); // 尝试获得自选锁,获得自旋锁返回真,未获得自旋锁返回假
       ... // 访问临界资源
       spin_unlock(&lock); // 解锁 spin_unlock_xxx(使能/恢复中断和使能/恢复后半部)
       
       
【读写自旋锁】  
    1. 原理
       读锁之间不用锁定,读写锁之间需要锁定
       例:
       AB进程同时做如下操作:
       A进程 B进程
       i == 0? i == 0? 不会出问题
       
       i = 0;
       (++i) == 0? i == 0 可能会产生问题
       ldr r0, [i]
       add r0, r0, #1(A认为i 是 1)
                                            i == 0?(B认为i 是 0)
       str r0, [i]
       i == 0?
       
    2. 适用
       读写自旋锁,主要用读锁存在概率较大情况
       
    3. 使用方法
       #include <linux/spinlock.h>
       
         
       rwlock_t lock; // 定义
       rwlock_init(&lock); // 初始化
       
       read_lock(&lock); // 读时,获得锁,如果未获得自旋 read_lock_xxx(关闭中断和关闭后半部)
       或:
       read_trylock(&lock); // 读时,尝试获得自选锁,获得自旋锁返回真,未获得自旋锁返回假
       ... // 访问临界资源
       read_unlock(&lock); // 解锁 read_unlock_xxx(恢复/使能中断和恢复/使能后半部)
       
       write_lock(&lock); // 写时,获得锁,如果未获得自旋 write_lock_xxx(关闭中断和关闭后半部)
       或:
       write_trylock(&lock); // 写时,尝试获得自选锁,获得自旋锁返回真,未获得自旋锁返回假
       ... // 访问临界资源
       write_unlock(&lock); // 解锁 write_unlock_xxx(恢复/使能中断和恢复/使能后半部)
       
【顺序锁】
    1. 原理
       读锁并不锁定写锁,写锁会锁定读锁
       
    2. 适用
       1. 主要用读锁存在的概率远大于写锁
       2. 顺序锁保护的临界资源不能使指针变量
       例:错误用法
       A进程 B进程
       usngiend seqnum;
       do {
          seqnum = read_seqbegin(&lock);
          切换到B进程
                                                       write_seqlock(&lock); 可以获得锁
                                                       p = NULL;
                                                       切换回A进程
          ....
          *p = 5;(Oops)
       } while (read_seqretry(&lock, seqnum))
       
    3. 用法
       #include <linux/seqlock.h>
       
       seqlock_t lock; // 定义
       seqlock_init(&lock); // 初始化
       
       // 写 获取锁
       write_seqlock(&lock); // 可以换成write_seqlock_...
       ... // 写临界资源
       write_sequnlock(&lock); // 可以换成write_sequnlock_...
       
       // 读 不会锁定写,但会被写锁定
       unsigned seqnum;
       
       do {
           seqnum = read_seqbegin(&lock); // 可以换成read_seqbegin_irqsave(&lock, flags)
           ... // 读临界资源
       } while (read_seqretry(&lock, seqnum)); // 可以换成read_seqretry_restore(&lock, flags)
【信号量】  
    1. 原理
       信号量其实就是用来表示可以适用的资源数
       信号量本身基于自旋锁实现
       
    2. 适用
       1. 信号量可以解决多CPU之间的竞态
       2. 信号量不能用于中断程序(信号量可能引起休眠,中断不能休眠,中断下休眠系统会奔溃)
       3. 信号量持有时间可以较长(还是要尽量的缩短持有信号量时间)
       4. 信号量持有期间,可以使用可能引起调度的函数
       
    3. 使用
       #include <linux/semaphore.h>
       
       struct semaphore sem; // 定义
       sema_init(&sem, val); // val是可用的资源数
       
       down(&sem); // 获取信号量, 获得不到信号量,休眠等待,不返回,一直到获取到信号量时返回
       或:
       down_interruptable(&sem); // 获取信号量,
                                               // 获得不到信号量,休眠等待, 存在下面两种情况被唤醒:
                       1. 调用本驱动的进程收到信号,本函数会返回,这时没有获得信号量,所以会返回 1
                       2. 有进程释放信号量,本函数会获得信号量,同时返回 0
       或:
       down_trylock(&sem); // 尝试获得信号量
                           // 获得信号量成功,立即返回 0
                           // 获得信号量失败,立即返回 1
                                                       
       ... // 访问被保护资源
       up(&sem)
     
【读写信号量 】
    1. 原理
       两个读信号量不会互相锁定,读写信号量之间会互相锁定
       
    2. 适用
       读操作使用概率比较大的情况(有多个进程会同时读)
       
    3. 使用方法
       
[8] 互斥体
    只有一个资源的信号量
   
【阻塞和非阻塞】
[1] 定义
    阻塞
    读fifo时,如果fifo中没有数据,读进程调用read系统调用时不会返回
   
    非阻塞
    读fifo时,如果fifo中没有数据,读进程调用read系统调用时会立即返回,并且返回读到的数据为0
   
    linux进程状态:
   


    检测fifo有无数据的方法:
    /*
     * @brief 查询文件状态是否改变
     * @param[in] @numfds 要检测的文件描述符最大值加1
     * @param[in] @readfds 检测文件是否可读的文件描述符集
     * @param[in] @writefds 检测文件是否可写的文件描述符集
     * @param[in] @exceptfds 检测文件是否发生异常的文件描述符集
     * @param[in]] @timeout 超时的结构体定义
     * @return > 0 表示发生变化的文件描述符个数
     * = 0 等待超时
     * -1 表示出错
     */
    int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
   
[2] 等待队列
    1. 原理
       添加到等待队列的进程,会休眠等待唤醒
      
       
    2. 适用
       凡是需要等待休眠的地方都可以用等待队列, 经常用于阻塞进程
       
    3. 使用方法
       #include <linux/wait.h>
       #include <linux/sched.h>
       
       wait_queue_head_t queue; // 定义等待队列头
       init_waitqueue_head(&queue); // 初始化等待队列
       
       // 在需要等待的进程中,调用下面函数:
       /*
        * 添加进程到等待队列,并且改变进程的状态为深度休眠
        * queue 等待队列
        * condition 等待条件,为true进程从等待队列中删除,为false继续等待
        */
       wait_event(queue, condition);
       
        /*
        * 添加进程到等待队列,并且改变进程的状态为浅度休眠
        * queue 等待队列
        * condition 等待条件,为true进程从等待队列中删除,为false继续等待
        * 如果接收到信号,唤醒进程,返回-ERESTARTSYS, 如果是条件成立,唤醒进程,返回0
        */
       wait_event_interruptable(queue, condition);
       
       
       // 另一个进程,唤醒等待进程
       // 唤醒等待队列上所有的进程,等待进程会自己检查条件是否成立,成立返回,不成立继续休眠等待
       void wake_up(wait_queue_head_t *queue);
       wake_up_interruptible(wait_queue_head_t *queue);
       
[3] 非阻塞实现方法
    1. 原理
/************************ poll()函数典型模板************************/
static unsigned int xxx_poll(struct file *filp, poll_table *wait)
{
unsigned int mask = 0;  
struct xxx_dev *dev = filp->private_data; /*获得设备结构体指针*/
...
poll_wait(filp, &dev->r_wait, wait);/* 加读等待队列头 */
poll_wait(filp, &dev->w_wait, wait);/* 加写等待队列头 */
if (...) /* 可读 */
mask |= POLLIN | POLLRDNORM; /*标示数据可获得*/
if (...) /* 可写 */
mask |= POLLOUT | POLLWRNORM; /*标示数据可写入*/
...
return mask;  
图解:

       1. 应用程序中调用select查询指定文件是否可读、可写或异常
       2. 系统会调用驱动中的poll函数(file_operations中的),查询指定文件是否可读、可写
       3. poll返回文件否可读、可写
       4. 如果应用程序调用select时,没有加超时,这时系统会调用wait_event_interruptible,
          根据select监控的文件描述符集,来确定都等待队列还是写等待队列上等待,读写任何一个条件成立,
          当前进程都会被从等待队列上摘除。
          如果应用程序调用select时,有加超时, 这时系统会调用wait_event_interruptible_timeout
       5. 如果wait_event_timeout超时,系统会再次调用驱动中的poll函数确认指定文件是否可读、可写或异常状态  
          如果有别的进程调用wake_up_interruptible(如果是读等待队列,这个函数在驱动write函数中调用,
                          如果是写等待队列,这个函数在驱动read函数中调用),
                          系统会再次调用驱动中的poll函数确认指定文件是否可读、可写或异常状态
       7. select系统调用返回
   
    2. 适用
       用于实现非阻塞
       
    3. 使用方法
       // wait queque
       #include <linux/wait.h>
       #include <linux/sched.h>
       
       // poll / select
       #include <linux/poll.h>
       
       // 修改read
       if (filp->f_flags & O_NONBLOCK) {
           // 非阻塞实现
         if (!fifo_have_data(&fifo)) {
            return -EAGAIN;
         }
      } else {
          // 阻塞
         wait_event_interruptible(readq, fifo_have_data(&fifo));
      }
     
      // 添加poll函数
      /*
        * 查询文件状态是否改变
        * filp 要查询的文件描述符相对应
        * wait 等待队列表
        * 返回文件可读、可写、异常状态
        */
        unsigned int char_poll(sturct file *filp, struct poll_table_struct *wait)
        {
           unsigned int mask = 0;
           
           poll_wait(filp, &dev->readq, wait);//加读等待队列头,不会进入休眠状态,这个函数会很快返回  
           
           if (fifo_have_data(&fifo)) { // 可读?
               mask |= POLLIN | POLLRDNORM; /*标示数据可获得*/
           }
           return mask;
         }
     
        // 修改write
        写入数据后,要调用wake_up_interruptible

【学习总结】

/****************************************顺序锁(seqlock******************************************************/

/*

 *注意:当要保护的资源很小,很简单,会频繁被访问而且写入访问很少发生且必须快速时,就可以使用seqlock,从本质上讲,seqlock会允许读取者对资源的自由访问,但需要读取者检查是否和写入者发生冲突,当发生这种冲突,就需要重试对资源的访问,seqlock通常不能保护包含指针的数据结构

 */

#include <linux/seqlock.h>

seqlock_t lock=SEQLOCK_UNLOCKED;

seqlock_t lock;

seqlock_init(&lock);

//读存取通过在进入临界区入口获取一个(无符号的)整数序列来工作在退出时,那个序列值与当前值比较如果不匹配读存取必须重试读者代码如下面的形式:

unsigned int seq;

do {

seq = read_seqbegin(&the_lock);

/* Do what you need to do */

} while read_seqretry(&the_lock, seq);

//这个类型的锁常常用在保护某种简单计算需要多个一致的值如果这个计算最后的测试表明发生了一个并发的写结果被简单地丢弃并且重新计算.如果你的 seqlock 可能从一个中断处理里存取你应当使用 IRQ 安全的版本来代替:

unsigned int read_seqbegin_irqsave(seqlock_t *lock, unsigned longflags);

int read_seqretry_irqrestore(seqlock_t *lock, unsigned int seq, unsignedlong flags);

//写者必须获取一个锁来进入由一个seqlock 保护的临界区为此调用:

void write_seqlock(seqlock_t *lock);

//写锁由一个自旋锁实现因此所有的通常的限制都适用调用:

void write_sequnlock(seqlock_t *lock);

//来释放锁因为自旋锁用来控制写存取所有通常的变体都可用:

void write_seqlock_irqsave(seqlock_t *lock, unsigned long flags);

void write_seqlock_irq(seqlock_t *lock);

void write_seqlock_bh(seqlock_t *lock);

void write_sequnlock_irqrestore(seqlock_t *lock, unsigned long flags);

void write_sequnlock_irq(seqlock_t *lock);

void write_sequnlock_bh(seqlock_t *lock);

//还有一个 write_tryseqlock 在它能够获得锁时返回非零.

/****************************end******************************************/

 

/***************************ioctl******************************************/

//在用户空间, ioctl 系统调用有下面的原型:

int ioctl(int fd, unsigned long cmd, ...);

//ioctl 驱动方法有和用户空间版本不同的原型:

int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd,unsigned long arg);

inodefilp两个指针的值对应于应用程序传递的文件描述符fd,参数cmd由用户空间不经修改地传递给驱动程序

。。。。。。

/****************************end*********************************************/

 

 

/****************************休眠*********************************************/

/*

当一个进程被置为休眠时,它会被标记为一种特殊状态并从调度器的运行队列移走,直到某些情况下修改了这个状态,进程才会在任CPU调度,也即运行该进程,休眠中的进程会被搁置在一边,等待将来的每个事件发生,我们的驱动程序不能再拥有自旋锁锁时休眠、seqlock或者RCU锁时休眠,如果我们已经禁止了中断也不能休眠,拥有信号量休眠是合法的,但任何拥有信号量

而休眠的代码必须很短,并且还要确保有能唤醒自己,能够找到休眠的进程意味着需要维护一个称为等待队列的数据结构,等待队列就是一个进程链表,其中包含了等待某个特定事件的所有进程,在linux中,一个等待队列通过一个等待队列头来管理

*/

//等待队列:

#include <linux/wait.h>

//初始化一个等待队列头:

DECLARE_WAIT_QUEUE_HEAD(name);

wait_queue_head_t xxx_queue; //定义一个等待队列头

init_waitqueue_head(&xxx_queue);//初始化对待队列头

DECLARE_WAITQUEUE(name, tsk); //定义等待队列,wait_queue_t namestruct task_struct *tsk

//添加或移除等待队列:

void fastcall add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait );

//将等待队列wait添加到等待队列头q指向的等待队列链表中

void fastcall remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait );

//将等待队列wait从等待队列头q指向的等待队列链表中移除

//等待事件(简单休眠):

wait_event(xxx_queue,condition);

 //xxx_queue为等待队列头,是值传递,condition是任意一个布尔表达式,在条件为真前,进程会保持休眠

wait_event_interruptible(xxx_queue,condition);

//可中断,返回值为非0表示休眠被某个信号中断,而驱动程序要返回-ERESTARTSYS

wait_event_timeout(xxx_queue,condition,timeout);

//只会等待给定的时间,当给定的时间到期时,这两个宏都会返回0值,不论condition如何求值,如果由其他事件唤醒,则返回剩余的延时时间

wait_event_interruptible_timeout(xxx_queue,condition,timeout);

//唤醒休眠的进程:

void wake_up(wait_queue_head_t *queue);

void wake_up_interruptible(wait_queue_head_t *queue);

//在等待队列上睡眠:

sleep_on(wait_queue_head_t *q);

//将目前进程的状态设置为TASK_UNINTERRUPTIBLE,并定义一个等待队列,之后把它附属到等待队列头,直到资源可获取,q引导的等待队列被唤醒,其和wake_up成对使用

interruptible_sleep_on(wait_queue_head_t *q);

//与上类似,其将目前进程的状态设置为TASK_INTERRUPTIBLE,其和wake_up_interruptible成对使用

/*

高级休眠:

复杂的锁定以及性能需求会强制驱动程序使用底层的函数来实现休眠

将进程置于休眠的步骤:

1 分配并初始化一个等待队列wait_queue_t,然后将其加入到对应的等待队列头指向的等待链表

2 设置进程的状态,将其标记休眠,<linux/sched.h>中定义了多个任务状态,TASK_RUNNING表示进程可运行,尽管进程并不一定在任何给定时间都运行在每个处理器上,TASK_INTERRUPTIBLETASK_UNINTERRUPTIBLE表明进程处于休眠状态,可调用下面函数来设置进程状态:

      void set_current_state(int new_state); //在老代码中还可以看到current->state=TASK_INTERRUPTIBLE

3)通过改变当前进程状态,我们只是改变了调度器处理该进程的方式,但尚未使进程让出处理器,那么最后一步就是放弃处理器

调用下面函数:

     schedule(); //schedule的调用将调用调度器,并让出CPU

*/

//手工休眠:

1)建立并初始化一个等待队列入口

     DEFINE_WAIT(xxx_wait);//name为等待队列入口变量的名称

     

     wait_queue_t xxx_wait;

     init_wait(&xxx_wait);

2)将等待队列入口添加到队列中,并设置进程的状态

    void prepare_to_wait(wait_queue_head_t *queue, wait_queue_t *wait, int state); //queuewait分别是等待队列头和进程入口,state为进程的新状态,它应该是TASK_INTERRUPTIBLETASK_UNINTERRUPTIBLE

3)让出CPU

    if(条件没有就绪)

      schedule();

4)一旦 schedule返回,就到了清理时间了

     void finish_wait(wait_queue_head_t *queue,wait_queue_t *wait) ;

5)之后,代码可测试其状态,并判断是否需要重新等待

//在许多设备驱动中亲自进行进程的状态改变和切换:

 {

  DECLARE_WAITQUEUE(wait, current); //定义等待队列

  add_wait_queue(&xxx_wait,&wait ); //添加等待队列

 

  do{

   .....

    if(资源未就绪)

     {

      __set_current_state(TASK_INTERRUPTIBLE); //改变进程状态

      schedule();//调度其他进程执行

      }

    .....

    }while(资源未就绪);

  

  ...........

  ...........

 }

/*********************************end**********************************************/

 

/********************************poll机制(非阻塞)************************************/

原型:unsigned int xxx_poll(struct file filp, poll_table *wait);

// poll_table结构用于实现pollselect系统调用

 

#include <linux/poll.h>

1void poll_wait(struct file *filp,wait_queue_head_t queue, poll_table *wait);

//poll_table结构添加一个等待队列,不会休眠,而是会在系统调用知道超时时间时,超时才会休眠

2poll的第二项任务是返回描述哪个操作可以立即执行的位掩码

    常用的位掩码有:

                    POLLLIN //如果设备可以无阻塞的读取,就设置该位

                  

                    POLLRDNORM

//如果通常的数据已经就绪,可以读取,就设置该位一个可读设备返回(POLLLIN|POLLRDNORM)

                  

                    POLLHUP

 // 当读取设备的进程到达文件尾时,驱动程序必须设置该(挂起)位,依照select功能描述,调用select的进程会被告知设备是可读的

                  

                    POLLERR //设备发生了错误

                  

                    POLLOUT //如果设备可以无阻塞的写入,就设置该位

                  

                    POLLWRNORM

//如果通常的数据已经就绪,可以写入,就设置该位一个可读设备返回(POLLOUT|POLLWRNORM)

一般程序设计模板:

             unsigned int xxx_poll(struct file filp, poll_table *wait)

                {

                 unsigned int mask;

                 wait_poll(filp,&xxx_dev->inq,wait);

                 wait_poll(filp,&xxx_dev->outq,wait);

                 if(可读)

                   mask=POLLLIN|POLLRDNORM;

                 if(可写)

                   mask=POLLOUT|POLLWRNORM;

                 if(无数据可获取)

                   mask=POLLHUP;

                 return mask;

                }

 

/**********************************end*****************************************/

【学习代码】
1. 自旋锁spinlock的实现

【主程序1char_dev.c

// 模块头文件

#include <linux/init.h>

#include <linux/module.h>

// 字符设备头文件

#include <linux/cdev.h>

#include <linux/fs.h>

// copy_to_user/copy_from_user

#include <linux/uaccess.h>

// ioctl

#include <linux/ioctl.h>

// 导出设备信息到sysfs

#include <linux/device.h>

#include "char_dev.h"

#include "fifo.h"

// 1. 创建字符设备对象(定义结构体变量)

// struct cdev char_demo; 静态定义

struct cdev *char_demo;

struct class * char_demo_class;

struct device *char_demo_device;

FIFO fifo; // 1. 定义

// 0-表示需用动态分配设备号 0 - 表示静态分配设备号

int major = 0;

int minor = 0;

int char_open(struct inode *inode, struct file *filp)

{

 printk("char open\n");

 return 0;

}

// int read(fd, buf, sizeof(buf)); app

ssize_t char_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)

{

 int ret = 0;

 char fifo_buf[50];

 

 // 3. 读功能实现

 ret = fifo_read(&fifo, fifo_buf, sizeof(fifo_buf));

 if (ret < 0) {

  ret = 0;

  goto exit;

     }

 

 /*

  * @brief 拷贝数据到应用程序空间

  * @param[out] to 应用程序空间buf,要拷贝到的地方

  * @param[in] from 内核空间buf,从哪个地方拷贝数据

  * @param[in] n 拷贝数据长度

  * @return 未成功拷贝的数据数量一般返回0

  * @notes copy_to_user可能会引起睡眠

  */

 // unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)

 

 ret = copy_to_user(buf, fifo_buf, 50);

 if (ret) {

  ret = -ENOMEM;

 } else {

  ret = 50;

 }

 

exit:

 return ret;

}

// int write(fd, buf, len);

ssize_t char_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)

{

 int ret = 0;

 char fifo_buf[50];

 

 if (size > 50) {

  size = 50;

 }

 

 /*

  * @brief 拷贝数据从应用程序空间

  * @param[out] to 内核空间buf,要拷贝到的地方

  * @param[in] from 应用空间buf,从哪个地方拷贝数据

  * @param[in] n 拷贝数据长度

  * @return 未成功拷贝的数据数量一般返回0

  * @copy_from_user可能会引起睡眠

  */

 // unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)

 

 ret = copy_from_user(fifo_buf, buf, size);

 if (ret) {

  ret = -ENOMEM;

 } else {

  ret = size;

  printk("%s\n", fifo_buf);

 }

 // 4. 写功能实现

 fifo_write(&fifo, fifo_buf);

 

 return ret;

}

long char_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)

{

 long ret = 0;

 

 switch (cmd) {

  case CMD_DEMO0:

   printk("CMD_DEMO0\n");

   break;

  

  case CMD_DEMO1:

   printk("CMD_DEMO1\n");

   break;

  

  default:

   ret = -ENOTTY;

   break;

 }

 

 return ret;

}

int char_release(struct inode *inode, struct file *filp)

{

 printk("char release\n");

 return 0;

}

struct file_operations fops = {

 .owner = THIS_MODULE, // 当前模块

 .open = char_open,

 .read = char_read,

 .write = char_write,

 .unlocked_ioctl = char_ioctl,

 .release = char_release,

};

 

int __init char_init(void)

{

 int ret = 0;

 dev_t devno;

 

 // 给设备分配位置

 // 设备号申请成功(cat /proc/devices 会有主设备号和名字的对应关系)

 if (major != 0) {

  // 静态分配设备号

  // 找设备号的方法:Documention/devices

  devno = MKDEV(major, 0);

  ret = register_chrdev_region(devno, 1, "char demo");

  if (ret) {

   goto register_chrdev_region_err;

  }

 

 } else {

 

  /*

  * @brief 动态分配设备号

  * @param[out] dev 返回分配的设备号

  * @param[in] baseminor 第一个次设备号

  * @param[in] count 分配设备号的数量

  * @param[in] name 主设备号的名字

  * @return 0 分配成功

  * < 0 错误码

  *

  int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,

   const char *name)

  */

   ret = alloc_chrdev_region(&devno, minor, 1, "char demo");

   if (ret) {

    goto register_chrdev_region_err;

   }

   major = MAJOR(devno);

 }

 

 // 1. 创建字符设备对象(动态分配)

 char_demo = cdev_alloc();

 if (NULL == char_demo) {

  ret = -ENOMEM;

  goto cdev_alloc_err;

 }

 

 // 2. 初始化字符设备对象

 char_demo->owner = THIS_MODULE;

 char_demo->ops = &fops;

 // cdev_init(&char_demo, &fops);

 

 // 2. fifo初始化

 fifo_init(&fifo);

 

 // 3. 添加字符设备到内核

 // ret = cdev_add(&char_demo, devno, 1);

 ret = cdev_add(char_demo, devno, 1);

 if (ret) {

  goto cdev_add_err;

 }

 

 // 自动创建设备文件结点(udevfs/mdev)

 // 1. 创建设备类

 char_demo_class = class_create(THIS_MODULE, "char_demo");

 if (IS_ERR(char_demo_class)) {

  ret = PTR_ERR(char_demo_class);

  goto class_create_err;

 }

 

 // 2. 导出设备信息到应用空间(类的目录下)

 char_demo_device = device_create(char_demo_class, NULL, devno, NULL, "chardev");

 if (IS_ERR(char_demo_device)) {

  ret = PTR_ERR(char_demo_class);

  goto device_create_err;

 }

 

 goto register_chrdev_region_err;

 

device_create_err:

 class_destroy(char_demo_class);

 

class_create_err:

 cdev_del(char_demo);

cdev_add_err:

cdev_alloc_err:

 unregister_chrdev_region(devno, 1);

 

register_chrdev_region_err:

 return ret;

}

void __exit char_exit(void)

{

 dev_t devno = MKDEV(major, minor);

 

 device_destroy(char_demo_class, devno);

 class_destroy(char_demo_class);

 // cdev_del(&char_demo);

 cdev_del(char_demo);

 unregister_chrdev_region(devno, 1);

}

module_init(char_init);

module_exit(char_exit);

MODULE_LICENSE("GPL");

 

【主程序2fifo.c

#include "fifo.h"

void fifo_init(FIFO *fifo)

{

 memset(fifo->buf, 0, sizeof(fifo->buf));

 fifo->read = 0;

 fifo->write = 0;

 

 // 2. 自旋锁初始化

 spin_lock_init(&fifo->lock);

}

int fifo_read(FIFO *fifo, char *buf, int size)

{

 if (NULL == fifo) {

  return -1;

 }

 if (size > ENUM_SIZE) {

  return -1;

 }

 

 spin_lock(&fifo->lock);

 if (fifo->read == fifo->write) {

  spin_unlock(&fifo->lock);

  return -1;

 }

 spin_unlock(&fifo->lock);

 

 memcpy(buf, fifo->buf[fifo->read], ENUM_SIZE);

 

 spin_lock(&fifo->lock);

 fifo->read += 1;

 fifo->read %= BUF_NUM;

 spin_unlock(&fifo->lock);

 

 return 0;

}

int fifo_write(FIFO *fifo, char *buf)

{

 if (NULL == fifo) {

  return -1;

 }

 

 if (NULL == buf) {

  return -1;

 }

 

 spin_lock(&fifo->lock);

 if ((fifo->write + 1) % ENUM_SIZE == fifo->read) {

  spin_unlock(&fifo->lock);

  return -1;

 }

 spin_unlock(&fifo->lock);

 

 memcpy(fifo->buf[fifo->write], buf, ENUM_SIZE);

 

 spin_lock(&fifo->lock);

 fifo->write += 1;

 fifo->write %= BUF_NUM;

 spin_unlock(&fifo->lock);

 

 return 0;

}

【头文件1char_dev.h

#ifndef __CHAR_DEV_H__

#define __CHAR_DEV_H__

#define CMD_DEMO0  _IO('K', 0)

#define CMD_DEMO1  _IO('K', 1)

#endif // __CHAR_DEV_H__

 

【头文件2fifo.h

#ifndef __FIFO_H__

#define __FIFO_H__

#include <linux/spinlock.h>

#include <linux/string.h>

#define BUF_NUM  5

#define ENUM_SIZE  50

typedef struct fifo_t{

 char buf[BUF_NUM][ENUM_SIZE];

 int read, write;

 

 // 自旋锁使用 1. 定义

 spinlock_t lock;

} FIFO;

void fifo_init(FIFO *fifo);

int fifo_read(FIFO *fifo, char *buf, int size);

int fifo_write(FIFO *fifo, char *buf);

#endif // __FIFO_H__

 

Makefile

# KERNELRELEASE 变量装载的是内核版本,变量在内核顶层目录下的Makefile赋值

ifeq ($(KERNELRELEASE),)

内核源代码目录(模块将运行的内核的)

#KERNELDIR ?= /usr/src/linux-headers-$(shell uname -r) #(uname -r查看)

KERNELDIR ?= /work/S5PC100/linux-2.6.35-farsight

PWD := $(shell pwd)

调用内核顶层目录下的Makefile,是它调用本Makefile编译本模块

# -C 在做任何事情之前,进入指定目录

# M= 告诉内核顶层目录的Makefile,模块的位置

# modules 告诉内核顶层目录的Makefile 仅仅编译模块

all:

 $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

清除编译出来的文件

clean:

rm -rf *.*~ core .depend .*.cmd *.ko *.mod..tmp_versions Module* modules*

else

被内核顶层目录的Makefile调用,来编译模块

obj-+= char_fifo.o #模块重命名

char_fifo-objs := char_dev.o fifo.o

endif

 

2. 阻塞wait_queue的实现

相对于上面的自旋锁,只需要修改主程序1中的几个函数:

// int read(fd, buf, sizeof(buf)); app

ssize_t char_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)

{

 int ret = 0;

 char fifo_buf[50];

 

 wait_event_interruptible(readq, fifo_have_data(&fifo));

 

 // 3. 读功能实现

 ret = fifo_read(&fifo, fifo_buf, sizeof(fifo_buf));

 if (ret < 0) {

  ret = 0;

  goto exit;

     }

 

 ret = copy_to_user(buf, fifo_buf, 50);

 if (ret) {

  ret = -ENOMEM;

 } else {

  ret = 50;

     }

 

exit:

     return ret;

}

ssize_t char_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)

{

 int ret = 0;

 char fifo_buf[50];

 

 if (size > 50) {

  size = 50;

 }

 

 ret = copy_from_user(fifo_buf, buf, size);

 if (ret) {

  ret = -ENOMEM;

 } else {

  ret = size;

  printk("%s\n", fifo_buf);

 }

 // 4. 写功能实现

 fifo_write(&fifo, fifo_buf);

 

 // 4. 另一进程唤醒

 wake_up_interruptible(&readq);

 

 return ret;

}


3. 非阻塞poll的实现

相对于上面的阻塞,只需要修改主程序1中的几个函数:

int char_open(struct inode *inode, struct file *filp)

{

 if (filp->f_flags & O_NONBLOCK) {

  printk("no block\n");

 }

 

 printk("char open\n");

 return 0;

}

ssize_t char_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)

{

 int ret = 0;

 char fifo_buf[50];

 

 // 5. 实现读非阻塞

 if (filp->f_flags & O_NONBLOCK) {

  // 非阻塞实现

  if (!fifo_have_data(&fifo)) {

   return -EAGAIN;

  }

 } else {

  wait_event_interruptible(readq, fifo_have_data(&fifo));

 }

 

 ret = fifo_read(&fifo, fifo_buf, sizeof(fifo_buf));

 if (ret < 0) {

  ret = 0;

  goto exit;

 }

 

 ret = copy_to_user(buf, fifo_buf, 50);

 if (ret) {

  ret = -ENOMEM;

 } else {

  ret = 50;

 }

 

exit:

 return ret;

}

ssize_t char_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)

{

 int ret = 0;

 char fifo_buf[50];

 

 if (size > 50) {

  size = 50;

 }

 

 ret = copy_from_user(fifo_buf, buf, size);

 if (ret) {

  ret = -ENOMEM;

 } else {

  ret = size;

  printk("%s\n", fifo_buf);

 }

 fifo_write(&fifo, fifo_buf);

 

 // 4. 另一进程唤醒

 wake_up_interruptible(&readq);

 

 return ret;

}

注:以上总结部分选自韦东山群答疑助手:沈朝平《Linux驱动程序学习笔记》!非常感谢!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值