高级字符驱动程序操作

版权声明:做个经常出去走走的技术宅 https://blog.csdn.net/qq_23084801/article/details/82316196

 

ioctl

//用户空间的ioctl
//... 一般用char *argp代替防止编译器进行类型检查
int ioctl(int fd, unsigned long cmd, ...);

//驱动程序ioctl原型
//前两个参数对应应用程序传递过来的fd,可选参数arg无论用户空间使用的是整数还是指针都以
//unsigned long的形式传递给驱动程序
int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);

本质上讲,每个ioctl都是一个独立的系统调用,而且 是非公开的。但是没有容易的办法来审核这些调用。有很多需求要我们通过其他途径实现复杂的控制操作。可行的方法包括:将命令嵌入到数据流中;使用虚拟文件系统如sysfs;但是对真正的设备操作来说,ioctl仍然是最简单最直接的选择。大多数ioctl实现中都包括一个switch语句根据cmd 选择不同操作,通常在代码中用符号名代替数值。为了方便程序员创建唯一的ioctl命令号,每一个号被分为多个字段。老版本是类型+序列号码,新版本是类型+序号+方向+大小。

ioctl.h中定义了一些构造命令编号的宏,type, number通过参数传入, size是对datatype去sizeof得到
_IO(type, nr) 构造无参数的命令编号
_IOR(type, nr, datatype) 构造从驱动程序中读取数据的命令编号
_IOW(type, nr, datatype) 用于写入命令

解位字段的宏:
_IOC_DIR(nr);
_IOC_TYPE();
_IOC_NR(nr);
_IOC_SIZE(nr);
/* Use 'K' as magic number */
#define SCULLC_IOC_MAGIC  'K'

#define SCULLC_IOCRESET    _IO(SCULLC_IOC_MAGIC, 0)

/*
 * S means "Set" through a ptr,
 * T means "Tell" directly
 * G means "Get" (to a pointed var)
 * Q means "Query", response is on the return value
 * X means "eXchange": G and S atomically
 * H means "sHift": T and Q atomically
 */
#define SCULLC_IOCSQUANTUM _IOW(SCULLC_IOC_MAGIC,  1, int)
#define SCULLC_IOCTQUANTUM _IO(SCULLC_IOC_MAGIC,   2)
#define SCULLC_IOCGQUANTUM _IOR(SCULLC_IOC_MAGIC,  3, int)
#define SCULLC_IOCQQUANTUM _IO(SCULLC_IOC_MAGIC,   4)
#define SCULLC_IOCXQUANTUM _IOWR(SCULLC_IOC_MAGIC, 5, int)
#define SCULLC_IOCHQUANTUM _IO(SCULLC_IOC_MAGIC,   6)
#define SCULLC_IOCSQSET    _IOW(SCULLC_IOC_MAGIC,  7, int)
#define SCULLC_IOCTQSET    _IO(SCULLC_IOC_MAGIC,   8)
#define SCULLC_IOCGQSET    _IOR(SCULLC_IOC_MAGIC,  9, int)
#define SCULLC_IOCQQSET    _IO(SCULLC_IOC_MAGIC,  10)
#define SCULLC_IOCXQSET    _IOWR(SCULLC_IOC_MAGIC,11, int)
#define SCULLC_IOCHQSET    _IO(SCULLC_IOC_MAGIC,  12)

#define SCULLC_IOC_MAXNR 12
struct file_operations scullc_fops = {
	.owner =     THIS_MODULE,
	
	.ioctl =     scullc_ioctl,
	.open =	     scullc_open,
	.release =   scullc_release,
	.aio_read =  scullc_aio_read,
	.aio_write = scullc_aio_write,
};

int scullc_ioctl (struct inode *inode, struct file *filp,
                 unsigned int cmd, unsigned long arg)
{

	int err = 0, ret = 0, tmp;

	/* don't even decode wrong cmds: better returning  ENOTTY than EFAULT */
	if (_IOC_TYPE(cmd) != SCULLC_IOC_MAGIC) return -ENOTTY;
	if (_IOC_NR(cmd) > SCULLC_IOC_MAXNR) return -ENOTTY;

	/*
	 * the type is a bitmask, and VERIFY_WRITE catches R/W
	 * transfers. Note that the type is user-oriented, while
	 * verify_area is kernel-oriented, so the concept of "read" and
	 * "write" is reversed
	 */
	if (_IOC_DIR(cmd) & _IOC_READ)
		err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
	else if (_IOC_DIR(cmd) & _IOC_WRITE)
		err =  !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
	if (err)
		return -EFAULT;

	switch(cmd) {

	case SCULLC_IOCRESET:
		scullc_qset = SCULLC_QSET;
		scullc_quantum = SCULLC_QUANTUM;
		break;


	case SCULLC_IOCHQSET:
		tmp = scullc_qset;
		scullc_qset = arg;
		return tmp;

	default:  /* redundant, as cmd was checked against MAXNR */
		return -ENOTTY;
	}

	return ret;
}

