Chinaunix首页 | 论坛 | 博客
  • 博客访问: 357809
  • 博文数量: 90
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 113
  • 用 户 组: 普通用户
  • 注册时间: 2016-01-27 19:56
文章分类

全部博文(90)

文章存档

2017年(12)

2016年(78)

分类: LINUX

2016-09-01 10:54:26

信号量的结构体定义如下:
linux+v2.6.28/include/linux/semaphore.h:

struct semaphore {
    spinlock_t lock;  //自旋锁
    unsigned int count;
    struct list_head wait_list; //内核的双向链表
};


初始化信号量:

#define init_MUTEX(sem)         sema_init(sem, 1)
#define init_MUTEX_LOCKED(sem)  sema_init(sem, 0)


获取信号量:

extern void down(struct semaphore *sem);
extern int __must_check down_interruptible(struct semaphore *sem);


以上两个函数都是用来获取信号量的。不同之处在于down会导致睡眠,而且不能被信号打断。而down_interruptible也会导致睡眠,但是它可以被信号打断。

释放信号量:

extern void up(struct semaphore *sem);

/***********************************************************************************************/
为了更好的理解阻塞和非阻塞,先来看两段代码:

/******************************代码1***************************************/

/* 阻塞地从串口读取一个字符*/
fd = open("/dev/ttyS1", O_RDWR);
...
ret = read(fd, buf, 1); //当串口上有数据时才返回
if(ret > 0)
    printf("%c\n", buf);
/******************************代码2***************************************/
/* 非阻塞地从串口读取一个字符 */
fd = open("/dev/ttyS2", O_RDWR|O_NONBLOCK);
...
while(read(fd, buf, 1) != 1); //串口上没有数据也返回,所以要循环读取串口
printf("%c", buf);


阻塞是指在执行设备操作时如果不能获得资源则挂起进程,直到资源可以被获取后再进行操作。
阻塞从字面上听起来意味着低效率,实则不然,如果设备不阻塞,则用户想要获取设备资源只能不停的查询,这反而会无谓地消耗CPU资源。而阻塞访问时,不能获取资源的进程将进入休眠,它将CPU资源让给其他进程,直到资源可以被获取。
在LInux驱动程序中,可以使用等待队列来实现阻塞进程的唤醒。

等待队列结构体如下:
linux+v2.6.28/include/linux/wait.h:

typedef struct wait_queue wait_queue_t;
struct __wait_queue {
        unsigned int flags;
#define WQ_FLAG_EXCLUSIVE 0x01
        void *private;
        wait_queue_func_t func;
        struct list_head task_list;
};   

等待队列头定义如下:
linux+v2.6.28/include/linux/wait.h:

struct __wait_queue_head {
       spinlock_t lock;
       struct list_head task_list;
};
typedef struct __wait_queue_head     wait_queue_head_t;


初始化等待队列头和等待队列:

初始化等待队列头有两种方式:
void init_waitqueue_head(wait_queue_head_t *q);
DECLARE_WAIT_QUEUE_HEAD(name);
linux+v2.6.28/include/linux/wait.h:

#define __WAIT_QUEUE_HEAD_INITIALIZER(name) { \
       .lock = __SPIN_LOCK_UNLOCKED(name.lock), \
       .task_list = { &(name).task_list, &(name).task_list } }
   
#define DECLARE_WAIT_QUEUE_HEAD(name) \

wait_queue_head_t name =  WAIT_QUEUE_HEAD_INITIALIZER(name)


linux+v2.6.28/kernel/wait.c:

void init_waitqueue_head(wait_queue_head_t *q)
{
    spin_lock_init(&q->lock);
    INIT_LIST_HEAD(&q->task_list);
}


对于等待队列,使用一个宏去定义和初始化:

DECLARE_WAITQUEUE(name, tsk)

#define __WAITQUEUE_INITIALIZER(name, tsk) { \
    .private = tsk, \
    .func = default_wake_function, \
    .task_list = { NULL, NULL } }
   
#define DECLARE_WAITQUEUE(name, tsk) \
     wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)


在使用等待队列前,需要定义和初始化等待队列头和等待队列。

定义和初始化完成后,需要从等待队列头添加或者移出等待队列:
linux+v2.6.28/kernel/wait.c:

/* 添加等待队列*/

