《嵌入式Linux驱动开发教程》--高级I/O操作

高级I/O操作

  • 1.ioctl设备操作
  • 2.proc文件系统
  • 3.非阻塞式IO
  • 4.阻塞式IO
  • 5. IO多路复用
  • 6.异步IO
  • 7.异步通知(信号驱动IO模型)
  • 8.相关代码
      • 8.1非阻塞式IO用户层代码
      • 8.2多路复用用户层代码
      • 8.3异步IO用户层代码
      • 8.4异步通知用户层代码
      • 8.5五种IO模型驱动代码
  • 9.mmap文件操作
  • 10.其它

1.ioctl设备操作

1.内核将读写之外的I/O操作给了ioctl系统调用。

	//系统调用函数原型
	int ioctl(int d, int request, unsigned long arg); 
	int d:文件描述符
	 int request:对设备操作的命令
	unsigned long arg: 用于传递的字符值或者指针
	(比如cmd要修改a的值,arg参数就是要修改的a的值)
	
	一般在ioctl中使用switch语句对cmd命令进行条件分支,对不同的命令执行不同的操作

2.CMD命令格式
一个命令由四部分组成,有特定的位宽,为避免命令的重复定义。
理想下幻数是全局唯一的。
《嵌入式Linux驱动开发教程》--高级I/O操作_第1张图片

定义、提取命令中的字段
《嵌入式Linux驱动开发教程》--高级I/O操作_第2张图片
举例:写入一个字节大小12的struct option结构。假设定义幻术s,命令码2。
方法1:_IOC(1,‘s’,2,12)
方法2:_IOW(‘s’,2,struct option)

3.ioctl系统调用时,在驱动解析命令前,有内核代码处理命令了。
如果命令定义和内核中的一样,驱动中的ioctl就不会被调用。

iocto用户层的使用

struct option {
	unsigned int datab;
	unsigned int parity;
	unsigned int stopb;
};

#define VS_MAGIC	's'

#define VS_SET_BAUD	_IOW(VS_MAGIC, 0, unsigned int)
#define VS_GET_BAUD	_IOW(VS_MAGIC, 1, unsigned int)
#define VS_SET_FFMT	_IOW(VS_MAGIC, 2, struct option)
#define VS_GET_FFMT	_IOW(VS_MAGIC, 3, struct option)
static long vser_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	if (_IOC_TYPE(cmd) != VS_MAGIC)
		return -ENOTTY;

	switch (cmd) {
	case VS_SET_BAUD:
		vsdev.baud = arg;
		break;
	case VS_GET_BAUD:
		arg = vsdev.baud;
		break;
	case VS_SET_FFMT:
		if (copy_from_user(&vsdev.opt, (struct option __user *)arg, sizeof(struct option)))
			return -EFAULT;
		break;
	case VS_GET_FFMT:
		if (copy_to_user((struct option __user *)arg, &vsdev.opt, sizeof(struct option)))
			return -EFAULT;
		break;
	default:
		return -ENOTTY;
	}

	return 0;
}

2.proc文件系统

1.proc文件系统是一种伪文件系统,这种文件系统不存在于磁盘上,只存在于内存中,只有在内核运行时才会动态生成里面的内容。

2.这个文件系统通常是挂载在/proc目录下,内核开发者向用户导出信息的常用方式,现在不常用被sysfs系统取代。

3.非阻塞式IO

应用程序和驱动程序一起就组成了多种IO模型,假设应用程序配置的的是非阻塞的方来打开设备文件,此时当资源不可用时,驱动就应该立即返回,并用一个错误码来通知应用程序,资源不可用,应用程序可稍后再做尝试。

4.阻塞式IO

1.为文件打开的默认方式,资源不可用,进程阻塞,进程休眠。
《嵌入式Linux驱动开发教程》--高级I/O操作_第3张图片

