Linux驱动----2、字符设备驱动

这篇博客深入探讨了Linux字符设备驱动,重点介绍了设备号在区分设备和驱动程序中的作用。同时,讲解了在内核空间中如何通过file_operations结构将驱动操作与设备号关联。还提到了file结构和inode结构在文件表示中的差异,以及在open、release和close操作中的行为。此外,文章讨论了字符设备的注册过程,并阐述了scull设备在内存使用方面的特点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.设备号

对字符设备的访问通过文件系统内的设备名称进行,这些名称也称为文件系统树的节点,位于/dev下。主设备号标志着设备对应的驱动程序,次设备号区分相同驱动程序下的不同设备。

dev_t dev;//由主次设备号构成

MAJOR(dev_t dev); //得到主设备号
MINOR(dev_t dev);
MKDEV(int major, int minor);//合成
//静态分配设备号, first是主次设备号的合成, count是次设备号个数, name是与该范围编号关联的设备名称
//它将出现在/proc/devices和sysfs中
int register_chrdev_region(dev_t first, unsigned int count, char *name);

//动态分配设备号, 分配到的设备号返回给参数dev, firstminor是要申请的第一个次设备号
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name)

当编译好模块*.ko文件后,需要安装该模块, 并在/dev下创建设备文件(mknod 模块名)。用户可以在rc.local文件中调用如下 脚本。

#!/bin/sh
module="simple"
device="simple"
mode="664"

# Group: since distributions do it differently, look for wheel or use staff
if grep '^staff:' /etc/group > /dev/null; then
    group="staff"
else
    group="wheel"
fi

# invoke insmod with all arguments we got
# and use a pathname, as newer modutils don't look in . by default
#$*表示从命令行传入的所有参数在insmod *.ko时传入
/sbin/insmod -f ./$module.ko $* || exit 1

#awk命令格式:如果符合pattern则执行后面的action,这里/proc/devices下每行由主设备号和设备名称构##成,$2指设备名称, $1指设备号;
#awk "\$2==\"mdp\" {print \$1}" /proc/devices 即获得mdp的设备号
major=$(awk "\$2==\"$module\" {print \$1}" /proc/devices)  
# Remove stale nodes and replace them, then give gid and perms
# Usually the script is shorter, it's simple that has several devices in it.

rm -f /dev/${device}[rn]
mknod /dev/${device}r c $major 0
mknod /dev/${device}n c $major 1
chgrp $group /dev/${device}[rn] 
chmod $mode  /dev/${device}[rn]

重要的数据结构

file_operations, 定义在linux/fs.h中用来把驱动程序操作连接到设备号。

file 其和用户空间的FILE结构没有关系,系统中每个打开的文件在内核空间都有一个对应的file结构。在open时创建并传递给在操作该文件的所有函数。

struct file {
    ...
	const struct file_operations	*f_op;
    ...
};

内核用inode结构在内部表示文件,它和file不同,file表示打开的文件描述符。对单个文件,可能有多个file,但他们都指向同一个inode结构。

struct inode {
    ...
	umode_t			i_mode;
	uid_t			i_uid;
	gid_t			i_gid;
	const struct inode_operations	*i_op;

	struct list_head	i_devices;
	union {
		struct pipe_inode_info	*i_pipe;
		struct block_device	*i_bdev;
		struct cdev		*i_cdev;
	};
	void			*i_private; /* fs or device private pointer */
    ...
};

字符设备的注册(linux/cdev.h)

module_init(scullc_init);
int scullc_init(void)
{
	dev_t dev = MKDEV(scullc_major, 0);
	
	if (scullc_major)
		result = register_chrdev_region(dev, scullc_devs, "scullc");
	else {
		result = alloc_chrdev_region(&dev, 0, scullc_devs, "scullc");
		scullc_major = MAJOR(dev);
	}
	if (result < 0)
		return result;

	scullc_devices = kmalloc(scullc_devs*sizeof (struct scullc_dev), GFP_KERNEL);
	if (!scullc_devices) {
		result = -ENOMEM;
		goto fail_malloc;
	}
	memset(scullc_devices, 0, scullc_devs*sizeof (struct scullc_dev));
	for (i = 0; i < scullc_devs; i++) {
		scullc_devices[i].quantum = scullc_quantum;
		scullc_devices[i].qset = scullc_qset;
		sema_init (&scullc_devices[i].sem, 1);
        
        ///
		scullc_setup_cdev(scullc_devices + i, i);
        ///
	}

	scullc_cache = kmem_cache_create("scullc", scullc_quantum,
			0, SLAB_HWCACHE_ALIGN, NULL, NULL); /* no ctor/dtor */
	if (!scullc_cache) {
		scullc_cleanup();
		return -ENOMEM;
	}

#ifdef SCULLC_USE_PROC /* only when available */
	create_proc_read_entry("scullcmem", 0, NULL, scullc_read_procmem, NULL);
#endif
	return 0; /* succeed */

  fail_malloc:
	unregister_chrdev_region(dev, scullc_devs);
	return result;
}

