宋寶華 《Linux設備驅動開發詳解》示例代碼之fifo字符設備驅動

驅動代碼如下:

scull.c

#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/gfp.h>
#include <linux/poll.h>
#include <asm/uaccess.h>

#define	SCULL_MAJOR	252
#define SCULL_NAME	"scull"
#define MAX_DATA	0x10

static	int scull_major = SCULL_MAJOR;
struct scull_dev {
	struct cdev cdev;
	unsigned char data[MAX_DATA];
	struct semaphore sem;
	unsigned int current_len;
	wait_queue_head_t r_wait;
	wait_queue_head_t w_wait;
};

MODULE_LICENSE("GPL");
MODULE_AUTHOR("BG2BKK");

struct	scull_dev *scull_devp;

int	scull_open(struct inode *inode, struct file *filp)
{
	struct scull_dev *dev = container_of(inode->i_cdev, struct scull_dev, cdev);
	filp->private_data = dev;
	printk(KERN_ALERT "open the scull device\n");
	return 0;
}

int	scull_release(struct inode *inode, struct file *filp)
{
	printk(KERN_ALERT "close the scull device\n");
	return 0;
}

ssize_t	scull_read(struct file *filp, char __user *buf, size_t count, loff_t *f_ops)
{
	struct scull_dev *dev = filp->private_data;
	int ret = 0;
	DECLARE_WAITQUEUE(wait, current);

	down(&dev->sem);
	add_wait_queue(&dev->r_wait, &wait);
	while(dev->current_len == 0)
	{
		if(filp->f_flags & O_NONBLOCK)
		{
			ret = -EAGAIN;
			goto out;
		}
		__set_current_state(TASK_INTERRUPTIBLE);
		up(&dev->sem);
		schedule();
		if(signal_pending(current)){
			ret = -ERESTARTSYS;
			goto out2;
		}
		down(&dev->sem);
	}
	if(count > dev->current_len)
		count = dev->current_len;
	if(copy_to_user(buf, dev->data,count)){
		ret = -EFAULT;
		goto out;
	} else {
		memcpy(dev->data, dev->data + count, dev->current_len - count);
		dev->current_len -= count;
		printk(KERN_ALERT "read %u bytes; current_len:%d\n",count, dev->current_len);
		wake_up_interruptible(&dev->w_wait);
		ret = count;
	}
out:
	up(&dev->sem);
out2:
	remove_wait_queue(&dev->w_wait, &wait);
	set_current_state(TASK_RUNNING);
return ret;
}

ssize_t scull_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_ops)
{
	struct scull_dev *dev = filp->private_data;
	int ret = 0;
	DECLARE_WAITQUEUE(wait, current);
	down(&dev->sem);
	add_wait_queue(&dev->w_wait, &wait);
	while(dev->current_len == MAX_DATA){
		if(filp->f_flags & O_NONBLOCK){
			ret = -EAGAIN;
			goto out;
		}
		__set_current_state(TASK_INTERRUPTIBLE);
		up(&dev->sem);
		schedule();
		if(signal_pending(current)){
			ret = -ERESTARTSYS;
			goto out2;
		}
		down(&dev->sem);
	}

	if(count > MAX_DATA - dev->current_len){
		count = MAX_DATA - dev->current_len;
	}
	if(copy_from_user(dev->data + dev->current_len, buf, count)){
		ret = -EFAULT;
		goto out;
	}
	else {
		dev->current_len += count;
		printk(KERN_ALERT "written %d bytes, current_len:%d\n",count, dev->current_len);
		wake_up_interruptible(&dev->r_wait);
		ret = count;
	}
out:
	up(&dev->sem);
out2:
	remove_wait_queue(&dev->w_wait, &wait);
	set_current_state(TASK_RUNNING);
return ret;
}