2.阻塞方式的优点:不会占用CPU的时间,而非阻塞方式会定期去看下资源是否可用
阻塞方式的缺点:进程进入休眠状态后,进程无法做其他事情

3.等待队列的宏与函数
《嵌入式Linux驱动开发教程》--高级I/O操作_第4张图片

	wq:等待队列
	condition:条件不成立时,将当前进程放到等待队列并休眠
	timeout:超时限制
	Interruptible:进程再休眠时可以被信号唤醒
	exclusive:该进程具有排他性。
	    默认情况唤醒操作唤醒等待队列的所有进程,exclusive在唤醒这个进程后不唤醒其它的
	locked:要求调用前先获得等待队列内部的锁
	irq:上锁的同时禁止中断
	返回值:不带timeout 0表示唤醒;
		   带timeout 0表示超时,大于0表示成功唤醒(返回值是距离唤醒还剩时间)
	
不同唤醒函数代表不同休眠方式

4.根本工作是构造并初始化等待队列头,构造等待队列节点,设置进程状态,节点加入到等待队列,放弃CPU,调度其他进程执行,资源可用时,唤醒等待队列上的进程。

5. IO多路复用

1.阻塞型IO的缺点:当进程进入休眠状态后,进程无法做其他事情。
一个进程要同时对多个设备进行操作不方便。进程进入休眠,无法访问其他的设备资源了。

2.IO多路复用有select、poll以及Linux所特有的epoll三种方式。

3.poll系统调用

int poll(struct pollfd *fds,nfds_t nfds,int timeout);
		struct pollfd *fds:要监听的文件描述符的集合
		nfds_t nfds:要监听的文件描述符的个数
		int timeout:超时值,以毫秒为单位,负数表示一直监听
关键数据结构:
	struct pollfd{
	    int fd;	  //要监听的文件描述符
	    short events;//要监听的事件
	    short revents;//返回的事件
	};
常见事件:POLLIN		//可进行读操作
 		 POLLOUT       //可进行写

4.poll系统调用示意图

关于图片理解:
《嵌入式Linux驱动开发教程》--高级I/O操作_第5张图片
我的理解:除了加红的驱动干的,其余都是内核干的,
所以驱动要做的事是1.将节点加入等待队列;2.判断资源是否可用

5.IO多路复用根本工作是:遍历所有被监听的设备驱动的poll接口函数,都没有事件发生时poll系统调用休眠,直到一个驱动唤醒。

6.驱动中的poll接口函数不会休眠,休眠的是poll系统调用中
阻塞型IO在驱动处休眠。

6.异步IO

1.异步I/O在提交完I/O操作请求后立即返回,程序不需等到I/O操作完成再去做别的事,具有非阻塞特性。

2.当驱动中IO操作完成后,调用者会得到通知,通常是内核向调动向调用者发送信号。或者自动套用调用者注册的回调函数,通知操作是由内核完成的,而不是驱动本身。驱动只是指定了要发的信号或回调函数

3.关键数据结构:aiocb ;aio_sigevent

	struct aiocb {
	    int aio_fildes;                 //表示被打开用来读和写的文件描述符
	    off_t aio_offset;             //偏移量,表示读或写操作的起始位置
	    volatile void *aio_buf;   //对读写操作,数据被复制的缓冲区
	    size_t aio_nbytes;          // 指定要读或写的字节数
	    int aio_reqprio;              //请求优先级,一般用不到
	    struct sigevent aio_sigevent; //与异步通知控制相关的结构体
	    int aio_lio_opcode;        //此字段指定了该操作是一个读操作还是一个写操作
	};
	 struct sigevent {
	       int    sigev_notify; //通知方式 
	       int     sigev_signo;  //  sigev_notif为SIGEV_SIGNAL时的通知信号 
	       union sigval sigev_value;  //伴随通知所传递的数据 
	       void    (*sigev_notify_function) (union sigval);
	                        //通知方式设置为"线程通知" (SIGEV_THREAD)时新线程的线程函数 
	       void     *sigev_notify_attributes;
	                        //通知方式设置为"线程通知" (SIGEV_THREAD)时新线程的属性 
	       pid_t    sigev_notify_thread_id;
	                        // 通知方式为SIGEV_THREAD_ID时,接收信号的线程的pid 
	   };
	    根据sigev_notify的不同对数据结构中不同值赋值
		SIGEV_NONE:不通知。当事件发生时不做任何操作。
		SIGEV_SIGNAL:通过发送sigev_signo所指定的信号的方式通知进程事件已经发生
		SIGEV_THREAD:通过调用sigev_notify_function的回调函数方式通知进程事件已经发生
		SIGEV_THREAD_ID(linux特有):目前只被posix timer使用

