linux内核分析–异步io(三)

用户建立了异步io环境,并且提交了异步io请求,该做的都做了,剩下的就是结果了,人生漂泊,有因无果,结果真的重要吗?务实一点说,重要,真正不在乎结果的人又有几个呢?人尤如此,内核就更不用说了,我拿到钱大把大把的花,等到请客吃饭时,囊中羞涩,这也是一种务实--肥水不流外人田;我有了时间,大把大把浪费,等到考试或考核时,总在呐喊:再多一秒吧!linux内核是这样的吗?很抱歉,不是!我觉得它是世界上最吝啬的了,不花一分冤枉钱,啥时候 windows也能这么吝啬就好了,我们就不用攒几个月的工资来买一块显卡仅仅为了玩一个几个月后就过时的游戏了,君不见Linux跑在奔二128m内存 机器上,天马行空,windows在酷睿1g内存机器依然蠕动,钞票啊!好了,不扯犊子了,第三场,开始!
为了得到结果,用户应该调用io_getevents库函数,该函数进行sys_io_getevents系统调用,实际上不用分析代码也能猜个八九不离十,但是分析该系统调用前,必须先看一眼aio_complete函数,毕竟,得有人把数据放到一个地方你才可以读,而aio_complete函数就是 做这个的:

int fastcall aio_complete(struct kiocb *iocb, long res, long res2)
{
struct kioctx *ctx = iocb->ki_ctx;
struct aio_ring_info *info;
struct aio_ring *ring;
struct io_event *event;
unsigned long flags;
unsigned long tail;
int ret;
if (is_sync_kiocb(iocb)) {
BUG_ON(iocb->ki_users != 1);
iocb->ki_user_data = res;
iocb->ki_users = 0;
wake_up_process(iocb->ki_obj.tsk);
return 1;
}
info = &ctx->ring_info;//还记得吗?在setup_ring中事情
spin_lock_irqsave(&ctx->ctx_lock, flags);
if (iocb->ki_run_list.prev && !list_empty(&iocb->ki_run_list))
list_del_init(&iocb->ki_run_list);
if (kiocbIsCancelled(iocb))
goto put_rq;
ring = kmap_atomic(info->ring_pages[0], KM_IRQ1);
tail = info->tail;
event = aio_ring_event(info, tail, KM_IRQ0);//这是个宏,用来临时将从用户虚存区间得到的物理页面影射到高端,这么做为了往里面写数据,毕竟现代操作系统内核只认虚拟地址
if (++tail >= info->nr)
tail = 0;
event->obj = (u64)(unsigned long)iocb->ki_obj.user;
event->data = iocb->ki_user_data;
event->res = res;
event->res2 = res2;
smp_wmb(); /* make event visible before updating tail */
info->tail = tail;
ring->tail = tail;
put_aio_ring_event(event, KM_IRQ0);//写完了,去映射,高端映射区间资源很宝贵,不要长期占用就好,都自觉点就行,内核并没有强制!
kunmap_atomic(ring, KM_IRQ1);
pr_debug("added to ring %p at [%lu]/n", iocb, tail);
pr_debug("%ld retries: %d of %d/n", iocb->ki_retried,
iocb->ki_nbytes - iocb->ki_left, iocb->ki_nbytes);
put_rq:
/* everything turned out well, dispose of the aiocb. */
ret = __aio_put_req(ctx, iocb);
spin_unlock_irqrestore(&ctx->ctx_lock, flags);
if (waitqueue_active(&ctx->wait))//在sys_io_getevents中可能当下没有数据的时候要睡眠,现在有了,唤醒吧!
wake_up(&ctx->wait);
if (ret)
put_ioctx(ctx);
return ret;
}
现在内核已经把数据放到一个地方了,就是info->ring_pages[0],就等着有人拿了,如果通读了代码,很多人不禁要问,内存直接申请一块不就的了,为什么还有映射来映射去的,难道不麻烦吗?其实,调用这个完成函数的进程根本就可能不是我们的用户进程,前面说过,所有的请求是放到工作队列中的,而工作队列是有自己的进程上下文的,所以就应该在请求者的地址空间申请内存,然后得到物理页,物理页面并不是进程级别的,所以可以随意操作。下面该真正的sys_io_getevents了:
asmlinkage long sys_io_getevents(aio_context_t ctx_id,
long min_nr,
long nr,
struct io_event __user *events,
struct timespec __user *timeout)
{
struct kioctx *ioctx = lookup_ioctx(ctx_id);//轻车熟路!!
long ret = -EINVAL;
if (likely(ioctx)) {
if (likely(min_nr <= nr && min_nr >= 0 && nr >= 0))
ret = read_events(ioctx, min_nr, nr, events, timeout);
put_ioctx(ioctx);
}
return ret;
}
/
static int read_events(struct kioctx *ctx,
long min_nr, long nr,
struct io_event __user *event,
struct timespec __user *timeout)
{
long start_jiffies = jiffies;
struct task_struct *tsk = current;
DECLARE_WAITQUEUE(wait, tsk);
int ret;
int i = 0;
struct io_event ent;
struct aio_timeout to;
int retry = 0;
memset(&ent, 0, sizeof(ent));
retry:
ret = 0;
while (likely(i < nr)) {
ret = aio_read_evt(ctx, &ent);//真正的读取
if (unlikely(ret <= 0))
break;

/* Could we split the check in two? */
ret = -EFAULT;
if (unlikely(copy_to_user(event, &ent, sizeof(ent)))) {
break;
}
ret = 0;
/* Good, event copied to userland, update counts. */
event ++;
i ++;
}
if (min_nr <= i)
return i;
if (ret)
return ret;
if (!retry && unlikely(!list_empty(&ctx->run_list))) {
retry = 1;
aio_run_all_iocbs(ctx);
goto retry;
}
init_timeout(&to);
if (timeout) {
struct timespec ts;
ret = -EFAULT;
if (unlikely(copy_from_user(&ts, timeout, sizeof(ts))))
goto out;
set_timeout(start_jiffies, &to, &ts);
}
while (likely(i < nr)) {
add_wait_queue_exclusive(&ctx->wait, &wait);
do {
set_task_state(tsk, TASK_INTERRUPTIBLE);//没有数据,睡眠!
ret = aio_read_evt(ctx, &ent);
if (ret)
break;
if (min_nr <= i)
break;
ret = 0;
if (to.timed_out) /* Only check after read evt */
break;
schedule();
if (signal_pending(tsk)) {
ret = -EINTR;
break;
}
/*ret = aio_read_evt(ctx, &ent);*/
} while (1) ;
set_task_state(tsk, TASK_RUNNING);//被唤醒,肯定有了数据
remove_wait_queue(&ctx->wait, &wait);
if (unlikely(ret <= 0))
break;
ret = -EFAULT;
if (unlikely(copy_to_user(event, &ent, sizeof(ent)))) {
dprintk("aio: lost an event due to EFAULT./n");
break;
}
/* Good, event copied to userland, update counts. */
event ++;
i ++;
}
if (timeout)
clear_timeout(&to);
out:
return i ? i : ret;
}
最后看一下aio_read_evt函数,这个函数把所有虚伪的东西落到实处:
static int aio_read_evt(struct kioctx *ioctx, struct io_event *ent)
{
struct aio_ring_info *info = &ioctx->ring_info;
struct aio_ring *ring;
unsigned long head;
int ret = 0;
ring = kmap_atomic(info->ring_pages[0], KM_USER0);
if (ring->head == ring->tail)
goto out;
spin_lock(&info->ring_lock);
head = ring->head % info->nr;
if (head != ring->tail) {
struct io_event *evp = aio_ring_event(info, head, KM_USER1);
*ent = *evp;
head = (head + 1) % info->nr;
smp_mb(); /* finish reading the event before updatng the head */
ring->head = head;
ret = 1;
put_aio_ring_event(evp, KM_USER1);
}
spin_unlock(&info->ring_lock);

}
这下脉络就很清晰了,到这里我想到了一些东西,我读内核的过程中发现,内核函数基本可以分为两种,一种为管理函数,一种为操作函数,管理函数主要涉及到很多很复杂的数据结构,可谓变态级别,而操作函数一般一目了然,比如写寄存器,就一个writeX函数,由此想到了我们的社会何尝不是如此,管理机关机构臃肿,人员闲杂,而劳动人民则是那么的淳朴......
愿好人一生平安!

原文链接: https://blog.csdn.net/dog250/article/details/5303049

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

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

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/410501

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月26日 下午12:03
下一篇 2023年4月26日 下午12:03

相关推荐