static void scullc_setup_cdev(struct scullc_dev *dev, int index)
{
	int err, devno = MKDEV(scullc_major, index);
    
    //为cdev分配空间
	cdev_init(&dev->cdev, &scullc_fops);
	dev->cdev.owner = THIS_MODULE;
	dev->cdev.ops = &scullc_fops;
    
    //告诉内核该设备活力,此时驱动程序必须完全准备好处理设备上的操作,devno是这次设备号的合成,最后一个参数为此设备号数量
	err = cdev_add (&dev->cdev, devno, 1);
	/* Fail gracefully if need be */
	if (err)
		printk(KERN_NOTICE "Error %d adding scull%d", err, index);
}

open

int scullc_open (struct inode *inode, struct file *filp)
{
	struct scullc_dev *dev; /* device information */

	/*  该函数原型container_of(pointer, container_type, container_filed),container_type结构
            中包含container_filed则根据container_filed返回container_type的指针 */
	dev = container_of(inode->i_cdev, struct scullc_dev, cdev);

    	/* now trim to 0 the length of the device if open was write-only */
	if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {
		if (down_interruptible (&dev->sem))
			return -ERESTARTSYS;
		scullc_trim(dev); /* ignore errors */
		up (&dev->sem);
	}

	/************************************************/
	filp->private_data = dev;
	/************************************************/
	return 0;          /* success */
}

release

不是每个close系统调用都会引起对release方法的调用(close不是release )。内核对每个file结构维护其被使用多少次的计数器。无论是fork还是dup,都不会创建新的数据结构(仅由open创建),它们只是增加已有结构的计数。只是在file结构的计数归0时,close系统调用才会执行release方法。

scull的内存使用

struct scull_qset {
	void **data;
	struct scull_qset *next;
};

//qset等于data二维数组的行数,数组每个元素都是一个地址
struct scull_dev {
	struct scull_qset *data;  /* Pointer to first quantum set */
	int quantum;              /* the current quantum size */
	int qset;                 /* the current array size */
	unsigned long size;       /* amount of data stored here */
	unsigned int access_key;  /* used by sculluid and scullpriv */
	struct semaphore sem;     /* mutual exclusion semaphore     */
	struct cdev cdev;	  /* Char device structure		*/
};

int scull_trim(struct scull_dev *dev)
{
	struct scull_qset *next, *dptr;
	int qset = dev->qset;   /* "dev" is not-null */
	int i;

	for (dptr = dev->data; dptr; dptr = next) { /* all the list items */
		if (dptr->data) {
			for (i = 0; i < qset; i++)
				kfree(dptr->data[i]);
			kfree(dptr->data);
			dptr->data = NULL;
		}
		next = dptr->next;
		kfree(dptr);
	}
	dev->size = 0;
	dev->quantum = scull_quantum;
	dev->qset = scull_qset;
	dev->data = NULL;
	return 0;
}

read和write的 char __user *buff参数是用户空间的指针,内核代码不能直接引用其中的内容。

ssize_t scull_read(struct file *filp, char __user *buf, size_t count,
                loff_t *f_pos)
{
	struct scull_dev *dev = filp->private_data; 
	struct scull_qset *dptr;	/* the first listitem */
	int quantum = dev->quantum, qset = dev->qset;
	int itemsize = quantum * qset; /* how many bytes in the listitem */
	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;

	/* find listitem, qset index, and offset in the quantum */
	item = (long)*f_pos / itemsize; //对应的qset
	rest = (long)*f_pos % itemsize;
	s_pos = rest / quantum;  //对应的量子,即二维数组的某行
	q_pos = rest % quantum;  //在某行上的偏移

	/* follow the list up to the right position (defined elsewhere) */
	dptr = scull_follow(dev, item);

	if (dptr == NULL || !dptr->data || ! dptr->data[s_pos])
		goto out; /* don't fill holes */

	/* read only up to the end of this quantum */
	if (count > quantum - q_pos)
		count = quantum - q_pos;

	if (copy_to_user(buf, dptr->data[s_pos] + q_pos, count)) {
		retval = -EFAULT;
		goto out;
	}
	*f_pos += count;
	retval = count;

  out:
	up(&dev->sem);
	return retval;
}
ssize_t scull_write(struct file *filp, const char __user *buf, size_t count,
                loff_t *f_pos)
{
	struct scull_dev *dev = filp->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; /* value used in "goto out" statements */

	if (down_interruptible(&dev->sem))
		return -ERESTARTSYS;

	/* find listitem, qset index and offset in the quantum */
	item = (long)*f_pos / itemsize;
	rest = (long)*f_pos % itemsize;
	s_pos = rest / quantum; q_pos = rest % quantum;

	/*************************************************/
        //如果写的这部分设备内存不存在,则先创建
	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;
	}
	/* write only up to the end of this quantum */
	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;

        /* update the size */
	if (dev->size < *f_pos)
		dev->size = *f_pos;

  out:
	up(&dev->sem);
	return retval;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值