4.常用API

aio_error函数 :0:异步操作成功返回
aio_return函数:可以用来判断异步IO的执行情况,aio_return函数本身失败,返回-1
               成功会返回read、write在被成功调用时可能返回的结果

5.异步IO在驱动阻塞,在应用层不阻塞

7.异步通知(信号驱动IO模型)

1.异步通知类似于前面提到的异步IO,只是当设备资源可用时,它才向应用层发信号。而不能直接调用应用层注册的回调函数,并且发信号的操作也是驱动程序自身来完成的
异步通知是驱动主动通知应用程序,再有应用程序来发起访问

2.应用层异步通知实现的步骤(异步通知用户层代码):
1.注册信号处理函数:相当于注册中断处理函数
2.打开设备文件,设置文件属主:目的是驱动打开file结构,找到对应的进程,从而向该进程发送信号
3.设置设备资源可用时驱动向进程发送的信号(非必须步骤)
4.设置文件的FASYNC标志,使能异步通知机制,这相当于打开中断使能位。

3.异步通知驱动层操作(五种IO模型驱动代码)
1.构造struct faync_struct链表的头。
2.实现fasync接口函数,调用fasync_helper函数来构造struct fasync_struct节点,并加入链表。
3.在资源可用时,调用kill_fasync发送信号,并设置资源可用类型是可读还是可写。
4.在文件最后一次关闭时,即在release接口中,需要显示调用驱动实现fasync接口函数,将节点从链表中删除,这样进程就不会在接收到信号。

8.相关代码

8.1非阻塞式IO用户层代码

	int fd;
	fd = open("/dev/vser0", O_RDWR | O_NONBLOCK);//返回值为文件标识符

8.2多路复用用户层代码

	struct pollfd fds[2];
	//fd:要监听的文件描述符	
	//events:监听的事件
	//revents:返回的事件
		//非阻塞方式打开两个设备
	fds[0].fd = open("/dev/vser0", O_RDWR | O_NONBLOCK);
	if (fds[0].fd == -1) 
		goto fail;
	fds[0].events = POLLIN;
	fds[0].revents = 0;

	fds[1].fd = open("/dev/input/event1", O_RDWR | O_NONBLOCK);
	if (fds[1].fd == -1) 
		goto fail;
	fds[1].events = POLLIN;
	fds[1].revents = 0;

	while (1) {
	   //poll系统调用
		ret = poll(fds, 2, -1);
		if (ret == -1)
			goto fail;
		//确定那个设备
		if (fds[0].revents & POLLIN) {
			ret = read(fds[0].fd, rbuf, sizeof(rbuf));
			if (ret < 0)
				goto fail;
			puts(rbuf);
		}

		if (fds[1].revents & POLLIN) {
			ret = read(fds[1].fd, &key, sizeof(key));
			if (ret < 0)
				goto fail;
			}
		}
	}

8.3异步IO用户层代码