void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
{
    unsigned long flags;
   
    wait->flags &= ~WQ_FLAG_EXCLUSIVE;
    spin_lock_irqsave(&q->lock, flags);
    __add_wait_queue(q, wait);
    spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL(add_wait_queue);

/* 移出等待队列*/

void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
{
    unsigned long flags;
   
    spin_lock_irqsave(&q->lock, flags);
    __remove_wait_queue(q, wait);
    spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL(remove_wait_queue);


等待事件:
wait_event(queue, condition);
wait_event_interruptible(queue, condition);
wait_event_timeout(queue, condition, timeout);
wait_event_interruptible_timeout(queue, condition, timeout);

wait_event_xxx函数第一个参数是等待队列头,第二个参数condition必须满足,否则阻塞。wait_event()和wait_event_interruptible()区别在于后则可以被信号打断。timeout是阻塞等待的超时时间,以jiffy为单位,在第三个参数的timeout到达时,不论condition是否满足,均返回。

举例来看 wait_event_interruptible()
linux+v2.6.28/include/linux/wait.h:

#define __wait_event_interruptible(wq, condition, ret) \
do {                                                   \
   DEFINE_WAIT(__wait);                                \
                                                       \
   for (;;) {                                          \
   prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);  \
   if (condition)//条件满足     返回                    \
   break;                                              \
   if (!signal_pending(current)) {                     \
   schedule();                                         \
   continue;                                           \
   }                                                   \
   ret = -ERESTARTSYS;                                 \
   break;                                              \
   }                                                   \
   finish_wait(&wq, &__wait);                          \
} while (0)


#define wait_event_interruptible(wq, condition)       \

 277({                                                \

 278        int __ret = 0;                            \

 279        if (!(condition))                         \

 280                __wait_event_interruptible(wq, condition, __ret);    \

 281        __ret;                                                       \

 282})


唤醒队列:
void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);
以上两个函数会唤醒以queue作为等待队列头的所用等待队列的进程.
/***********************************************************************/

poll函数使用来支持用户层的select函数调用.
select函数原型如下:
int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

其中readfsd, writefds, exceptfds分别是被select()监视的读、写、异常处理的文件描述符集合。numfds是需要检查的号码的最大的文件描述符加一。timeout参数是一个指向struct timeval结构体的指针,它可以使select()等待timeout时间后返回.
struct timeval定义如下:

struct timeval {
        int tv_sec;  //秒
        int tv_usec; //微秒
}
下面的宏用来设置、清除、判断文件描述符集合:
FD_ZERO(fd_set *set);  清除一个文件描述符集合

FD_SET(int fd, fd_set *set); 将一个文件描述符加入到文件描述符集中

FD_CLR(int fd, fd_set *set); 将一个文件描述符从文件描述符集中清除

FD_ISSET(int fd, fd_set *set); 判断文件描述符是否置位

设备驱动中的poll原型如下:
unsigned int (*poll)(struct file *filp, struct poll_table *wait);
第一个参数为file结构体指针,第二个参数为轮询表指针。
这个函数应该进行以下两项工作:
(1)对可能引起设备文件状态变化的等待队列调用poll_wait函数,将对应的等待队列头添加到poll_table。
(2)返回表示是否能对设备进行无阻塞读,写访问的掩码。
poll_table和poll_wait定义如下:

typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);
   
   typedef struct poll_table_struct {
    poll_queue_proc qproc;
  } poll_table;
   
  static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
    if (p && wait_address)
    p->qproc(filp, wait_address, p); //这里不知道怎么实现的,高手指点
}


poll返回设备资源的可获取状态:POLLIN, POLLOUT, POLLPRI, POLLERR, POLLNVAL等宏的位和或的结果.每个宏代表设备的一种状态.
设备驱动的poll()典型模板是:

static unsigned int lan_poll(struct file *filp, poll_table *wait)
{
    unsigned int mask = 0;
    struct xxx_dev_t *xxx_dev = filp->private_data;
    down(&xxx_dev->sem);
    poll_wait(filp, &xxx_dev->r_wait, wait);//加入读等待队列头以便轮询
    poll_wait(filp, &xxx_dev->w_wait, wait);//加入写等待队列头以便轮询
    if(...)
        mask |= POLLIN | POLLRDNORM; //表示数据可以被读取
    if(...)
        mask |= POLLOUT | POLLWRNORM; //表示数据可以被写入
    up(&xxx_dev->sem);         
    return mask;        
}


以上功能将在下面的字符设备中得到实现:

/*
 * globalmem设备,没有对应真实的硬件,主要用来学习Linux设备驱动开发。
 * (1) 使用信号量支持并发
 * (2) 使用等待队列支持阻塞
 * (3) 加入了poll函数支持用户下的select查询
 * 建议交流:lanpeng722@gmail.com
 */


#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>

