linux内核分析--异步io(二)

简介:

该分析sys_io_submit函数了,这个函数有点复杂,但是条理很清晰,先说一句就是提交异步io,具体怎么提交呢?我们知道,对于异步io,一次性可以提交多个请求,那么可以想象的就是在sys_io_submit中会把我们用户程序的多个请求分解成一个一个的请求,依次提交,这是很合理的假设,内核实际上也是这么做的,刚才的建立异步io的阶段只是建立了一个可以让异步io表演的大的环境,现在的提交请求和将来的读取数据便是大戏了,准备好了吗?马上开演!

asmlinkage long sys_io_submit(aio_context_t ctx_id, long nr,
struct iocb __user * __user *iocbpp)
{
struct kioctx *ctx;
long ret = 0;
int i;
if (unlikely(nr < 0))
return -EINVAL;
if (unlikely(!access_ok(VERIFY_READ, iocbpp, (nr*sizeof(*iocbpp)))))
return -EFAULT;
ctx = lookup_ioctx(ctx_id);//这里查找我们刚才建立的kioctx
if (unlikely(!ctx)) {

for (i=0; i<nr; i++) {//这个循环实质上分解了用户请求
struct iocb __user *user_iocb;
struct iocb tmp;
if (unlikely(__get_user(user_iocb, iocbpp + i))) {
ret = -EFAULT;
break;
}
if (unlikely(copy_from_user(&tmp, user_iocb, sizeof(tmp))))


ret = io_submit_one(ctx, user_iocb, &tmp);//一次提交一个,直到全部提交完毕
if (ret)
break;
}
put_ioctx(ctx);
return i ? i : ret;
}

上面的函数提到了查找kioctx结构,这个函数猜也能猜到怎么实现的,就是在进程的mm中的ioctx_list中寻找id和sunmit参数的id相 同的kioctx,说了半天,到底什么是kioctx,它到底有什么用?它实际上正如前面说的那样,是一个大舞台,提供了一个工作体,提供了一个运行队列 等等,所有属于这个大舞台的请求必须纳入它的管理范畴,它的字段决定了谁应该被调度,以及调度后应该做甚。下面来看看完成实际工作的io_submit_one函数:

1476 int fastcall io_submit_one(struct kioctx *ctx, struct iocb __user *user_iocb,
struct iocb *iocb)
{
struct kiocb *req;
struct file *file;
ssize_t ret;
/* enforce forwards compatibility on users */
if (unlikely(iocb->aio_reserved1 || iocb->aio_reserved2 ||

/* prevent overflows */
if (unlikely(
(iocb->aio_buf != (unsigned long)iocb->aio_buf) ||
(iocb->aio_nbytes != (size_t)iocb->aio_nbytes) ||
((ssize_t)iocb->aio_nbytes < 0)
)) {

file = fget(iocb->aio_fildes);
req = aio_get_req(ctx); /* returns with 2 references to req */
if (unlikely(!req)) {

req->ki_filp = file;
ret = put_user(req->ki_key, &user_iocb->aio_key);
if (unlikely(ret)) {
dprintk("EFAULT: aio_key/n");
goto out_put_req;
}
req->ki_obj.user = user_iocb;
req->ki_user_data = iocb->aio_data;
req->ki_pos = iocb->aio_offset;
req->ki_buf = (char __user *)(unsigned long)iocb->aio_buf;
req->ki_left = req->ki_nbytes = iocb->aio_nbytes;
req->ki_opcode = iocb->aio_lio_opcode;
init_waitqueue_func_entry(&req->ki_wait, aio_wake_function);//注册唤醒时的回调函数
INIT_LIST_HEAD(&req->ki_wait.task_list);
req->ki_retried = 0;
ret = aio_setup_iocb(req);//设置retry回调函数
if (ret)
goto out_put_req;
spin_lock_irq(&ctx->ctx_lock);
aio_run_iocb(req);//首先先执行一次,没准一次就能成功,要不怕做无用功,内核真是精打细算啊
if (!list_empty(&ctx->run_list)) {
/* drain the run list */
while (__aio_run_iocbs(ctx))//如果当前异步上下文的运行队列有请求,执行之!
;
}
spin_unlock_irq(&ctx->ctx_lock);
aio_put_req(req); /* drop extra ref to req */
return 0;

}
我们来看看aio_run_iocb函数,实际上__aio_run_iocbs(ctx)最终也是要调用aio_run_iocb函数的:
static ssize_t aio_run_iocb(struct kiocb *iocb)
{
struct kioctx *ctx = iocb->ki_ctx;
ssize_t (*retry)(struct kiocb *);
ssize_t ret;
if (iocb->ki_retried++ > 1024*1024) {

}
if (!(iocb->ki_retried & 0xff)) {
}
if (!(retry = iocb->ki_retry)) {

}
kiocbClearKicked(iocb);
iocb->ki_run_list.next = iocb->ki_run_list.prev = NULL;
spin_unlock_irq(&ctx->ctx_lock);
/* Quit retrying if the i/o has been cancelled */
if (kiocbIsCancelled(iocb)) {

}
BUG_ON(current->io_wait != NULL);
current->io_wait = &iocb->ki_wait;//以下3行很重要,如果当前请求未果,则可能睡眠在io_wait,当被唤醒的时候执行aio_wake_function
ret = retry(iocb);
current->io_wait = NULL;
if (ret != -EIOCBRETRY && ret != -EIOCBQUEUED) {

}

}
注意aio_wake_function是唤醒回调函数,这个函数本质上也是执行将上下文挂入工作队列的任务,为什么呢?为何不让它直接把任务完成呢?因为它可能在中断上下文中,这又很多限制,比如不能睡眠,于是乎就把任务挂入一个工作队列,这样就有了进程的上下文,一切变得明朗!到这基本上就完事了,不用用户进程操心了,到了实在闲来无事的时候来取数据吧!如果实在觉得这篇文章到此意尤未尽,那么请看《linux工作队列和异步io 》下面将要进行的就是取数据了。一会回来,更精彩!



本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1274055

相关文章
|
3月前
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
187 67
|
1月前
|
Linux C语言 网络架构
Linux的基础IO内容补充-FILE
而当我们将运行结果重定向到log.txt文件时,数据的刷新策略就变为了全缓冲,此时我们使用printf和fwrite函数打印的数据都打印到了C语言自带的缓冲区当中,之后当我们使用fork函数创建子进程时,由于进程间具有独立性,而之后当父进程或是子进程对要刷新缓冲区内容时,本质就是对父子进程共享的数据进行了修改,此时就需要对数据进行写时拷贝,至此缓冲区当中的数据就变成了两份,一份父进程的,一份子进程的,所以重定向到log.txt文件当中printf和fwrite函数打印的数据就有两份。此时我们就可以知道,
33 0
|
1月前
|
存储 Linux Shell
Linux的基础IO
那么,这里我们温习一下操作系统的概念我们在Linux平台下运行C代码时,C库函数就是对Linux系统调用接口进行的封装,在Windows平台下运行C代码时,C库函数就是对Windows系统调用接口进行的封装,这样做使得语言有了跨平台性,也方便进行二次开发。这就是因为在根本上操作系统确实像银行一样,并不完全信任用户程序,因为直接开放底层资源(如内存、磁盘、硬件访问权限)给用户程序会带来巨大的风险。所以就向银行一样他的服务是由工作人员隔着一层玻璃,然后对顾客进行服务的。
34 0
|
1月前
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
41 0
|
3月前
|
存储 Linux
Linux内核中的current机制解析
总的来说,current机制是Linux内核中进程管理的基础,它通过获取当前进程的task_struct结构的地址,可以方便地获取和修改进程的信息。这个机制在内核中的使用非常广泛,对于理解Linux内核的工作原理有着重要的意义。
143 11
|
4月前
|
监控 Linux
Linux基础:文件和目录类命令分析。
总的来说,这些基础命令,像是Linux中藏匿的小矮人,每一次我们使用他们,他们就把我们的指令准确的传递给Linux,让我们的指令变为现实。所以,现在就开始你的Linux之旅,挥动你的命令之剑,探索这个充满神秘而又奇妙的世界吧!
108 19
|
4月前
|
自然语言处理 监控 Linux
Linux 内核源码分析---proc 文件系统
`proc`文件系统是Linux内核中一个灵活而强大的工具,提供了一个与内核数据结构交互的接口。通过本文的分析,我们深入探讨了 `proc`文件系统的实现原理,包括其初始化、文件的创建与操作、动态内容生成等方面。通过对这些内容的理解,开发者可以更好地利用 `proc`文件系统来监控和调试内核,同时也为系统管理提供了便利的工具。
170 16
|
5月前
|
存储 网络协议 Linux
【Linux】进程IO|系统调用|open|write|文件描述符fd|封装|理解一切皆文件
本文详细介绍了Linux中的进程IO与系统调用,包括 `open`、`write`、`read`和 `close`函数及其用法,解释了文件描述符(fd)的概念,并深入探讨了Linux中的“一切皆文件”思想。这种设计极大地简化了系统编程,使得处理不同类型的IO设备变得更加一致和简单。通过本文的学习,您应该能够更好地理解和应用Linux中的进程IO操作,提高系统编程的效率和能力。
217 34
|
5月前
|
缓存 网络协议 Linux
PCIe 以太网芯片 RTL8125B 的 spec 和 Linux driver 分析备忘
本文详细介绍了 Realtek RTL8125B PCIe 以太网芯片的规格以及在 Linux 中的驱动安装和配置方法。通过深入分析驱动源码,可以更好地理解其工作原理和优化方法。在实际应用中,合理配置和优化驱动程序可以显著提升网络性能和稳定性。希望本文能帮助您更好地使用和管理 RTL8125B,以满足各种网络应用需求。
430 33
|
5月前
|
数据管理 Linux iOS开发
Splunk Enterprise 9.4.1 (macOS, Linux, Windows) 发布 - 机器数据管理和分析
Splunk Enterprise 9.4.1 (macOS, Linux, Windows) 发布 - 机器数据管理和分析
81 0
Splunk Enterprise 9.4.1 (macOS, Linux, Windows) 发布 - 机器数据管理和分析