//异步I/O 涉及到aiocb 异步I/O控制块
void aiow_completion_handler(sigval_t sigval)
{
	int ret;
	struct aiocb *req;

	req = (struct aiocb *)sigval.sival_ptr;
//aio_error 成功时返回0代表异步工作成功
	if (aio_error(req) == 0) {
		//aio_return 成功时返回aio_read在被成功调用时可能返回的结果
		ret = aio_return(req);
		printf("aio write %d bytes\n", ret);
	}

	return;
}
    int ret;
	int fd;
	struct aiocb aiow, aior;

	fd = open("/dev/vser0", O_RDWR);
	if (fd == -1) 
		goto fail;

	memset(&aiow, 0, sizeof(aiow));
	memset(&aior, 0, sizeof(aior));

    //初始化一个异步I/O控制块
	//异步I/O控制块的文件标识符
	aiow.aio_fildes = fd;
	//写缓冲区
	aiow.aio_buf = malloc(32);
	strcpy((char *)aiow.aio_buf, "aio test");
	//写字节数
	aiow.aio_nbytes = strlen((char *)aiow.aio_buf) + 1;
	//偏移量,表示读或写操作的起始位置
	aiow.aio_offset = 0;
	//sigev_notify字段定义了通知的方式
	aiow.aio_sigevent.sigev_notify = SIGEV_THREAD;
	//通知方式设置为"线程通知" (SIGEV_THREAD)时新线程的线程函数(回调函数)
	aiow.aio_sigevent.sigev_notify_function = aiow_completion_handler;
	//通知方式设置为"线程通知" (SIGEV_THREAD)时新线程的属性 ,默认NULL
	aiow.aio_sigevent.sigev_notify_attributes = NULL;
	//伴随通知所传递的数据,是个枚举sival_int以及sival_ptr的一个
	aiow.aio_sigevent.sigev_value.sival_ptr = &aiow;
   
    //初始化aior 已省略
    
	while (1) {
		if (aio_write(&aiow) == -1)
			goto fail;
		if (aio_read(&aior) == -1)
			goto fail;
		sleep(1);
	}

8.4异步通知用户层代码

	int ret;
	int flag;
	struct sigaction act, oldact;
	//初始化sigaction结构
	sigemptyset(&act.sa_mask);
	sigaddset(&act.sa_mask, SIGIO);
	act.sa_flags = SA_SIGINFO;
	act.sa_sigaction = sigio_handler;
	//1.注册异步通知 信号处理函数
	if (sigaction(SIGIO, &act, &oldact) == -1)
		goto fail;
	
	fd = open("/dev/vser0", O_RDWR | O_NONBLOCK);
	if (fd == -1) 
		goto fail;
    //2.获取打开的文件的属主,目的是将进程信息保存在file中,使驱动能向进程发送信号
	if (fcntl(fd, F_SETOWN, getpid()) == -1)
		goto fail;
	//3.设置驱动要发送的信号
	if (fcntl(fd, F_SETSIG, SIGIO) == -1)
		goto fail;
	//4.设置文文件的FASYNC标志
	//4.1先获取文件的标志
	if ((flag = fcntl(fd, F_GETFL)) == -1)
		goto fail;
	//4.2再设置文件FASYNC标志,使能异步通知
	if (fcntl(fd, F_SETFL, flag | FASYNC) == -1)
		goto fail;

	while (1)
		sleep(1);
int fd;
//信号处理函数 
void sigio_handler(int signum, siginfo_t *siginfo, void *act)
{
	int ret;
	char buf[32];
	//发送的信号判断
	if (signum == SIGIO) {
		//siginfo->si_band 用来判断是读还是写
		if (siginfo->si_band & POLLIN) {
			printf("FIFO is not empty\n");
			if ((ret = read(fd, buf, sizeof(buf))) != -1) {
				buf[ret] = '\0';
				puts(buf);
			}
		}
		if (siginfo->si_band & POLLOUT)
			printf("FIFO is not full\n");
	}
}

8.5五种IO模型驱动代码

#include 
#include 
#include 

#include 
#include 
#include 

#include 
#include 

#include 
#include 
#include 
#include 

#include "vser.h"

