《LINUX设备驱动程序》读书笔记---------字符设备(三)


这一节没有新的内容,只是将前面(一)(二)中的代码整合起来,形成一个让我们可以运行测试的scull驱动程序(补充了scull_unload脚本)。经过测试,按照如下流程可以在ubuntu14.04上正常运行。

1.scull_load和scull_unload
  • scull_load
    #!/bin/sh
    module="scull"
    device="scull"
    mode="664"
    
    #invoke insmod with all arguments we got
    #and use a pathname,as newer modutils don't look in . by default
    /sbin/insmod ./$module.ko $* || exit 1
    
    
    major=$(awk "\$2==\"$module\" {print \$1}" /proc/devices)
    #remove stale nodes
    rm -f /dev/${device}[0-3]
    mknod /dev/${device}0 c $major 0
    mknod /dev/${device}1 c $major 1
    mknod /dev/${device}2 c $major 2
    mknod /dev/${device}3 c $major 3
    
    #give appropriate group/permissions, and change the group.
    #Not all distributions have staff, some have "wheel" instead.
    
    group="staff"
    grep -q '^staff:' /etc/group || group="wheel"
    
    chgrp $group /dev/${device}[0-3]
    chmod $mode /dev/${device}[0-3]
    
    
  • scull_unload
    #!/bin/sh
    module="scull"
    device="scull"
    
    # invoke rmmod with all arguments we got
    /sbin/rmmod $module $* || exit 1
    
    # Remove stale nodes
    
    rm -f /dev/${device} /dev/${device}[0-3] 
    
    
