Linux驱动学习(四)——高级字符设备驱动程序

8 篇文章 0 订阅

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)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值