#define VSER_MAJOR	256
#define VSER_MINOR	0
#define VSER_DEV_CNT	1
#define VSER_DEV_NAME	"vser"

struct vser_dev {
	unsigned int baud;
	struct option opt;
	struct cdev cdev;
	//阻塞式以及I/O多路复用 等待队列头 
	wait_queue_head_t rwqh;
	wait_queue_head_t wwqh;
	//异步通知 fasync_struct链表
	struct fasync_struct *fapp;
};

DEFINE_KFIFO(vsfifo, char, 32);
static struct vser_dev vsdev;

static int vser_fasync(int fd, struct file *filp, int on);

static int vser_open(struct inode *inode, struct file *filp)
{
	return 0;
}

static int vser_release(struct inode *inode, struct file *filp)
{
	vser_fasync(-1, filp, 0);
	return 0;
}

static ssize_t vser_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
	int ret;
	unsigned int copied = 0;
	//是否有可用资源判断
	if (kfifo_is_empty(&vsfifo)) {
	//非阻塞式
		if (filp->f_flags & O_NONBLOCK)
			return -EAGAIN;
	//阻塞式,等待队列可被外部中断唤醒,且只唤醒这个进程
		if (wait_event_interruptible_exclusive(vsdev.rwqh, !kfifo_is_empty(&vsfifo)))
			return -ERESTARTSYS;
	}

	ret = kfifo_to_user(&vsfifo, buf, count, &copied);
    //阻塞式,如何fifo没有满,唤醒写操作
	if (!kfifo_is_full(&vsfifo)) {
		//阻塞式,中断唤醒等待队列
		wake_up_interruptible(&vsdev.wwqh);
		//异步通知 向应用程序发送可以写的信号
		kill_fasync(&vsdev.fapp, SIGIO, POLL_OUT);
	}

	return ret == 0 ? copied : ret;
}

static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{

	int ret;
	unsigned int copied = 0;
	//是否有可用资源判断
	if (kfifo_is_full(&vsfifo)) {
	//非阻塞式
		if (filp->f_flags & O_NONBLOCK)
			return -EAGAIN;
	//阻塞式,等待队列可被外部中断唤醒,且只唤醒这个进程
		if (wait_event_interruptible_exclusive(vsdev.wwqh, !kfifo_is_full(&vsfifo)))
			return -ERESTARTSYS;
	}

	ret = kfifo_from_user(&vsfifo, buf, count, &copied);
	//阻塞式,如何fifo没有空,唤醒读操作
	if (!kfifo_is_empty(&vsfifo)) {
		//阻塞式,中断唤醒等待队列
		wake_up_interruptible(&vsdev.rwqh);
		//异步通知 向应用程序发送可以读的信号
		kill_fasync(&vsdev.fapp, SIGIO, POLL_IN);
	}

	return ret == 0 ? copied : ret;
}

//ioctl系统调用
static long vser_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	//幻术判断,判断是否是当前设备
	if (_IOC_TYPE(cmd) != VS_MAGIC)
		return -ENOTTY;
	//命令解析
	switch (cmd) {
	case VS_SET_BAUD:
		vsdev.baud = arg;
		break;
	case VS_GET_BAUD:
		arg = vsdev.baud;
		break;
	case VS_SET_FFMT:
	//copy_from_user与copy_to_user返回的都是未复制的字节数,全部复制返回0
		if (copy_from_user(&vsdev.opt, (struct option __user *)arg, sizeof(struct option)))
			return -EFAULT;
		break;
	case VS_GET_FFMT:
		if (copy_to_user((struct option __user *)arg, &vsdev.opt, sizeof(struct option)))
			return -EFAULT;
		break;
	default:
		return -ENOTTY;
	}

	return 0;
}

