一个驱动当它无法立刻满足请求应当如何响应? 一个对 read 的调用可能当没有数据时到来, 而以后会期待更多的数据. 或者一个进程可能试图写, 但是你的设备没有准备好接受数据, 因为你的输出缓冲满了. 调用进程往往不关心这种问题; 程序员只希望调用 read 或 write 并且使调用返回, 在必要的工作已完成后. 这样, 在这样的情形中, 你的驱动应当(缺省地)阻塞进程, 使它进入睡眠直到请求可继续。
有时还有调用进程通知你他不想阻塞, 不管它的 I/O 是否继续. 明确的非阻塞 I/O 由filp->f_flags 中的 O_NONBLOCK 标志来指示. 这个标志定义于 <linux/fcntl.h>, 被 <linux/fs.h>自动包含。如果指定 O_NONBLOCK, read 和 write 的行为是不同的. 在这个情况下, 这个调用简单地返回 -EAGAIN(("try it agin")如果一个进程当没有数据可用时调用 read , 或者如果当缓冲中没有空间时它调用 write . 如你可能期望的, 非阻塞操作立刻返回, 允许这个应用程序轮询数据.
fellowmisc.c
#include <linux/module.h>
#include <linux/init.h>#include <linux/types.h>#include <linux/miscdevice.h>#include <linux/fs.h>#include <linux/errno.h>#include <linux/mm.h>#include <linux/slab.h>#include <asm/io.h>#include <asm/uaccess.h>#include <linux/wait.h>#include <linux/semaphore.h>#include <linux/sched.h>#include "fellowmisc.h"#define BUFF_SIZE 16struct fellowmisc_dev{ struct miscdevice misc; struct miscdata data; char *buffer;//环形buffer int wp; int rp; int buffer_size; int free; wait_queue_head_t inq,outq; struct semaphore sem;};struct fellowmisc_dev *fellowmisc_devp;int fellowmisc_open(struct inode *inode, struct file *filep){ filep->private_data = fellowmisc_devp; return 0;}int fellowmisc_release(struct inode *inode, struct file *filep){ return 0;}ssize_t fellowmisc_read(struct file *filep, char __user *buf, size_t count, loff_t *f_pos){ size_t read_count = 0; struct fellowmisc_dev *devp = (struct fellowmisc_dev*)filep->private_data; char *tmpbuffer = kzalloc(count, GFP_KERNEL); if (down_interruptible(&(devp->sem))) return -ERESTARTSYS; while (devp->free == devp->buffer_size)//当buffer是空的时,没数据可读 { up(&(devp->sem)); if (filep->f_flags & O_NONBLOCK)//如果是非阻塞,返回EAGAIN return -EAGAIN; printk("%s reading: going to sleep\n", current->comm); if (wait_event_interruptible(devp->inq, (devp->free != devp->buffer_size)))//如果是阻塞,则将进程放到等待队列睡眠,等到有数据写唤醒。 return -ERESTARTSYS; if (down_interruptible(&(devp->sem))) return -ERESTARTSYS; } read_count = count < (devp->buffer_size - devp->free) ? count : (devp->buffer_size - devp->free); if (devp->wp > devp->rp) strncpy(tmpbuffer, devp->buffer + devp->rp, read_count); else { strncpy(tmpbuffer, devp->buffer + devp->rp, devp->buffer_size - devp->rp); strncpy(tmpbuffer + devp->buffer_size - devp->rp, devp->buffer, read_count - (devp->buffer_size - devp->rp)); } if (copy_to_user(buf, tmpbuffer,read_count)) { up(&(devp->sem)); return -EFAULT; } devp->rp += read_count; if (devp->rp >= devp->buffer_size) devp->rp -= devp->buffer_size; devp->free += read_count; up(&(devp->sem)); wake_up_interruptible(&(devp->outq)); printk("%s read %d bytes\n", current->comm, read_count); kfree(tmpbuffer); return read_count;}ssize_t fellowmisc_write(struct file *filep, const char __user *buf, size_t count, loff_t *f_pos)
{ size_t write_count; struct fellowmisc_dev *devp = (struct fellowmisc_dev*)filep->private_data; char *tmpbuffer = kzalloc(count, GFP_KERNEL); if (down_interruptible(&(devp->sem))) return -ERESTARTSYS; while (0 == devp->free)//如果buffer满了,则无法写入。 { up(&(devp->sem)); if (filep->f_flags & O_NONBLOCK) return -EAGAIN; printk("%s writing: going to sleep\n", current->comm); if (wait_event_interruptible(devp->outq, (0 != devp->free)))//如果是阻塞,则将进程放到等待队列睡眠,等到buffer有空间唤醒。 return -ERESTARTSYS; if (down_interruptible(&(devp->sem))) return -ERESTARTSYS; } write_count = count < devp->free ? count : devp->free; if (copy_from_user(tmpbuffer, buf, count)) { up(&(devp->sem)); return -EFAULT; } if (devp->wp + write_count < devp->buffer_size) strncpy(devp->buffer + devp->wp, tmpbuffer, write_count); else { strncpy(devp->buffer + devp->wp, tmpbuffer, devp->buffer_size - devp->wp); strncpy(devp->buffer, tmpbuffer + devp->buffer_size - devp->wp, write_count - (devp->buffer_size - devp->wp)); } devp->wp += write_count; if (devp->wp >= devp->buffer_size) devp->wp -= devp->buffer_size; devp->free -= write_count; up(&(devp->sem)); wake_up_interruptible(&(devp->inq)); printk("%s write %d bytes\n", current->comm, write_count); kfree(tmpbuffer); return write_count;}long fellowmisc_ioctl(struct file *filep,unsigned int cmd,unsigned long arg)
{
int ret = 0; struct fellowmisc_dev *devp = (struct fellowmisc_dev *)(filep->private_data); if (_IOC_TYPE(cmd) != FELLOW_MISC_IOC_MAGIC) return -EINVAL; if (_IOC_NR(cmd) > FELLOW_MISC_IOC_MAXNR) return -EINVAL; switch(cmd) { case FELLOW_MISC_IOC_PRINT: printk("FELLOW_MISC_IOC_PRINT\n"); printk("val:%d, size: %d, str: %s\n", devp->data.val, devp->data.size, devp->data.str); break; case FELLOW_MISC_IOC_SET: printk("FELLOW_MISC_IOC_SET\n"); ret = copy_from_user((unsigned char*)&(devp->data), (unsigned char *)arg, sizeof(struct miscdata)); printk("set val:%d, size: %d, str: %s\n", devp->data.val, devp->data.size, devp->data.str); break; case FELLOW_MISC_IOC_GET: printk("FELLOW_MISC_IOC_GET\n"); ret = copy_to_user((unsigned char*)arg,(unsigned char*)&(devp->data), sizeof(struct miscdata)); break; default: return -EINVAL;}
return ret;}static const struct file_operations fellowmisc_fops ={
.owner = THIS_MODULE, .open = fellowmisc_open, .release = fellowmisc_release, .unlocked_ioctl = fellowmisc_ioctl, .read = fellowmisc_read, .write = fellowmisc_write,};static struct miscdevice fellow_misc = { .minor = MISC_DYNAMIC_MINOR, .name = "fellowmisc", .fops = &fellowmisc_fops,};static int fellowmisc_init(void){ int ret = 0; printk("fellowmisc_init\n"); fellowmisc_devp = kmalloc(sizeof(struct fellowmisc_dev), GFP_KERNEL); if (!fellowmisc_devp) { ret = -ENOMEM; goto fail; } memset(&(fellowmisc_devp->data), 0, sizeof(fellowmisc_devp->data)); fellowmisc_devp->misc = fellow_misc; init_waitqueue_head(&(fellowmisc_devp->inq)); init_waitqueue_head(&(fellowmisc_devp->outq)); fellowmisc_devp->buffer = kzalloc(BUFF_SIZE, GFP_KERNEL); fellowmisc_devp->wp = fellowmisc_devp->rp = 0; fellowmisc_devp->buffer_size = fellowmisc_devp->free = BUFF_SIZE;sema_init(&(fellowmisc_devp->sem), 1);
return misc_register(&(fellowmisc_devp->misc));fail: return ret;}static void fellowmisc_exit(void)
{ misc_deregister(&(fellowmisc_devp->misc)); kfree(fellowmisc_devp->buffer); kfree(fellowmisc_devp);}MODULE_AUTHOR("fellow");
MODULE_LICENSE("GPL");module_init(fellowmisc_init);module_exit(fellowmisc_exit);
app.c
#include <stdio.h>
#include <unistd.h>#include <fcntl.h>#include <errno.h>#include <string.h>#include <pthread.h>#include "fellowmisc.h"void *read_thread(void *arg){ int fd = *(int *)arg; char readdata[8]; int ret = 0; printf("read thread!\n"); int n = 0; while (n++ < 5) { memset(readdata, 0, sizeof(readdata)); if ((ret = read(fd, readdata, sizeof(readdata)))< 0) { printf("read fail:%s\n", strerror(errno)); } else { printf("readdata:%s, ret= %d\n", readdata, ret); } sleep(1); } return NULL;}void *write_thread(void *arg){ int fd = *(int *)arg; char writedata[8] = "abcdefg"; int ret = 0; printf("write thread!\n"); int n = 0; while (n++ <5) { if ((ret = write(fd, writedata, sizeof(writedata)))< 0) { printf("write fail:%s\n", strerror(errno)); } else { printf("writedata:%s, ret: %d\n", writedata, ret); } sleep(1); } return NULL;}int main(int argc, char **argv){ int fd = -1; fd = open("/dev/fellowmisc", O_RDWR); if (fd < 0) { printf("open fail:%s\n", strerror(errno)); return -1; } if (argc == 2 && !strncmp(argv[1], "block", strlen("block"))) { printf("open with block"); } else { fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); } pthread_t tid_read; pthread_t tid_write; pthread_create(&tid_read, NULL, read_thread, &fd); pthread_create(&tid_write, NULL, write_thread, &fd); sleep(10); close(fd); return 0;}