#define LAN_SIZE    0x1000 //全局内存大小:4KB
#define MEM_CLEAR    0x1 //清除全局内存
#define LAN_MAJOR    244 //主设备号

static int lan_major = LAN_MAJOR;

struct lan_dev_t{
    struct cdev cdev;
    unsigned int current_len; //fifo 有效数据长度
    unsigned char lan_buf[LAN_SIZE];
    struct semaphore sem;    //信号量用于支持并发操作
    wait_queue_head_t r_wait; //阻塞读用的等待队列头
    wait_queue_head_t w_wait; //阻塞写用的等待队列头
};

struct lan_dev_t *lan_dev;

static int lan_open(struct inode *inode, struct file *filp)
{
    filp->private_data = lan_dev;
    printk("Open OK!\n");
    return 0;
}

static int lan_release(struct inode *inode, struct file *filp)
{
    printk("Close OK!\n");
    return 0;
}
static ssize_t lan_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
    unsigned long p = *ppos;
    size_t count = size;
    int ret = 0;
    struct lan_dev_t *lan_dev = filp->private_data;
/***************************************************************************/    
    if((filp->f_flags&O_NONBLOCK)){
        goto no_block;
    }    
    DECLARE_WAITQUEUE(wait, current); //定义等待队列
    down(&lan_dev->sem);
    add_wait_queue(&lan_dev->r_wait, &wait); //进入读等待队列

    while(lan_dev->current_len == 0)
    {
        __set_current_state(TASK_INTERRUPTIBLE); //改进程为睡眠状态
        up(&lan_dev->sem);        
        schedule(); //调度其他进程        
        if(signal_pending(current)){
            ret = -ERESTARTSYS;
            goto signal_come;
        }
        down(&lan_dev->sem);
    }
    if(size > lan_dev->current_len)
        size = lan_dev->current_len;
    if(copy_to_user(buf, lan_dev->lan_buf, size)){
        ret = -EFAULT;
        goto out;
    }else{
        memcpy(lan_dev->lan_buf, lan_dev->lan_buf+size, lan_dev->current_len-size);
        lan_dev->current_len -= size;
        wake_up_interruptible(&lan_dev->w_wait); //唤醒写等待进程
        ret = size;    
    }
out:
    up(&lan_dev->sem);
signal_come:
    remove_wait_queue(&lan_dev->w_wait, &wait);
    set_current_state(TASK_RUNNING);
    return ret;    
/**************************************************************************/    
no_block:
    if(p >= LAN_SIZE)
        return count? -ENXIO: 0;
    if(count > LAN_SIZE - p)
        count = LAN_SIZE - p;
    if(count > lan_dev->current_len)
        count = lan_dev->current_len;    
    down(&lan_dev->sem);    
    if(copy_to_user(buf, (void *)(lan_dev->lan_buf+p), count)){
        ret = -EFAULT;
    }else{
        *ppos += count;
        p = *ppos;
        if(lan_dev->current_len > 0){
            memcpy(lan_dev->lan_buf, lan_dev->lan_buf+count, lan_dev->current_len-count);
            lan_dev->current_len -= count;
        }
        ret = count;
        printk("Read %d byte from %ld\n", count, p);
    }
    up(&lan_dev->sem);
/****************************************************************************/    
    return ret;        
}
static ssize_t lan_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
    unsigned long p = *ppos;
    unsigned int count = size;
    int ret = 0;
    struct lan_dev_t *lan_dev = filp->private_data;
/****************************************************************************/
    if((filp->f_flags&O_NONBLOCK)){
        goto no_block;
    }
    
    DECLARE_WAITQUEUE(wait, current);
    down(&lan_dev->sem);
    add_wait_queue(&lan_dev->w_wait, &wait);
    
    while(lan_dev->current_len == LAN_SIZE)
    {
        __set_current_state(TASK_INTERRUPTIBLE);
        up(&lan_dev->sem);
        
        schedule();
        if(signal_pending(current)){
            ret = -EAGAIN;
            goto signal_come;
        }
        down(&lan_dev->sem);
    }    
    if(size > LAN_SIZE - lan_dev->current_len)
        size = LAN_SIZE - lan_dev->current_len;
    if(copy_from_user(lan_dev->lan_buf+lan_dev->current_len, buf, size)){
        ret = -EFAULT;
        goto out;
    }else{
        lan_dev->current_len += size;
        printk("lan_dev->current_len = %d\n", lan_dev->current_len);
        wake_up_interruptible(&lan_dev->r_wait);
        ret = size;
    }
out:
    up(&lan_dev->sem);
signal_come:
    remove_wait_queue(&lan_dev->w_wait, &wait);
    set_current_state(TASK_RUNNING);
    return ret;            