//poll多路复用
static unsigned int vser_poll(struct file *filp, struct poll_table_struct *p)
{
	int mask = 0;
	//将构造的等待队列节点加入到等待队列中
	/************************ poll_wait函数定义如下 **********************/
	/* static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
     * {
	 *     if (p && wait_address)
	 *         p->qproc(filp, wait_address, p);
     *         //本质调用的是__pollwait(filp, wait_address, p);这个函数
	 * }
	 */
	/***********************************************************************/	
	/* p->qproc 是一个函数指针,指向 __pollwait函数。
	 * __pollwait 做的事情也就是将当前的进程添加到 button_waitq 等待队列中。
	 * 这里只是添加到等待队列而已,并不会立即休眠。
	  */

	poll_wait(filp, &vsdev.rwqh, p);
	poll_wait(filp, &vsdev.wwqh, p);

	//根据不同的可用资源判断进行读写操作
	if (!kfifo_is_empty(&vsfifo))
		mask |= POLLIN | POLLRDNORM;
	if (!kfifo_is_full(&vsfifo))
		mask |= POLLOUT | POLLWRNORM;

	return mask;
}
//异步I/O,读操作  驱动操作完成后,调用者会得到通知,通知是由内核完成的
static ssize_t vser_aio_read(struct kiocb *iocb, const struct iovec *iov, unsigned long nr_segs, loff_t pos)
{
	size_t read = 0;
	unsigned long i;
	ssize_t ret;

	for (i = 0; i < nr_segs; i++) {
		ret = vser_read(iocb->ki_filp, iov[i].iov_base, iov[i].iov_len, &pos);
		if (ret < 0)
			break;
		read += ret;
	}

	return read ? read : -EFAULT;
}
//异步I/O,写操作
static ssize_t vser_aio_write(struct kiocb *iocb, const struct iovec *iov, unsigned long nr_segs, loff_t pos)
{
	size_t written = 0;
	unsigned long i;
	ssize_t ret;

	for (i = 0; i < nr_segs; i++) {
		ret = vser_write(iocb->ki_filp, iov[i].iov_base, iov[i].iov_len, &pos);
		if (ret < 0)
			break;
		written += ret;
	}

	return written ? written : -EFAULT;
}

static int vser_fasync(int fd, struct file *filp, int on)
{
	//异步通知 构造fasync_struct节点,并加入链表
	return fasync_helper(fd, filp, on, &vsdev.fapp);
}

static struct file_operations vser_ops = {
	.owner = THIS_MODULE,
	.open = vser_open,
	.release = vser_release,
	.read = vser_read,
	.write = vser_write,
	.unlocked_ioctl = vser_ioctl,
	.poll = vser_poll,
	.aio_read = vser_aio_read,
	.aio_write = vser_aio_write,
	.fasync = vser_fasync,       //异步通知函数指针
};

static int __init vser_init(void)
{
	int ret;
	dev_t dev;

	dev = MKDEV(VSER_MAJOR, VSER_MINOR);
	ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
	if (ret)
		goto reg_err;

	cdev_init(&vsdev.cdev, &vser_ops);
	vsdev.cdev.owner = THIS_MODULE;
	vsdev.baud = 115200;
	vsdev.opt.datab = 8;
	vsdev.opt.parity = 0;
	vsdev.opt.stopb = 1;

	ret = cdev_add(&vsdev.cdev, dev, VSER_DEV_CNT);
	if (ret)
		goto add_err;
	//初始化等待队列的队列头
	init_waitqueue_head(&vsdev.rwqh);
	init_waitqueue_head(&vsdev.wwqh);


	return 0;

add_err:
	unregister_chrdev_region(dev, VSER_DEV_CNT);
reg_err:
	return ret;
}

static void __exit vser_exit(void)
{
	dev_t dev;

	dev = MKDEV(VSER_MAJOR, VSER_MINOR);

	cdev_del(&vsdev.cdev);
	unregister_chrdev_region(dev, VSER_DEV_CNT);
}

module_init(vser_init);
module_exit(vser_exit);

9.mmap文件操作

