参考:宋宝华,设备驱动开发详解 。ldd3
作者:me
内核2.6.35.22
gcc 4.4.5
编译模块和test程序,
insmod 模块 //lsmod //cat /dev/devices 验证加载的效果
mknod /dev/my_char c 258 0//创建设被节点
运行$echo dddff > /dev/my_char//每部运行使用dmesg看消息
运行$cat /dev/my_char 看效果
运行test程序
root@andy-virtual-machine:/home/xiao/four/new_char/R_W_L# cat test.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
int main (void)
{
int fd;
int ret;
char buf[] = "hello/n";
fd = open ("/dev/my_char", O_WRONLY|O_APPEND, 0777);
if (-1 == fd) {
perror ("open err:");
}
while (1)
{
static int count;
ret = write (fd, buf, sizeof(buf));
printf ("ok count %d/n",++count);
}
if (-1 == fd) {
perror ("write err:");
}
close (fd);
}
root@andy-virtual-machine:/home/xiao/four/new_char/R_W_L# vi test.c
root@andy-virtual-machine:/home/xiao/four/new_char/R_W_L# cat char.c
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/list.h>
#include <asm/uaccess.h>
#include <linux/mm.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/slab.h>
#define MAJOR_CHAR 258
#define SIZE_CHAR 1024
#define DBUG_ME 1
struct char_dev {
struct cdev cdev;
char mem[SIZE_CHAR];
};
struct char_dev *char_devp;
int char_major = MAJOR_CHAR;
MODULE_LICENSE ("GPL");
/* ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);*/
static ssize_t char_read (struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
int ret = 0;
loff_t p = filp->f_pos;
struct char_dev *dev = filp->private_data;
#if DBUG_ME
printk (KERN_INFO "in char_read,befor read ,and filp->f_pos = %llu,and ppos = %llu/n", filp->f_pos,*ppos);
#endif
if (count > SIZE_CHAR - p)
count = SIZE_CHAR - p;
if (copy_to_user (buf, (void *)(dev->mem + p), count))
ret = -EFAULT;
else {
ret = count ;
filp->f_pos += count;
*ppos += count;//read end of //if not have this read will all go on , never stop; 如果不加这句话,cat时后就一直读,用test程序里写的read也是一直读
#if DBUG_ME
printk (KERN_INFO "in char_read,after read ,and filp->f_pos = %llu , count = %d,and ppos = %llu/n", filp->f_pos, count,*ppos);
#endif
}
return ret;//没有实现,读走的功能,光是只读功能
}
static ssize_t char_write (struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
int ret = 0;
loff_t p = filp->f_pos;
struct char_dev *dev = filp->private_data;
#if DBUG_ME
printk (KERN_INFO "in char_write,befor write ,and filp->f_pos = %llu,and ppos = %llu/n", filp->f_pos, *ppos);
#endif
if (count > SIZE_CHAR - p)
count = SIZE_CHAR - p;
if (copy_from_user ((void *)(dev->mem + p), buf, count)) //extern inline long copy_from_user(void *to, const void __user *from, long n)
ret = -EFAULT;
else {
ret = count ;
filp->f_pos += count;
*ppos += count;//add for 如果没有这话,用test程序里的write循环写,一直覆盖以前的东西,有了后就是一次打开中不断的追加
#if DBUG_ME
printk (KERN_INFO "in char_write,after write ,and filp->f_pos = %llu , count = %d,and ppos = %llu/n", filp->f_pos, count, *ppos);
#endif
}
return ret;
//程序没有实现,关闭文件后再次打开时写也追加在文件后,看宋老师的程序,最后在struct char_dev里放了个unsigned long 的变量,用来记录现在文件的结尾标志,这个方法可以解决问题,但是不好,思考内核是如何实现的
}
static int char_open (struct inode *inode, struct file *filp)
{
struct char_dev *char_me_devp;
char_me_devp = container_of (inode->i_cdev, struct char_dev, cdev);
filp->private_data = char_me_devp;
#if DBUG_ME
printk (KERN_INFO "in char_open,and filp->f_pos = %llu/n", filp->f_pos);
#endif
return 0;
}
static int char_release (struct inode *inode, struct file *filp)
{
#if DBUG_ME
printk (KERN_INFO "in char_release/n");
#endif
return 0;
}
struct file_operations char_fops = {
.owner = THIS_MODULE,
.open = char_open,
.release = char_release,
.read = char_read,
.write = char_write,
};
static void char_setup_cdev (struct char_dev *dev, int index)
{
int ret;
dev_t devno = MKDEV (char_major, index);
cdev_init (&dev->cdev, &char_fops);
dev->cdev.owner = THIS_MODULE;
ret = cdev_add (&dev->cdev, devno, 1);
if (ret)
printk (KERN_NOTICE "error %d/n adding char_dev %d/n", ret, index);
}
static int char_me_init (void)
{
int ret;
dev_t devno = MKDEV (char_major, 0);
if (char_major)
ret = register_chrdev_region (devno, 1, "my_char");
else {
ret = alloc_chrdev_region (&devno, 0, 1, "my_char");
char_major = MAJOR (devno);
}
if (ret < 0)
return ret;
char_devp = (struct char_dev*)kmalloc (sizeof(struct char_dev), GFP_KERNEL);
if (NULL == char_devp) {
ret = -ENOMEM;
goto fail_kmalloc;
}
memset (char_devp, 0, sizeof(struct char_dev));
char_setup_cdev (char_devp, 0);
#if DBUG_ME
printk (KERN_INFO "in char_me_init/n");
#endif
return 0;
fail_kmalloc:
unregister_chrdev_region (devno, 1);
return ret;
}
static void char_me_exit (void)
{
dev_t devno = MKDEV (char_major, 0);
cdev_del (&char_devp->cdev);
kfree (char_devp);
unregister_chrdev_region (devno, 1);
#if DBUG_ME
printk (KERN_INFO "in char_me_exit/n");
#endif
}
module_init (char_me_init);
module_exit (char_me_exit);
加入lseek后的代码,依据实现的代码可以看出,filp->f_pos,是定位当前读写文件指针的
test代码ruxia
root@andy-virtual-machine:/home/xiao/four/new_char/race# cat test.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
int main (void)
{
int fd;
int ret;
char buf[] = "0";
fd = open ("/dev/my_char", O_RDWR|O_APPEND, 0777);
if (-1 == fd) {
perror ("open err:");
}
ret = read (fd, buf, 1);
printf ("ok count %s/n",buf);
sleep(1);
lseek (fd,1,SEEK_CUR);
ret = read (fd, buf, 1);
printf ("ok count %s/n",buf);
sleep(1);
/* while (1)
{
sleep (1);
static int count;
ret = write (fd, buf, sizeof(buf));
printf ("ok count %d/n",++count);
}*/
if (-1 == fd) {
perror ("write err:");
}
close (fd);
}
root@andy-virtual-machine:/home/xiao/four/new_char/race# cat char.c
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/list.h>
#include <asm/uaccess.h>
#include <linux/mm.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/slab.h>
#define MAJOR_CHAR 258
#define SIZE_CHAR 1024
#define DBUG_ME 1
struct char_dev {
struct cdev cdev;
char mem[SIZE_CHAR];
};
struct char_dev *char_devp;
int char_major = MAJOR_CHAR;
MODULE_LICENSE ("GPL");
/* ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);*/
static loff_t char_lseek (struct file *filp, loff_t offset, int orig)
{
struct char_dev *dev = filp->private_data;
int ret;
switch (orig) {
case SEEK_SET:
if ((offset < 0) || (((unsigned int) offset) > SIZE_CHAR)) {
ret = -EINVAL;
break;
}
filp->f_pos = (unsigned long)offset;
ret = filp->f_pos;
break;
case SEEK_CUR:
if (((filp->f_pos + offset) > SIZE_CHAR) || ((filp->f_pos + offset) < 0)) {
ret = -EINVAL;
break;
}
filp->f_pos += offset;
ret = filp->f_pos;
break;
}
return ret;
}
static ssize_t char_read (struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
int ret = 0;
loff_t p = *ppos;
struct char_dev *dev = filp->private_data;
if (p >= SIZE_CHAR) {
return 0;
}
#if DBUG_ME
printk (KERN_INFO "in char_read,befor read ,and filp->f_pos = %llu,and ppos = %llu/n", filp->f_pos,*ppos);
#endif
if (count > SIZE_CHAR - p)
count = SIZE_CHAR - p;
if (copy_to_user (buf, (void *)(dev->mem + p), count))
ret = -EFAULT;
else {
ret = count ;
filp->f_pos += count;
*ppos += count;//read end of //if not have this read will all go on , never stop;
#if DBUG_ME
printk (KERN_INFO "in char_read,after read ,and filp->f_pos = %llu , count = %d,and ppos = %llu/n", filp->f_pos, count,*ppos);
#endif
}
return ret;
}
static ssize_t char_write (struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
int ret = 0;
loff_t p = *ppos;
struct char_dev *dev = filp->private_data;
#if DBUG_ME
printk (KERN_INFO "in char_write,befor write ,and filp->f_pos = %llu,and ppos = %llu/n", filp->f_pos, *ppos);
#endif
if (count > SIZE_CHAR - p)
count = SIZE_CHAR - p;
if (copy_from_user ((void *)(dev->mem + p), buf, count)) //extern inline long copy_from_user(void *to, const void __user *from, long n)
ret = -EFAULT;
else {
ret = count ;
filp->f_pos += count;
*ppos += count;//add for
#if DBUG_ME
printk (KERN_INFO "in char_write,after write ,and filp->f_pos = %llu , count = %d,and ppos = %llu/n", filp->f_pos, count, *ppos);
#endif
}
return ret;
}
static int char_open (struct inode *inode, struct file *filp)
{
struct char_dev *char_me_devp;
char_me_devp = container_of (inode->i_cdev, struct char_dev, cdev);
filp->private_data = char_me_devp;
#if DBUG_ME
printk (KERN_INFO "in char_open,and filp->f_pos = %llu/n", filp->f_pos);
#endif
return 0;
}
static int char_release (struct inode *inode, struct file *filp)
{
#if DBUG_ME
printk (KERN_INFO "in char_release/n");
#endif
return 0;
}
struct file_operations char_fops = {
.owner = THIS_MODULE,
.open = char_open,
.release = char_release,
.read = char_read,
.write = char_write,
.llseek = char_lseek
};
static void char_setup_cdev (struct char_dev *dev, int index)
{
int ret;
dev_t devno = MKDEV (char_major, index);
cdev_init (&dev->cdev, &char_fops);
dev->cdev.owner = THIS_MODULE;
ret = cdev_add (&dev->cdev, devno, 1);
if (ret)
printk (KERN_NOTICE "error %d/n adding char_dev %d/n", ret, index);
}
static int char_me_init (void)
{
int ret;
dev_t devno = MKDEV (char_major, 0);
if (char_major)
ret = register_chrdev_region (devno, 1, "my_char");
else {
ret = alloc_chrdev_region (&devno, 0, 1, "my_char");
char_major = MAJOR (devno);
}
if (ret < 0)
return ret;
char_devp = (struct char_dev*)kmalloc (sizeof(struct char_dev), GFP_KERNEL);
if (NULL == char_devp) {
ret = -ENOMEM;
goto fail_kmalloc;
}
memset (char_devp, 0, sizeof(struct char_dev));
char_setup_cdev (char_devp, 0);
#if DBUG_ME
printk (KERN_INFO "in char_me_init/n");
#endif
return 0;
fail_kmalloc:
unregister_chrdev_region (devno, 1);
return ret;
}
static void char_me_exit (void)
{
dev_t devno = MKDEV (char_major, 0);
cdev_del (&char_devp->cdev);
kfree (char_devp);
unregister_chrdev_region (devno, 1);
#if DBUG_ME
printk (KERN_INFO "in char_me_exit/n");
#endif
}
module_init (char_me_init);
module_exit (char_me_exit);
宋老师代码/ *======================================================================
A globalfifo driver as an example of char device drivers
This example is to introduce asynchronous notifier
The initial developer of the original code is Baohua Song
<[email protected]>. All Rights Reserved.
======================================================================*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <linux/poll.h>
#define GLOBALFIFO_SIZE 0x1000 /*È«ŸÖfifo×îŽó4K×ÖœÚ*/
#define FIFO_CLEAR 0x1 /*Çå0È«ŸÖÄÚŽæµÄ³€¶È*/
#define GLOBALFIFO_MAJOR 250 /*Ô€ÉèµÄglobalfifoµÄÖ÷É豞ºÅ*/
static int globalfifo_fasync(int fd, struct file *filp, int mode); //add by lht
static int globalfifo_major = GLOBALFIFO_MAJOR;
/*globalfifoÉ豞œá¹¹Ìå*/
struct globalfifo_dev
{
struct cdev cdev; /*cdevœá¹¹Ìå*/
unsigned int current_len; /*fifoÓÐЧÊýŸÝ³€¶È*/
unsigned char mem[GLOBALFIFO_SIZE]; /*È«ŸÖÄÚŽæ*/
struct semaphore sem; /*²¢·¢¿ØÖÆÓõÄÐźÅÁ¿*/
wait_queue_head_t r_wait; /*×èÈû¶ÁÓõĵȎý¶ÓÁÐÍ·*/
wait_queue_head_t w_wait; /*×èÈûÐŽÓõĵȎý¶ÓÁÐÍ·*/
struct fasync_struct *async_queue; /* Ò천œá¹¹ÌåÖžÕ룬ÓÃÓÚ¶Á */
};
struct globalfifo_dev *globalfifo_devp; /*É豞œá¹¹ÌåÖžÕë*/
/*ÎÄŒþŽò¿ªº¯Êý*/
int globalfifo_open(struct inode *inode, struct file *filp)
{
/*œ«É豞œá¹¹ÌåÖžÕëž³ÖµžøÎÄŒþËœÓÐÊýŸÝÖžÕë*/
filp->private_data = globalfifo_devp;
return 0;
}
/*ÎÄŒþÊͷź¯Êý*/
int globalfifo_release(struct inode *inode, struct file *filp)
{
/* œ«ÎÄŒþŽÓÒ천͚֪ÁбíÖÐÉŸ³ý */
//globalmem_fasync( - 1, filp, 0);//modified by lht
globalfifo_fasync( - 1, filp, 0);
return 0;
}
/* ioctlÉ豞¿ØÖƺ¯Êý */
static int globalfifo_ioctl(struct inode *inodep, struct file *filp, unsigned
int cmd, unsigned long arg)
{
struct globalfifo_dev *dev = filp->private_data;/*»ñµÃÉ豞œá¹¹ÌåÖžÕë*/
switch (cmd)
{
case FIFO_CLEAR:
down(&dev->sem); //»ñµÃÐźÅÁ¿
dev->current_len = 0;
memset(dev->mem,0,GLOBALFIFO_SIZE);
up(&dev->sem); //ÊÍ·ÅÐźÅÁ¿
printk(KERN_INFO "globalfifo is set to zero/n");
break;
default:
return - EINVAL;
}
return 0;
}
static unsigned int globalfifo_poll(struct file *filp, poll_table *wait)
{
unsigned int mask = 0;
struct globalfifo_dev *dev = filp->private_data; /*»ñµÃÉ豞œá¹¹ÌåÖžÕë*/
down(&dev->sem);
poll_wait(filp, &dev->r_wait, wait);
poll_wait(filp, &dev->w_wait, wait);
/*fifo·Ç¿Õ*/
if (dev->current_len != 0)
{
mask |= POLLIN | POLLRDNORM; /*±êÊŸÊýŸÝ¿É»ñµÃ*/
}
/*fifo·ÇÂú*/
if (dev->current_len != GLOBALFIFO_SIZE)
{
mask |= POLLOUT | POLLWRNORM; /*±êÊŸÊýŸÝ¿ÉÐŽÈë*/
}
up(&dev->sem);
return mask;
}
/* globalfifo fasyncº¯Êý*/
static int globalfifo_fasync(int fd, struct file *filp, int mode)
{
struct globalfifo_dev *dev = filp->private_data;
return fasync_helper(fd, filp, mode, &dev->async_queue);
}
/*globalfifo¶Áº¯Êý*/
static ssize_t globalfifo_read(struct file *filp, char __user *buf, size_t count,
loff_t *ppos)
{
int ret;
struct globalfifo_dev *dev = filp->private_data; //»ñµÃÉ豞œá¹¹ÌåÖžÕë
DECLARE_WAITQUEUE(wait, current); //¶šÒåµÈŽý¶ÓÁÐ
down(&dev->sem); //»ñµÃÐźÅÁ¿
add_wait_queue(&dev->r_wait, &wait); //œøÈë¶ÁµÈŽý¶ÓÁÐÍ·
/* µÈŽýFIFO·Ç¿Õ */
if (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->mem, count))
{
ret = - EFAULT;
goto out;
}
else
{
memcpy(dev->mem, dev->mem + count, dev->current_len - count); //fifoÊýŸÝÇ°ÒÆ
dev->current_len -= count; //ÓÐЧÊýŸÝ³€¶ÈŒõÉÙ
printk(KERN_INFO "read %d bytes(s),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;
}
/*globalfifoÐŽ²Ù×÷*/
static ssize_t globalfifo_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos)
{
struct globalfifo_dev *dev = filp->private_data; //»ñµÃÉ豞œá¹¹ÌåÖžÕë
int ret;
DECLARE_WAITQUEUE(wait, current); //¶šÒåµÈŽý¶ÓÁÐ
down(&dev->sem); //»ñÈ¡ÐźÅÁ¿
add_wait_queue(&dev->w_wait, &wait); //œøÈëÐŽµÈŽý¶ÓÁÐÍ·
/* µÈŽýFIFO·ÇÂú */
if (dev->current_len == GLOBALFIFO_SIZE)
{
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 > GLOBALFIFO_SIZE - dev->current_len)
count = GLOBALFIFO_SIZE - dev->current_len;
if (copy_from_user(dev->mem + dev->current_len, buf, count))
{
ret = - EFAULT;
goto out;
}
else
{
dev->current_len += count;
printk(KERN_INFO "written %d bytes(s),current_len:%d/n", count, dev
->current_len);
wake_up_interruptible(&dev->r_wait); //»œÐѶÁµÈŽý¶ÓÁÐ
/* ²úÉúÒ천¶ÁÐźŠ*/
if (dev->async_queue)
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
ret = count;
}
out: up(&dev->sem); //ÊÍ·ÅÐźÅÁ¿
out2:remove_wait_queue(&dev->w_wait, &wait); //ŽÓžœÊôµÄµÈŽý¶ÓÁÐÍ·ÒƳý
set_current_state(TASK_RUNNING);
return ret;
}
/*ÎÄŒþ²Ù×÷œá¹¹Ìå*/
static const struct file_operations globalfifo_fops =
{
.owner = THIS_MODULE,
.read = globalfifo_read,
.write = globalfifo_write,
.ioctl = globalfifo_ioctl,
.poll = globalfifo_poll,
.open = globalfifo_open,
.release = globalfifo_release,
.fasync = globalfifo_fasync,
};
/*³õÊŒ»¯²¢×¢²ácdev*/
static void globalfifo_setup_cdev(struct globalfifo_dev *dev, int index)
{
int err, devno = MKDEV(globalfifo_major, index);
cdev_init(&dev->cdev, &globalfifo_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &globalfifo_fops;
err = cdev_add(&dev->cdev, devno, 1);
if (err)
printk(KERN_NOTICE "Error %d adding LED%d", err, index);
}
/*É豞Çý¶¯Ä£¿éŒÓÔغ¯Êý*/
int globalfifo_init(void)
{
int ret;
dev_t devno = MKDEV(globalfifo_major, 0);
/* ÉêÇëÉ豞ºÅ*/
if (globalfifo_major)
ret = register_chrdev_region(devno, 1, "globalfifo");
else /* ¶¯Ì¬ÉêÇëÉ豞ºÅ */
{
ret = alloc_chrdev_region(&devno, 0, 1, "globalfifo");
globalfifo_major = MAJOR(devno);
}
if (ret < 0)
return ret;
/* ¶¯Ì¬ÉêÇëÉ豞œá¹¹ÌåµÄÄÚŽæ*/
globalfifo_devp = kmalloc(sizeof(struct globalfifo_dev), GFP_KERNEL);
if (!globalfifo_devp) /*ÉêÇëʧ°Ü*/
{
ret = - ENOMEM;
goto fail_malloc;
}
memset(globalfifo_devp, 0, sizeof(struct globalfifo_dev));
globalfifo_setup_cdev(globalfifo_devp, 0);
init_MUTEX(&globalfifo_devp->sem); /*³õÊŒ»¯ÐźÅÁ¿*/
init_waitqueue_head(&globalfifo_devp->r_wait); /*³õÊŒ»¯¶ÁµÈŽý¶ÓÁÐÍ·*/
init_waitqueue_head(&globalfifo_devp->w_wait); /*³õÊŒ»¯ÐŽµÈŽý¶ÓÁÐÍ·*/
return 0;
fail_malloc: unregister_chrdev_region(devno, 1);
return ret;
}
/*Ä£¿éжÔغ¯Êý*/
void globalfifo_exit(void)
{
cdev_del(&globalfifo_devp->cdev); /*×¢Ïúcdev*/
kfree(globalfifo_devp); /*ÊÍ·ÅÉ豞œá¹¹ÌåÄÚŽæ*/
unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1); /*ÊÍ·ÅÉ豞ºÅ*/
}
MODULE_AUTHOR("Song Baohua");
MODULE_LICENSE("Dual BSD/GPL");
module_param(globalfifo_major, int, S_IRUGO);
module_init(globalfifo_init);
module_exit(globalfifo_exit);