static	unsigned int scull_poll(struct file *filp, poll_table *wait)
{
	unsigned int mask = 0;
	struct scull_dev *dev = filp->private_data;
	down(&dev->sem);
	
	poll_wait(filp, &dev->r_wait, wait);
	poll_wait(filp, &dev->w_wait, wait);

	if(dev->current_len != 0)
		mask |= POLLIN | POLLRDNORM;
	if(dev->current_len != MAX_DATA)
		mask |= POLLOUT | POLLWRNORM;
	up(&dev->sem);
	return mask;
}

static const struct	file_operations scull_fops = {
	.owner	=	THIS_MODULE,
	.open	=	scull_open,
	.release=	scull_release,
	.write	=	scull_write,
	.read	=	scull_read,
	.poll	=	scull_poll,
};

static	void	scull_setup_cdev(struct scull_dev *dev, int index)
{
	int err, devno = MKDEV(scull_major, index);
	cdev_init(&dev->cdev, &scull_fops);
	dev->cdev.owner = THIS_MODULE;
	err = cdev_add(&dev->cdev, devno, 1);
	if(err)
		printk(KERN_NOTICE "ERROR %d adding scull_dev %d", err, index);
}

int	scull_init(void)
{
	int result;
	dev_t	devno = MKDEV(scull_major,0);
	if(scull_major)
		result = register_chrdev_region(devno, 1, "scull");
	else {
		result = alloc_chrdev_region(&devno, 0, 1, "scull");
		scull_major = MAJOR(devno);
	}
	if(result < 0)
		return result;
	
	scull_devp = kmalloc(sizeof(struct scull_dev), GFP_KERNEL);
	if(!scull_devp){
		result = -ENOMEM;
		goto fail_malloc;
	}
	memset(scull_devp, 0 , sizeof(struct scull_dev));
	scull_setup_cdev(scull_devp, 0);

	init_MUTEX(&scull_devp->sem);
	init_waitqueue_head(&scull_devp->r_wait);
	init_waitqueue_head(&scull_devp->w_wait);

	printk(KERN_ALERT "init scull device\n");
	return 0;
fail_malloc:
	unregister_chrdev_region(devno, 1);
	return result;
}

void	scull_cleanup(void)
{
	cdev_del(&scull_devp->cdev);
	kfree(scull_devp);
	unregister_chrdev_region(MKDEV(scull_major, 0), 1);
	printk(KERN_ALERT "clean scull device\n");	
}

module_init(scull_init);
module_exit(scull_cleanup);
測試代碼

/*************************************************************************
 *fileName:    test.c
 *description: test the myscull.c
 *author:      Hzc
 *create time: 2007-04-20
 *modify info: -
*************************************************************************/
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>

#define BUFFER_LEN 20
#define FIFO_CLEAR 0x01
#define device	"/dev/scull"

int main()
{
	int num;
	fd_set rfds, wfds;
	int fd = open(device, O_RDONLY| O_NONBLOCK);
	if(fd != 0){
		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 written\n");
		}
	} else{
		printf("Device " device "open failer");
	}
}

Makefile

KERNEL_DIR := /lib/modules/$(shell uname -r)/build
PWD	:= $(shell pwd)

obj-m := scull.o
default:
	$(MAKE) -C $(KERNEL_DIR) SUBDIRS=$(PWD) modules

test: test.c
	gcc $< -o [email protected]  -g

clean:
	rm -rf *.o *.ko *~ *.order *.symvers *.markers *.mod.c

測試腳本

1 load_scull.sh

#!/bin/sh

/sbin/insmod scull.ko
mknod /dev/scull c 252 0

2 unload_scull.sh


#!/bin/sh

/sbin/rmmod scull.ko
rm /dev/scull -f

3 test.sh

#!/bin/sh
make clean
make 
make test
sudo ./unload_scull
sudo ./load_scull
sudo ./test.o

測試時,執行./test.o,終端一直輸出poll monitor: can be write,

在另一終端,以根用戶模式,echo hello > /dev/scull,可以看到輸出poll monitor: can be read和poll monitor: can be write

多echo hello > /dev/scull幾次後,由於scull設備緩衝區僅16B,不再能寫入,此時只輸出poll monitor: can be read


發佈了51 篇原創文章 · 獲贊 23 · 訪問量 19萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章