1.针对的问题:显卡有显存的设备,驱动程序将显存映射到内核的地址空间,用户想在屏幕绘制时,要在用户空间开辟一样大的内存。为了消除则这个复制操作,应用程序要直接访问显存,但显存的映射在内核空间,应用程序没有权限:
2.mmap接口作用:将内核空间的内存的物理地址空间再次映射到用户空间,一个物理内存有两个映射,可以通过直接操作用户空间的这片映射之后的内存来直接访问物理内存,从而提高效率。进行大量数据传递

相关函数API

//映射应用程序调用
void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offset);
	 start:用户所要映射的目的地址,一般放NULL,由系统找。
	 length:申请的长度
	 prot:映射区保护方式,取值范围	
		 PROT_EXEC:映射区可执行      PROT_READ:映射区可读取
	          PROT_WRITE:映射区可写入    PROT_NONE:映射区不可存取
	         如果要几个功能合在一起,用管道符 | 连通
	flags:对映射对象的配置
		 MAP_SHARED:共享映射区           MAP_PRIVATE:读时共享,写时复制,对映射区的操作不会对原文件造成影响
		 MAP_ANONYMOUS:建立匿名映射,不涉及文件,所以用不到fd,也不允许与其他进程共享
		 MAP_DENYWRITE:对文件的写操作将被禁止,只能通过映射文件对原文件进行操作
		 MAP_LOCKED:将映射区锁定,不会被虚拟内存重置
	fd:代表文件的文件描述符。
	offset:偏移量。一般设为0,表示从头开始映射。
	返回值:返回被映射区的指针
//解除映射函数
int munmap( void * addr, size_t len )
	addr是调用mmap()时返回的地址
	len是映射区的大小
//驱动中mmap接口实现
int remap_pfn_range(struct vm_area_struct *vma,unsigned long addr,unsigned long pfn,unsigned long size,pgprot_t port);
	truct vm_area_struct *vma: 描述一片映射区域的结构指针
	unsigned long addr: 用户指定的映射之后的虚拟起始地址,若用户未指定则会有内核来指定。
	unsigned long pfn, 物理内存所对应的页框号,就是将物理 \地址除以页大小得到的值
	unsigned long size: 想要映射的空间的大小。
	pgprot_t port:内存区域的访问权限

10.其它

//从用户空间获取数据
unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n);
//往用户空间发送数据
unsigned long __must_check copy_to_user(void *to, const void __user *from, unsigned long n);

	to :内核空间缓冲区地址
	from:是用户空间地址
	n :数据字节数
	返回值:未复制成功的字节数
	 __must_check :要求必须检查函数返回值
	_user: 说明内核空间要使用用户空间的数据
	void*:允许其它任何指针赋值
这两个函数不使用memcpy是因为这两个函数调用access_ok来验证用户空间内存是否可读写,避免缺页故障的问题。
这两个函数会使进程休眠,简单复制可以使用
put_user()可以向用户空间传递单个数据。
get_user()可以从用户空间获取单个数据
int put_user(x,p)
int get_user(x,p)
	x为内核空间的数据
	p为用户空间的指针
	返回值:获取成功,返回0

单个数据并不是指一个字节数据,对ARM而言, put_user一次性可传递一个char , short或者int型的数据,12或者4字节

• IO调用:应用程序进程向操作系统内核发起调用。
IO执行:操作系统内核完成IO操作,对于驱动设备来说就是寻找指定设备驱动
《嵌入式Linux驱动开发教程》--高级I/O操作_第6张图片

学习参考
https://blog.csdn.net/qq_41453285/article/details/89423794
https://blog.csdn.net/u013511885/article/details/123922598
https://zhuanlan.zhihu.com/p/577599879
https://juejin.cn/post/7036518015462015006
https://zhuanlan.zhihu.com/p/270010926
https://blog.csdn.net/uestcprince/article/details/90734564
https://www.cnblogs.com/yuanqiangfei/p/15735530.html

你可能感兴趣的:(Linux驱动开发,驱动开发,linux,运维)