2.scull.c和scull.h
  • scull.h
    #ifndef _SCULL_H
    #define _SCULL_H
    
    #ifndef SCULL_MAJOR
    #define SCULL_MAJOR 0   /* dynamic major by default */
    #endif
     
    
    #ifndef SCULL_NR_DEVS
    #define SCULL_NR_DEVS 4    /* scull0 through scull3 */
    #endif
    
    #ifndef SCULL_QUANTUM
    #define SCULL_QUANTUM 4000
    #endif
     
    #ifndef SCULL_QSET
    #define SCULL_QSET    1000
    #endif
    
    
    struct scull_qset{
    	void **data;
    	struct scull_qset *next;
    };
    
    struct scull_dev{
    	struct scull_qset *data;	
    	int quantum;				
    	int qset;					
    	unsigned long size;			
    	unsigned int access_key;	
    	struct semaphore sem;		
    	struct cdev cdev;			
    };
    #endif
    
    
  • scull.c
    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/fs.h>
    #include <linux/cdev.h>
    #include <asm/uaccess.h>
    #include <linux/semaphore.h>
    #include <linux/slab.h>
    
    #include "scull.h"
    
    
    int scull_major =   SCULL_MAJOR; 		// 主设备号
    int scull_minor =   0; 					// 次设备号
    int scull_nr_devs = SCULL_NR_DEVS;    	// scull设备数量
    int scull_quantum = SCULL_QUANTUM;  	// 量子大小(字节)
    int scull_qset =    SCULL_QSET; 		// 量子集大小
    module_param(scull_major, int, S_IRUGO);
    module_param(scull_minor, int, S_IRUGO);
    module_param(scull_nr_devs, int, S_IRUGO);
    module_param(scull_quantum, int, S_IRUGO);
    module_param(scull_qset, int, S_IRUGO);
    
    
    struct scull_dev *scull_devices;
    
    /*scull长度裁剪为0*/
    int scull_trim(struct scull_dev *dev)
    {
    	struct scull_qset *next,*dptr;
    	int qset = dev->qset;	
    	int i;
    	for(dptr = dev->data;dptr;dptr = next)/*遍历链表,访问每个scull_qset节点*/
    	{
    		if(dptr->data)
    		{
    			for(i=0;i<qset;i++)		/*遍历量子集,释放每个量子*/
    				kfree(dptr->data[i]);
    			kfree(dptr->data);		/*释放量子集*/
    			dptr->data = NULL;			
    		}
    		next = dptr->next;
    		kfree(dptr);		/*释放scull_qset节点*/
    	}
    	/*初始化scull_dev*/
    	dev->size = 0;
    	dev->quantum = scull_quantum;
    	dev->qset = scull_qset;
    	dev->data = NULL;		
    	return 0;
    }
    /*返回链表中第n个scull_qset*/
    struct scull_qset *scull_follow(struct scull_dev *dev, int n)
    {
        struct scull_qset *qs = dev->data; // 指向第0个scull_qset
     
        /* 第0个为空的话,则分配 */
        if (! qs) {
            qs = dev->data = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);    
                                                    
            if (qs == NULL)
                return NULL;  /* Never mind */
            memset(qs, 0, sizeof(struct scull_qset));
        }
     
        /* 遍历列表,指向第N个scull_qset */
        while (n--) {
            if (!qs->next) {
                qs->next = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);
                if (qs->next == NULL) 
                    return NULL; 
                memset(qs->next, 0, sizeof(struct scull_qset));
            }
            qs = qs->next;
            continue;
        }
        return qs;
    }
    
    
    
    int scull_open(struct inode *inode,struct file *filp)
    {
    	struct scull_dev *dev;
    	dev = container_of(inode->i_cdev,struct scull_dev,cdev);
    	filp->private_data = dev; /*保存到private_data域,以便其他方法使用*/
    	
    	/*如果是只写打开,长度裁剪为0*/
    	if((filp->f_flags & O_ACCMODE) == O_WRONLY){
    		scull_trim(dev); /*ignore errors*/
    	}	
    	return 0; /*success*/
    }
    int scull_release(struct inode *inode,struct file *filp)
    {
    	return 0;
    }
    
    ssize_t scull_read(struct file *filp,char __user *buf,size_t count,loff_t *f_pos)
    {
    	struct scull_dev *dev = filp->private_data;   /*在open中,我们把dev保存在了file的private_data域*/
    	struct scull_qset *dptr;
    	int quantum = dev->quantum, qset = dev->qset; 	/*quantum是量子大小,qset是一个量子集有多少量子*/
    	int itemsize = quantum * qset;					/*计算链表中一个scull_qset节点 指向的空间大小*/
    	int item,s_pos,q_pos,rest;
    	ssize_t retval = 0;
    	
    	if(down_interruptible(&dev->sem)) 		
    		return -ERESTARTSYS;
    	if(*f_pos >= dev->size)			/*判断文件当前位置是否超出设备区域*/
    		goto out;
    	if(*f_pos + count > dev->size)	/*判断当前位置+写入的字节是否会超出设备区域*/
    		count = dev->size - *f_pos;
    	item = (long)*f_pos / itemsize;	/*计算是第几个scull_qset节点*/
    	rest = (long)*f_pos % itemsize;	
    	s_pos = rest / quantum; /*计算是量子集中第几个量子*/
    	q_pos = rest % quantum; /*计算在量子中的偏移*/
    	
    	
    	dptr = scull_follow(dev,item); /*返回指针指向相应scull_qset节点*/
    	if(dptr == NULL || !dptr->data || !dptr->data[s_pos]) 
    		goto out;
    	if(count > quantum - q_pos) /*一个量子中放不下要写入的数据,那么只写入能写下的*/
    		count = quantum-q_pos;
    	if(copy_to_user(buf,dptr->data[s_pos] + q_pos,count)) /*copy_to_user*/
    	{
    		retval = -EFAULT;
    		goto out;
    	}
    	*f_pos += count;  /*更新文件位置指示*/
    	retval = count;	  /*更新实际写入字节*/
    out:
    	up(&dev->sem);
    	return retval;
    }
    
    
    ssize_t scull_write(struct file *flip,const char __user *buf,size_t count,loff_t *f_pos)
    {
    	struct scull_dev *dev = flip->private_data;
    	struct scull_qset *dptr;
    	int quantum = dev->quantum,qset = dev->qset;
    	int itemsize = quantum * qset;
    	int item,s_pos,q_pos,rest;
    	ssize_t retval = -ENOMEM ;
    	
    	if(down_interruptible(&dev->sem)) 		
    		return -ERESTARTSYS;
    	item = (long)*f_pos / itemsize;	
    	rest = (long)*f_pos % itemsize;	
    	s_pos = rest / quantum; 
    	q_pos = rest % quantum; 
    	/*在此之前都与read相同,
    		不同的地方在于,写数据的时候要为量子集和量子开辟空间
    	*/
    	dptr = scull_follow(dev,item); 
    	if(dptr == NULL ) 
    		goto out;
    	if(!dptr->data)/*为量子集开辟空间*/
    	{
    		dptr->data = kmalloc(qset * sizeof(char *),GFP_KERNEL);
    		if(!dptr->data)
    			goto out;
    		memset(dptr->data,0,qset * sizeof(char *));
    	}
    	if(!dptr->data[s_pos]) /*为量子开辟空间*/
    	{
    		dptr->data[s_pos] = kmalloc(quantum,GFP_KERNEL);
    		if(!dptr->data[s_pos])
    			goto out;
    	}
    	if(count > quantum - q_pos)
    		count = quantum - q_pos;
    	if(copy_from_user(dptr->data[s_pos]+q_pos,buf,count))
    	{
    		retval = -EFAULT;
    		goto out;
    	}
    	*f_pos += count;
    	retval = count;
    	/*更新设备size大小*/
    	if(dev->size < *f_pos)
    		dev->size = *f_pos;
    	
    out:
    	up(&dev->sem);
    	return retval;	
    }
    
    
    
    
    struct file_operations scull_fops = {
    	.owner = THIS_MODULE,	
    	.open = scull_open,
    	.release = scull_release,
    	.read = scull_read,
    	.write = scull_write,
    };
    
    void scull_cleanup_module(void)
    {
    	int i;
    	dev_t devno = MKDEV(scull_major, scull_minor);	
    	if (scull_devices) 
    	{
    		for (i = 0; i < scull_nr_devs; i++) 
    		{
    			scull_trim(scull_devices + i);	
    			cdev_del(&scull_devices[i].cdev);
    		}
    		kfree(scull_devices); 
    	}
    	
    	unregister_chrdev_region(devno, scull_nr_devs);
    	printk(KERN_DEBUG "scull_cleanup_module\n");
    }
    
    static void scull_setup_cdev(struct scull_dev *dev, int index)
    {
    	int err,devno = MKDEV(scull_major,scull_minor+index);
    	cdev_init(&dev->cdev,&scull_fops);
    	dev->cdev.owner = THIS_MODULE;
    	//dev->cdev.ops = &scull_fops;  /*cdev_init中明明完成了这一条代码,为何要重复???*/
    	err = cdev_add(&dev->cdev,devno,1); 
    	if(err)
    		printk(KERN_NOTICE "Error %d adding scull%d",err,index);
    }
    
    
    
    int scull_init_module(void)
    {
    	int result,i;
    	dev_t dev = 0;
    	/*注册设备号*/
    	if(scull_major) //scull_major如果是默认值0,就跳转else,动态分配设备号
    	{
    		dev = MKDEV(scull_major,scull_minor);
    		result = register_chrdev_region(dev,scull_nr_devs,"scull");
    	}else{
    		result = alloc_chrdev_region(&dev,scull_minor,scull_nr_devs,"scull");
    		scull_major = MAJOR(dev);
    	}
    	if(result < 0)
    	{
    		printk(KERN_WARNING"scull:cant't get major %d\n",scull_major);
    		return result;
    	}
    	scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);
        if (!scull_devices) {
            result = -ENOMEM;
            goto fail;  /* Make this more graceful */
        }
        memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev));
    	/*初始化字符设备(共4个)*/
    	for (i = 0; i < scull_nr_devs; i++) {
    		scull_devices[i].quantum = scull_quantum;
    		scull_devices[i].qset = scull_qset;
    		sema_init(&scull_devices[i].sem,1);
    		scull_setup_cdev(&scull_devices[i], i);
    	}
    	printk(KERN_DEBUG "scull_init_module succeed\n");
    	return 0; 
    fail:
        scull_cleanup_module();
        return result;
    }
    
    
    
    module_init(scull_init_module); 
    module_exit(scull_cleanup_module);
    MODULE_LICENSE("Dual BSD/GPL"); 
    
3.scull测试
  1. 将Makefile和scull.c scull.h放在同一个文件夹下,运行make命令编译生成ko文件。

  2. 执行scull_load脚本文件 sudo ./scull_load

  3. 使用ls和cat命令进行测试(可能需要执行 sudo -i 进入root用户才能正确运行,只是sudo可能不行)

    执行ls -l > /dev/scull0
    在这里插入图片描述
    执行cat /dev/scull0查看结果
    在这里插入图片描述

成功~!用scull_unload卸载吧!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值