一个驱动当它无法立刻满足请求应当如何响应? 一个对 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 16
struct 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
", 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
", 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
", 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
", 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
");
printk("val:%d, size: %d, str: %s
", devp->data.val, devp->data.size, devp->data.str);
break;
case FELLOW_MISC_IOC_SET:
printk("FELLOW_MISC_IOC_SET
");
ret = copy_from_user((unsigned char*)&(devp->data), (unsigned char *)arg, sizeof(struct miscdata));
printk("set val:%d, size: %d, str: %s
", devp->data.val, devp->data.size, devp->data.str);
break;
case FELLOW_MISC_IOC_GET:
printk("FELLOW_MISC_IOC_GET
");
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
");
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!
");
int n = 0;
while (n++ < 5)
{
memset(readdata, 0, sizeof(readdata));
if ((ret = read(fd, readdata, sizeof(readdata)))< 0)
{
printf("read fail:%s
", strerror(errno));
}
else
{
printf("readdata:%s, ret= %d
", 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!
");
int n = 0;
while (n++ <5)
{
if ((ret = write(fd, writedata, sizeof(writedata)))< 0)
{
printf("write fail:%s
", strerror(errno));
}
else
{
printf("writedata:%s, ret: %d
", 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
", 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;
}