LDD第二章到第六章的综合复习。
用循环缓冲区实现一个FIFO,支持多个reader和writer,利用信号量在竞态下保护数据区域,并且在无数据的时候阻塞读,数据满的时候阻塞写,可以通过ioctl返回FIFO状态。
需要的技术:
1、信号量:竞争与锁的机制。
2、等待队列:进程的休眠与唤醒。用两种方式实现读写阻塞。
3、poll:返回文件可读或者可写的状态,为select调用。
4、ioctl:返回或者传入特定结构体,在本例中为FIFO的状态。
其实无论驱动程序逻辑多么复杂,基本的框架都是一些操作函数的实现,一个字符设备的大致框架如下:
将cdev嵌入自己的结构体里,可以支持更多的属性,而不是利用全局变量(等待队列、信号量等)进行管理
(一)初始化工作
1、请求设备号(指定或动态分配主设备号,请求子设备号)
2、分配结构体内存并初始化内部关键变量(申请buffer,初始化信号量等)
3、注册设备,步骤cdev_alloc --> cdev_init --> cdev_add,其中我们的字符设备是嵌套在fifo struct里面的,所以alloc工作在第二步已经做好。
(二)清理工作
1、注销设备,cdev_del
2、释放fifo struct内存,包括申请的buffer内存
3、回收设备号
执行完rmmod之后可以检查cat /proc/devices是否将我们申请的设备号释放完毕
(三)文件操作
1、open:包括必要的初始化和统计工作,以及与权限相关的操作
2、release:资源回收
3、read&write:对于数据的操作,在普通文件中主要是数据的读写,f_pos的移动;fifo中不支持lseek,但是需要在特定条件下阻塞读和写,在满足条件时唤醒,并且维护wp和rp以保证循环buffer的正常。
4、ioctl:用于对设备的控制,返回设备的状态,比如fifo当前的使用情况,或者是设备中每个变量的状态,如天线数据的子帧号等;也可以用作小数据量的传输。
5、mmap:用于用户空间和设备之间的内存映射。
6、poll:用于返回可读写状态等;
7、llseek:用于移动当前读写位置。
(四)调试支持
/proc文件,可以用来传输小数据量的数据,辅助调试。利用proc文件传输大量数据可能会导致设备驱动程序组织的比较乱,不建议使用。
代码还有bug,write函数无法写入,暂时先贴在下面,请各位高手指点一下小弟~
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/fs.h> // struct file and file_operations
#include <linux/types.h> // typedef dev_t, etc.
#include <linux/kdev_t.h>
#include <linux/poll.h> // interface poll_wait...
#include <linux/kernel.h> // printk(), min()
#include <linux/errno.h> // error codes
#include <linux/cdev.h> // cdev_add()
#include <linux/ioctl.h> // _IOW,etc.
#include <linux/slab.h> // kmalloc()
#include <linux/fcntl.h>
#include <linux/sched.h> // schedule() and TASK_*
#include <asm/uaccess.h> // interface copy_from_user and copy to user
#include <asm/system.h> // cli(), *_flags
MODULE_LICENSE("GPL");
struct cdev_fifo
{
wait_queue_head_t inq, outq; // read/write wait queue
char *buffer, *end; // loop buffer begin and end
int bufsize; // loop buffer size
char *rp, *wp; // read/write position
int nreaders, nwriters; // readers/writers counter
struct fasync_struct *async_queue;
struct semaphore sem_buf; // mutex
struct cdev cdev; // char device
};
struct cdev_info
{
int bufsize;
int nreaders;
int nwriters;
int spacefreesize;
};
#define CDEV_IOC_MAGIC 'k'
#define CDEV_IOC_MAXNR 2
#define CDEV_IOC_GETINFO _IOWR(CDEV_IOC_MAGIC, 0, struct cdev_info)
static int cdev_fifo_major = 0;
static int cdev_fifo_minor = 0;
static int cdev_fifo_num = 1;
static int cdev_fifo_size = 4000;
module_param(cdev_fifo_major, int, 0); //insmod xxx.ko cdev_fifo_major = 254
module_param(cdev_fifo_minor, int, 0);
module_param(cdev_fifo_num, int, 0);
module_param(cdev_fifo_size, int, 0);
static struct cdev_fifo *cdev_fifo_devices;
static ssize_t cdev_open(struct inode *, struct file *);
static ssize_t cdev_release(struct inode *, struct file *);
static ssize_t cdev_read(struct file *, char *, size_t, loff_t*);
static ssize_t cdev_write(struct file *, const char *, size_t, loff_t*);
//no use of llseek static loff_t cdev_llseek(struct file *, loff_t, int);
static unsigned int cdev_poll(struct file *, poll_table *);
static int cdev_ioctl(struct inode *, struct file *, unsigned int, unsigned long);
static int cdev_fasync(int, struct file *, int);
struct file_operations cdev_fops = {
.owner = THIS_MODULE,
.open = cdev_open,
.read = cdev_read,
.write = cdev_write,
.llseek = no_llseek,
.ioctl = cdev_ioctl,
.poll = cdev_poll,
.fasync = cdev_fasync,
.release = cdev_release,
};
static void cdev_fifo_setup_cdev(struct cdev_fifo *dev, int index)
{
int err, devno = MKDEV(cdev_fifo_major, cdev_fifo_minor+index);
// register char device
// cdev_alloc --> cdev_init --> cdev_add
cdev_init(&dev->cdev, &cdev_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &cdev_fops;
err = cdev_add(&dev->cdev, devno, 1);
if (err)
printk("Error %d adding cdev_fifo %d.\n", err, index);
}
static int __init cdev_module_init(void)
{
int result, i;
dev_t dev = 0;
// ask a range of minor numbers to work with
if (cdev_fifo_major) // for certain major number
{
dev = MKDEV(cdev_fifo_major, cdev_fifo_minor);
result = register_chrdev_region(dev, cdev_fifo_num, "cdev_fifo");
}
else // for dynamic allocated major number
{
// allocate dev major
result = alloc_chrdev_region(&dev, cdev_fifo_minor /*dev minor*/, cdev_fifo_num /*count*/, "cdev_fifo");
cdev_fifo_major = MAJOR(dev);
}
if (result < 0)
{
printk("cdev_fifo can't get major %d\n", cdev_fifo_major);
return result;
}
// allocate fifo devices according to the number when module loaded
cdev_fifo_devices = kmalloc(cdev_fifo_num * sizeof(struct cdev_fifo), GFP_KERNEL);
if (!cdev_fifo_devices) // allocate failure
{
unregister_chrdev_region(dev, cdev_fifo_num);
result = -ENOMEM;
return result;
}
printk("kmalloc struct success.\n");
// initialize device struct variables
memset(cdev_fifo_devices, 0, cdev_fifo_num * sizeof(struct cdev_fifo));
for (i = 0; i < cdev_fifo_num; ++i)
{
init_waitqueue_head(&(cdev_fifo_devices[i].inq));
init_waitqueue_head(&(cdev_fifo_devices[i].outq));
sema_init(&(cdev_fifo_devices[i].sem_buf), 1);
cdev_fifo_setup_cdev(cdev_fifo_devices+i, i);
}
printk("cdev_fifo register success.\n");
return 0;
}
static void __exit cdev_module_exit(void)
{
int i;
dev_t devno = MKDEV(cdev_fifo_major, cdev_fifo_minor);
if (cdev_fifo_devices)
{
for (i = 0; i < cdev_fifo_num; ++i)
{
cdev_del(&(cdev_fifo_devices[i].cdev)); // remove devices
kfree(cdev_fifo_devices[i].buffer);
}
kfree(cdev_fifo_devices); // free memory
cdev_fifo_devices = NULL;
}
unregister_chrdev_region(devno, cdev_fifo_num); // free device number
printk("cdev_fifo unregister success.\n");
}
/*
* file management: open and close
*/
static int cdev_open(struct inode *inode, struct file *filp)
{
struct cdev_fifo *dev;
dev = container_of(inode->i_cdev, struct cdev_fifo, cdev);
filp->private_data = dev; //for read, write...
if (down_interruptible(&dev->sem_buf))
return -ERESTARTSYS;
if (!dev->buffer)
{
// allocate the buffer
dev->buffer = kmalloc(cdev_fifo_size, GFP_KERNEL);
if (!dev->buffer)
{
up(&dev->sem_buf);
return -ENOMEM;
}
}
dev->bufsize = cdev_fifo_size;
dev->end = dev->buffer + dev->bufsize;
dev->rp = dev->wp = dev->buffer; // rd and wr from the beginning
// count readers and writers
if (filp->f_mode & FMODE_READ)
dev->nreaders++;
if (filp->f_mode & FMODE_WRITE)
dev->nwriters++;
up(&dev->sem_buf);
printk("\"%s\" open file successful\n", current->comm);
return nonseekable_open(inode, filp);
}
static int cdev_release(struct inode *inode, struct file *filp)
{
struct cdev_fifo *dev = filp->private_data;
cdev_fasync(-1, filp, 0);
if (down_interruptible(&dev->sem_buf))
return -ERESTARTSYS;
if (filp->f_mode & FMODE_READ)
dev->nreaders--;
if (filp->f_mode & FMODE_WRITE)
dev->nwriters--;
if (dev->nreaders + dev->nwriters == 0)
{
kfree(dev->buffer); // free memory
dev->buffer = NULL;
}
up(&dev->sem_buf);
return 0;
}
/*
* data management: read and write
*/
static ssize_t cdev_read(struct file *filp, char *buf, size_t len, loff_t *f_pos)
{
struct cdev_fifo *dev = filp->private_data;
if (down_interruptible(&dev->sem_buf))
return -ERESTARTSYS; // interrupted
while (dev->rp == dev->wp) // fifo empty, read nothing
{
up(&dev->sem_buf); // release lock
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
printk("\"%s\" reading: going to sleep\n", current->comm);
if (wait_event_interruptible(dev->inq, (dev->rp != dev->wp)))
return -ERESTARTSYS; // signal: tell fs layer handle it
if (down_interruptible(&dev->sem_buf))
return -ERESTARTSYS;
}
// fifo is not empty, return data
if (dev->wp > dev->rp)
len = min(len, (size_t)(dev->wp - dev->rp));
else // wp rrapped, return end-rp
len = min(len, (size_t)(dev->end - dev->rp));
if (copy_to_user(buf, dev->rp, len))
{
up(&dev->sem_buf);
return -EFAULT;
}
dev->rp += len;
if (dev->rp == dev->end)
dev->rp = dev->buffer;
up(&dev->sem_buf);
// wake up writers
wake_up_interruptible(&dev->outq);
printk("\"%s\" did read %li bytes\n", current->comm, (long)len);
return len;
}
// return free space size
static int spacefree(struct cdev_fifo *dev)
{
if (dev->rp == dev->wp)
{
return dev->bufsize - 1;
}
return ((dev->rp + dev->bufsize - dev->wp)) - 1;
}
// wait for free space for writing
static int cdev_getwritespace(struct cdev_fifo *dev, struct file *filp)
{
while (spacefree(dev) == 0) // fifo full
{
DEFINE_WAIT(wait);
up(&dev->sem_buf);
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
printk("\"%s\" writing: going to sleep\n", current->comm);
prepare_to_wait(&dev->outq, &wait, TASK_INTERRUPTIBLE);
if (spacefree(dev) == 0) // check condition!!!
schedule();
finish_wait(&dev->outq, &wait);
if (signal_pending(current))
return -ERESTARTSYS; //signal wake up
if (down_interruptible(&dev->sem_buf))
return -ERESTARTSYS;
}
return 0;
}
static ssize_t cdev_write(struct file *filp, const char *buf, size_t len, loff_t *f_pos)
{
struct cdev_fifo *dev = filp->private_data;
int result;
printk("request semaphore\n");
if (down_interruptible(&dev->sem_buf))
return -ERESTARTSYS;
printk("get semaphore, check space.\n");
result = cdev_getwritespace(dev, filp);
if (result)
return result;
printk("write space free.\n");
// space is free, begin to write
len = min(len, (size_t)spacefree(dev));
if (dev->wp >= dev->rp)
len = min(len, (size_t)(dev->end - dev->wp));
else
len = min(len, (size_t)(dev->rp - dev->wp -1));
printk("Going to accept %li bytes to %p from %p\n", (long)len, dev->wp, buf);
if (copy_from_user(dev->wp, buf, len))
{
up(&dev->sem_buf);
return -EFAULT;
}
dev->wp += len;
if (dev->wp == dev->end)
dev->wp = dev->buffer; //wp wrapped
up(&dev->sem_buf);
wake_up_interruptible(&dev->inq); //wake up read and select
if (dev->async_queue)
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
printk("\"%s\" did write %li bytes.\n", current->comm, (long)len);
return sizeof(int);
}
static int cdev_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
int retval = 0;
struct cdev_info curinfo;
struct cdev_fifo *dev = filp->private_data;
if (_IOC_TYPE(cmd) != CDEV_IOC_MAGIC)
return -ENOTTY;
if (_IOC_NR(cmd) > CDEV_IOC_MAXNR)
return -ENOTTY;
switch (cmd)
{
case CDEV_IOC_GETINFO:
curinfo.bufsize = dev->bufsize;
curinfo.nreaders = dev->nreaders;
curinfo.nwriters = dev->nwriters;
curinfo.spacefreesize = spacefree(dev);
if (copy_to_user((unsigned char __user *)arg, &curinfo, sizeof(curinfo)))
{
printk("cdev_fifo ioctl error: copy_to_user\n");
retval = -EFAULT;
}
break;
default:
return -ENOTTY;
}
return retval;
}
static unsigned int cdev_poll(struct file *filp, poll_table *wait)
{
struct cdev_fifo *dev = filp->private_data;
unsigned int mask = 0;
if (down_interruptible(&dev->sem_buf))
return -ERESTARTSYS;
poll_wait(filp, &dev->inq, wait);
poll_wait(filp, &dev->outq, wait);
if (dev->rp != dev->wp)
mask |= POLLIN | POLLRDNORM; // readable
if (spacefree(dev))
mask |= POLLOUT | POLLWRNORM; // writable
up(&dev->sem_buf);
return mask;
}
static int cdev_fasync(int fd, struct file *filp, int mode)
{
struct cdev_fifo *dev = filp->private_data;
return fasync_helper(fd, filp, mode, &dev->async_queue);
}
module_init(cdev_module_init);
module_exit(cdev_module_exit);
测试程序:
测试读
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
char buf[256];
int readlen = 0, retlen;
FILE* fd;
fd = fopen("/dev/fifocdev", "r");
if (fd != -1)
{
while(1)
{
printf("enter read len=");
scanf("%d",&readlen);
retlen = fread((void*)buf, sizeof(char), readlen, fd);
buf[retlen] = '\0';
printf("read size= %d, content=%s.\n", retlen, buf);
}
fclose(fd);
}
else
printf("open error.\n");
return 0;
}
测试写
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
char buf[256];
int readlen = 0, retlen;
FILE* fd;
memset(buf,0,sizeof(buf));
fd = fopen("/dev/fifocdev", "w");
if (fd != -1)
{
while(1)
{
printf("enter write string=");
scanf("%s",(char*)&buf);
printf("strlen=%d\n",strlen(buf));
retlen = fwrite((char*)&buf, sizeof(char), strlen(buf)+1, fd);
printf("write size= %d, content=%s.\n", strlen(buf)+1, buf);
}
fclose(fd);
}
else
printf("open error.\n");
return 0;
}
ioctl
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#define DEVICE_FILENAME "/dev/fifocdev"
struct cdev_info
{
int bufsize;
int nreaders;
int nwriters;
int spacefreesize;
};
#define CDEV_IOC_MAGIC 'k'
#define CDEV_IOC_GETINFO _IOWR(CDEV_IOC_MAGIC, 0, struct cdev_info)
int main()
{
struct cdev_info info;
int dev;
dev = open(DEVICE_FILENAME, O_RDWR | O_NDELAY);
if (dev >= 0)
{
printf("open dev success.\n");
ioctl(dev, CDEV_IOC_GETINFO, &info);
printf("bufsize=%d.\n", info.bufsize);
printf("nreaders=%d.\n", info.nreaders);
printf("nwriters=%d.\n", info.nwriters);
printf("spacefreesize=%d.\n", info.spacefreesize);
close(dev);
}
else
printf("open device error.\n");
return 0;
}
可以用ioctl和dmesg辅助调试。
在insmod之后,根据/proc/devices里面的主次设备号建立字符设备文件
sudo mknod c 251 0
问题:
直接运行./testwritefifo,会出现段错误,fwrite的地方报错
利用sudo ./testwritefifo,返回写入成功,但是dmesg并没有看到pringk 出来的写入操作(没进去write函数),ioctl也发现FIFO的freesize并没有变化,但是nwriter确实是+1了,说明测试写FIFO程序打开文件成功了。(从dmesg也可以看到writefifo open file successful)。