驱动程序要对每个用到用户空间地址做检查。可以通过access_ok来验证。但是其并没有完成内存验证的全部工作,只是检查所引用的内存是否位于进程所对应访问权限的区域内,特别是没有指向内核空间的内存区。

当传递单个数据时使用put_user和get_user,他们和__put_user,__get_user后者没有调用access_ok需要手动调用
put_user(datum, ptr);
__put_user(datum, ptr);
get_user(local, ptr);
__get_user(local, ptr);

对某些操作驱动程序必须进行附加的检查以确认用户是否有权进行操作。内核导出两个系统调用capget和capset,全部 

权限操作可以从<linux/capability.h>中找到。

阻塞型IO

当数据不可用时,驱动程序应该默认阻塞该进程,并考虑在将来唤醒。

进程安全休眠的原则:

  1. 不要在原子上下文进入休眠。休眠进程可能一直不释放信号量等资源导致其他使用该资源的设备也会进入休眠。
  2. 当进程被唤醒时必须再次检查它等待的条件为真,因为被其他同样等待的进程抢占
  3. 维护等待队列:等待某个特定事件的所有进程。
#include <linux/wait.h>
静态初始化等待队列头
DECLARE_WAIT_QUEUE_HEAD(name);

动态
wait_queue_head_t my_queue;
init_waitqueue_head(&my_queue);

休眠和唤醒

wait_event_interruptible(queue, condition);

void wake_up_interruptible(wait_queue_head_t *queue);

指定O_NONBLOCK标志当数据没有就绪就简单返回-EAGAIN。 只有read, write, open会受非阻塞标志影响。

独占等待

等待队列入口设置了WQ_FLAG_EXCLUSIEV,独占进程每次只会唤醒一个,而唤醒所有非独占进程。

void prepare_to_wait_exclusive(wait_queue_head_t *queue, wait_queue_t *wait, int state);
这个函数设置等待队列入口的独占标志,并将进程加入等待队列的尾部(其他进程向头部添加)

poll和select

poll, select, epoll都允许进程决定是否可以对一个或多个打开的文件做非阻塞的读取或写入。这些调用也会阻塞进程,直到给定的文件描述符 集合中的任何一个可读取或者写入。select在BSD Unix中引入,poll由System V引入。poll调用结束,poll_table结构被重新分配,所有先前添加到poll表中的等待队列入口都会从这个表中以及等待队列中移除。所以涉及太多文件描述符时开销就比较大epoll系统调用类用于解决这个问题。

unsigned int (*poll) (struct file *filp, poll_table *wait);
返回一个用来描述操作是否可以立即无阻塞执行的位掩码,如果有数据就绪read就可以立即执行而不用休眠
如果没有数据可获得应该返回POLLHUB
通过poll_wait驱动程序向poll_table中添加一个等待队列

int (*fsync) (struct file *file, struct dentry *dentry, int datasync);
该调用只有在设备已经被完全刷新(输出缓冲区全空)时才会返回,即使要花费一段时间,是否设置了O_NONBLOCK标志对其没有影响。参数datasync用于区分fsync和fdatasync这两个系统调用

异步通知

poll是轮询,为启动文件的异步通知用户程序必须 

执行两个步骤:首先,指定一个进程作为文件的属主。当进程调用fcntl执行F_SETOWN时,属主进程ID被保存在filp->f_owner中。然后,用户在设备中调用fcntl设置FASYNC标志启动异步通知机制。当数据到达时,所有注册为异步通知的进程 都会收到一个SIGIO信号。当文件关闭时必须调用fasync在活动的异步读取进程列表中删除该文件。

定位设备llseek

如果设备未定义llseek,内核默认通过修改filp->f_pos而执行定位。然而 大多数设备只提供了数据流(如键盘和串口),而不是数据区应该在open函数中调用nonseekable_open,通知内核该 设备不支持llseek, 因为某人方法是允许定位的。

  1. scullsingle 只允许一个进程打开设备
  2. sculluid只允许同一用户多个进程打开
  3. scullwuid和sculluid 不同之处在于,open时会等待设备(等待设备关闭)而不是返回-EBUSY
  4. scullpriv实现了虚拟设备(赋值的设备由软件驱动程序创建,就像虚拟终端 )都使用同一个物理终端一样。

猜你喜欢

转载自blog.csdn.net/qq_23084801/article/details/82316196