/****************************************************************************/
no_block:    
    if(p >= LAN_SIZE)
        return count? -ENXIO: 0;
    if(count > LAN_SIZE - p)
        count = LAN_SIZE - p;
    down(&lan_dev->sem);    
    if(copy_from_user(lan_dev->lan_buf + p, buf, count))
        ret = -EFAULT;
    else{
        *ppos += count;
        p = *ppos;
        lan_dev->current_len += count;
        ret = count;
        printk("Write %d byte from %ld\n", count, p);
    }
    up(&lan_dev->sem);
    return ret;    
}

static int lan_ioctl(struct inode *inodep, struct file *filp, unsigned int cmd, unsigned long arg)
{
    struct lan_dev_t *lan_dev = filp->private_data;
    switch(cmd)
    {
        case MEM_CLEAR:
            down(&lan_dev->sem);
            memset(lan_dev->lan_buf, 0, LAN_SIZE);
            lan_dev->current_len = 0;
            up(&lan_dev->sem);
            printk("Memset buf ok!\n");
            break;
        default:
            return -EINVAL;    
    }
    return 0;
}

static unsigned int lan_poll(struct file *filp, poll_table *wait)
{
    unsigned int mask = 0;
    struct lan_dev_t *lan_dev = filp->private_data;
    down(&lan_dev->sem);
    
    poll_wait(filp, &lan_dev->r_wait, wait);
    poll_wait(filp, &lan_dev->w_wait, wait);
    
    if(lan_dev->current_len != 0)
        mask |= POLLIN | POLLRDNORM; //表示数据可以被读取

    if(lan_dev->current_len != LAN_SIZE)
        mask |= POLLOUT | POLLWRNORM; //表示数据可以被写入

    up(&lan_dev->sem);            
    return mask;        
}
        
static struct file_operations lan_fops = {
    .owner = THIS_MODULE,
    .open = lan_open,
    //.llseek = lan_llseek,
    .read = lan_read,
    .write = lan_write,
    .ioctl = lan_ioctl,
    .poll = lan_poll,
    .release = lan_release,    
};

static void lan_setup_cdev(struct lan_dev_t *lan_dev, int index)
{
    int err, devno;
    devno = MKDEV(lan_major, index);
    cdev_init(&lan_dev->cdev, &lan_fops);
    err = cdev_add(&lan_dev->cdev, devno, 1);
    if(err)
        printk(KERN_NOTICE"Error %d adding cdev!\n", err);    
}

static int __init lancdev_init(void)
{
    int ret;
    dev_t devno = MKDEV(lan_major, 0);
    ret = register_chrdev_region(devno, 1, "globalmem");
    if(ret < 0)
        return ret;
    lan_dev = kmalloc(sizeof(struct lan_dev_t), GFP_KERNEL);
    if(!lan_dev){
        ret = -ENOMEM;
        printk("kmalloc Error!\n");
        return ret;
    }
    memset(lan_dev, 0, sizeof(struct lan_dev_t));    
    lan_setup_cdev(lan_dev, MINOR(devno));
    init_MUTEX(&lan_dev->sem);
    init_waitqueue_head(&lan_dev->r_wait);
    init_waitqueue_head(&lan_dev->w_wait);
    return 0;            
}
static void __exit lancdev_exit(void)
{
    cdev_del(&lan_dev->cdev);
    kfree(lan_dev);
    unregister_chrdev_region(MKDEV(lan_major, 0), 1);    
}

MODULE_AUTHOR("LanPeng");
MODULE_LICENSE("GPL");

module_init(lancdev_init);
module_exit(lancdev_exit);


用户测试程序select测试:

#include

#include

#include

#include

#include

#include

int main(int argc, char** argv)
{
    int fd;
    fd_set rfds, wfds;
    fd = open("/dev/globalmem_lan", O_RDWR);
    if(fd < 0){
        printf("Open Error!\n");
        return -1;
    }
    if(ioctl(fd, 0x01, 0) < 0){
        printf("ioctl Error!\n");
        return -1;
    }
    while(1){
        FD_ZERO(&rfds);
        FD_ZERO(&wfds);
        FD_SET(fd, &rfds);
        FD_SET(fd, &wfds);
        
        select(fd + 1, &rfds, &wfds, NULL, NULL);
        if(FD_ISSET(fd, &rfds)){
            printf("Poll monitor : can be read!\n");
        }
        if(FD_ISSET(fd, &wfds)){
            printf("Poll monitor: can be write!\n");
        }
    }
    return 0;
}


阅读(1170) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~