Major cleanup and debugging of the new AIO/LIO code. The LIO code is

now corrected.  New tunables/instrumentation added.  The code is now
likely "good enough to use."  I will add the userland support soon.
The "high performance" mode for raw devices is still missing, and will
be added next.  POSIX system calls that now appear to work:
aio_cancel, aio_error, aio_read, aio_return, aio_suspend, aio_write,
lio_listio.  Missing, but to be added soon: aio_fsync.
This commit is contained in:
John Dyson 1997-10-09 04:14:41 +00:00
parent 31ef058922
commit a624e84fff
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=30240

View File

@ -13,7 +13,7 @@
* bad that happens because of using this software isn't the responsibility
* of the author. This software is distributed AS-IS.
*
* $Id: vfs_aio.c,v 1.3 1997/07/17 04:49:31 dyson Exp $
* $Id: vfs_aio.c,v 1.4 1997/09/02 20:06:00 bde Exp $
*/
/*
@ -42,6 +42,7 @@
#include <sys/uio.h>
#include <sys/malloc.h>
#include <sys/signalvar.h>
#include <sys/sysctl.h>
#include <vm/vm.h>
#include <vm/vm_param.h>
@ -63,6 +64,8 @@
#define DIAGNOSTIC
#endif
#define DEBUGAIO 1
static int jobrefid;
#define JOBST_NULL 0x0
@ -77,6 +80,44 @@ static int jobrefid;
#define MAX_AIO_QUEUE 1024 /* Bigger than AIO_LISTIO_MAX */
#define TARGET_AIO_PROCS 64
int max_aio_procs = MAX_AIO_PROCS;
int num_aio_procs = 0;
int target_aio_procs = TARGET_AIO_PROCS;
int max_queue_count = MAX_AIO_QUEUE;
int num_queue_count = 0;
int max_aio_per_proc = MAX_AIO_PER_PROC,
max_aio_queue_per_proc=MAX_AIO_QUEUE_PER_PROC;
SYSCTL_NODE(_vfs, OID_AUTO, aio, CTLFLAG_RW, 0, "AIO mgmt");
SYSCTL_INT(_vfs_aio, OID_AUTO, max_aio_per_proc,
CTLFLAG_RW, &max_aio_per_proc, 0, "");
SYSCTL_INT(_vfs_aio, OID_AUTO, max_aio_queue_per_proc,
CTLFLAG_RW, &max_aio_queue_per_proc, 0, "");
SYSCTL_INT(_vfs_aio, OID_AUTO, max_aio_procs,
CTLFLAG_RW, &max_aio_procs, 0, "");
SYSCTL_INT(_vfs_aio, OID_AUTO, num_aio_procs,
CTLFLAG_RD, &num_aio_procs, 0, "");
SYSCTL_INT(_vfs_aio, OID_AUTO, num_queue_count,
CTLFLAG_RD, &num_queue_count, 0, "");
SYSCTL_INT(_vfs_aio, OID_AUTO, max_aio_queue,
CTLFLAG_RW, &max_queue_count, 0, "");
SYSCTL_INT(_vfs_aio, OID_AUTO, target_aio_procs,
CTLFLAG_RW, &target_aio_procs, 0, "");
#if DEBUGAIO > 0
static int debugaio;
SYSCTL_INT(_vfs_aio, OID_AUTO, debugaio, CTLFLAG_RW, &debugaio, 0, "");
#endif
/*
* Job queue item
*/
@ -114,12 +155,6 @@ TAILQ_HEAD (,aioproclist) aio_freeproc, aio_activeproc;
TAILQ_HEAD(,aiocblist) aio_jobs; /* Async job list */
TAILQ_HEAD(,aiocblist) aio_freejobs;
int max_aio_procs = MAX_AIO_PROCS;
int num_aio_procs = 0;
int target_aio_procs = TARGET_AIO_PROCS;
int max_queue_count = MAX_AIO_QUEUE;
int num_queue_count = 0;
void aio_init_aioinfo(struct proc *p) ;
void aio_onceonly(void *) ;
@ -133,6 +168,7 @@ static void aio_marksuspend(struct proc *p, int njobs, int *joblist, int set) ;
SYSINIT(aio, SI_SUB_VFS, SI_ORDER_ANY, aio_onceonly, NULL);
/*
* Startup initialization
*/
@ -153,9 +189,9 @@ aio_init_aioinfo(struct proc *p) {
if (p->p_aioinfo == NULL) {
ki = malloc(sizeof (struct kaioinfo), M_AIO, M_WAITOK);
p->p_aioinfo = ki;
ki->kaio_maxactive_count = MAX_AIO_PER_PROC;
ki->kaio_maxactive_count = max_aio_per_proc;
ki->kaio_active_count = 0;
ki->kaio_qallowed_count = MAX_AIO_QUEUE_PER_PROC;
ki->kaio_qallowed_count = max_aio_queue_per_proc;
ki->kaio_queue_count = 0;
TAILQ_INIT(&ki->kaio_jobdone);
TAILQ_INIT(&ki->kaio_jobqueue);
@ -185,12 +221,15 @@ aio_free_entry(struct aiocblist *aiocbe) {
if (aiocbe->jobflags & AIOCBLIST_ASYNCFREE)
return 0;
aiocbe->jobflags |= AIOCBLIST_RUNDOWN;
tsleep(aiocbe, PRIBIO|PCATCH, "jobwai", 0);
/*
if (tsleep(aiocbe, PRIBIO|PCATCH, "jobwai", hz*5)) {
aiocbe->jobflags |= AIOCBLIST_ASYNCFREE;
aiocbe->jobflags &= ~AIOCBLIST_RUNDOWN;
return 1;
}
aiocbe->jobflags &= ~AIOCBLIST_RUNDOWN;
*/
}
aiocbe->jobflags &= ~AIOCBLIST_ASYNCFREE;
@ -201,6 +240,11 @@ aio_free_entry(struct aiocblist *aiocbe) {
--ki->kaio_queue_count;
--num_queue_count;
#if DEBUGAIO > 0
if (debugaio > 0)
printf("freeing entry: %d, %d\n",
ki->kaio_queue_count, num_queue_count);
#endif
if ( aiocbe->jobstate == JOBST_JOBQPROC) {
aiop = aiocbe->jobaioproc;
@ -228,6 +272,17 @@ aio_proc_rundown(struct proc *p) {
if (ki == NULL)
return;
while (ki->kaio_active_count > 0) {
if (tsleep(ki, PRIBIO, "kaiowt", 60 * hz))
break;
}
#if DEBUGAIO > 0
if (debugaio > 0)
printf("Proc rundown: %d %d\n",
num_queue_count, ki->kaio_queue_count);
#endif
restart1:
for ( aiocbe = TAILQ_FIRST(&ki->kaio_jobdone);
aiocbe;
@ -246,6 +301,7 @@ aio_proc_rundown(struct proc *p) {
goto restart2;
}
free(ki, M_AIO);
p->p_aioinfo = NULL;
}
/*
@ -294,14 +350,19 @@ aio_process(struct aiocblist *aiocbe) {
unsigned int fd;
int cnt;
int error;
off_t offset;
userp = aiocbe->userproc;
cb = &aiocbe->uaiocb;
#ifdef DEBUGAIO
printf("fd: %d, offset: 0x%x, address: 0x%x, size: %d\n",
cb->aio_fildes, (int) cb->aio_offset,
cb->aio_buf, cb->aio_nbytes);
#if DEBUGAIO > 0
if (debugaio > 1)
printf("AIO %s, fd: %d, offset: 0x%x, address: 0x%x, size: %d\n",
cb->aio_lio_opcode == LIO_READ?"Read":"Write",
cb->aio_fildes, (int) cb->aio_offset,
cb->aio_buf, cb->aio_nbytes);
#endif
#if SLOW
tsleep(curproc, PVM, "aioprc", hz);
#endif
fdp = curproc->p_fd;
@ -316,7 +377,7 @@ aio_process(struct aiocblist *aiocbe) {
auio.uio_iov = &aiov;
auio.uio_iovcnt = 1;
auio.uio_offset = cb->aio_offset;
auio.uio_offset = offset = cb->aio_offset;
auio.uio_resid = cb->aio_nbytes;
cnt = cb->aio_nbytes;
auio.uio_segflg = UIO_USERSPACE;
@ -338,6 +399,12 @@ aio_process(struct aiocblist *aiocbe) {
psignal(userp, SIGPIPE);
}
}
#if DEBUGAIO > 0
if (debugaio > 1)
printf("%s complete: error: %d, status: %d, nio: %d, resid: %d, offset: %d\n",
cb->aio_lio_opcode == LIO_READ?"Read":"Write",
error, cnt, cnt - auio.uio_resid, auio.uio_resid, (int) offset & 0xffffffff);
#endif
cnt -= auio.uio_resid;
cb->_aiocb_private.error = error;
@ -391,10 +458,11 @@ aio_startproc(void *uproc)
curproc->p_ucred->cr_groups[0] = 1;
curproc->p_flag |= P_SYSTEM;
#ifdef DEBUGAIO
printf("Started new process: %d\n", curproc->p_pid);
#if DEBUGAIO > 0
if (debugaio > 2)
printf("Started new process: %d\n", curproc->p_pid);
#endif
wakeup(uproc);
wakeup(&aio_freeproc);
while(1) {
struct vmspace *myvm, *tmpvm;
@ -406,7 +474,7 @@ aio_startproc(void *uproc)
TAILQ_INSERT_HEAD(&aio_freeproc, aiop, list);
aiop->aioprocflags |= AIOP_FREE;
}
tsleep(curproc, PZERO, "aiordy", 0);
tsleep(cp, PRIBIO, "aiordy", 0);
if (aiop->aioprocflags & AIOP_FREE) {
TAILQ_REMOVE(&aio_freeproc, aiop, list);
TAILQ_INSERT_TAIL(&aio_activeproc, aiop, list);
@ -440,9 +508,23 @@ aio_startproc(void *uproc)
}
ki->kaio_active_count++;
#if DEBUGAIO > 0
if (debugaio > 0)
printf("process: pid: %d(%d), active: %d, queue: %d\n",
cb->_aiocb_private.kernelinfo,
userp->p_pid, ki->kaio_active_count, ki->kaio_queue_count);
#endif
aiocbe->jobaioproc = aiop;
aio_process(aiocbe);
--ki->kaio_active_count;
if (ki->kaio_active_count == 0)
wakeup(ki);
#if DEBUGAIO > 0
if (debugaio > 0)
printf("DONE process: pid: %d(%d), active: %d, queue: %d\n",
cb->_aiocb_private.kernelinfo,
userp->p_pid, ki->kaio_active_count, ki->kaio_queue_count);
#endif
aiocbe->jobstate = JOBST_JOBFINISHED;
@ -501,12 +583,13 @@ aio_newproc() {
cpu_set_fork_handler(p = pfind(rval[0]), aio_startproc, curproc);
#ifdef DEBUGAIO
printf("Waiting for new process: %d, count: %d\n",
curproc->p_pid, num_aio_procs);
#if DEBUGAIO > 0
if (debugaio > 2)
printf("Waiting for new process: %d, count: %d\n",
curproc->p_pid, num_aio_procs);
#endif
error = tsleep(curproc, PZERO, "aiosta", 5*hz);
error = tsleep(&aio_freeproc, PZERO, "aiosta", 5*hz);
++num_aio_procs;
return error;
@ -537,10 +620,21 @@ _aio_aqueue(struct proc *p, struct aiocb *job, int type) {
error = copyin((caddr_t)job,
(caddr_t) &aiocbe->uaiocb, sizeof aiocbe->uaiocb);
if (error) {
#if DEBUGAIO > 0
if (debugaio > 0)
printf("aio_aqueue: Copyin error: %d\n", error);
#endif
TAILQ_INSERT_HEAD(&aio_freejobs, aiocbe, list);
return error;
}
/*
* Get the opcode
*/
if (type != LIO_NOP) {
aiocbe->uaiocb.aio_lio_opcode = type;
}
opcode = aiocbe->uaiocb.aio_lio_opcode;
/*
* Get the fd info for process
@ -554,6 +648,10 @@ _aio_aqueue(struct proc *p, struct aiocb *job, int type) {
if (fd >= fdp->fd_nfiles) {
TAILQ_INSERT_HEAD(&aio_freejobs, aiocbe, list);
if (type == 0) {
#if DEBUGAIO > 0
if (debugaio > 0)
printf("aio_aqueue: Null type\n");
#endif
suword(&job->_aiocb_private.status, -1);
suword(&job->_aiocb_private.error, EBADF);
}
@ -561,12 +659,17 @@ _aio_aqueue(struct proc *p, struct aiocb *job, int type) {
}
fp = fdp->fd_ofiles[fd];
if ((fp == NULL) || ((fp->f_flag & FWRITE) == 0)) {
if ((fp == NULL) ||
((opcode == LIO_WRITE) && ((fp->f_flag & FWRITE) == 0))) {
TAILQ_INSERT_HEAD(&aio_freejobs, aiocbe, list);
if (type == 0) {
suword(&job->_aiocb_private.status, -1);
suword(&job->_aiocb_private.error, EBADF);
}
#if DEBUGAIO > 0
if (debugaio > 0)
printf("aio_aqueue: Bad file descriptor\n");
#endif
return EBADF;
}
@ -576,11 +679,16 @@ _aio_aqueue(struct proc *p, struct aiocb *job, int type) {
suword(&job->_aiocb_private.status, -1);
suword(&job->_aiocb_private.error, EINVAL);
}
#if DEBUGAIO > 0
if (debugaio > 0)
printf("aio_aqueue: bad offset\n");
#endif
return EINVAL;
}
#ifdef DEBUGAIO
printf("job addr: 0x%x, 0x%x, %d\n", job, &job->_aiocb_private.kernelinfo, jobrefid);
#if DEBUGAIO > 0
if (debugaio > 2)
printf("job addr: 0x%x, 0x%x, %d\n", job, &job->_aiocb_private.kernelinfo, jobrefid);
#endif
error = suword(&job->_aiocb_private.kernelinfo, jobrefid);
@ -590,20 +698,20 @@ _aio_aqueue(struct proc *p, struct aiocb *job, int type) {
suword(&job->_aiocb_private.status, -1);
suword(&job->_aiocb_private.error, EINVAL);
}
#if DEBUGAIO > 0
if (debugaio > 0)
printf("aio_aqueue: fetch of kernelinfo from user space\n");
#endif
return error;
}
aiocbe->uaiocb._aiocb_private.kernelinfo = (void *)jobrefid;
#ifdef DEBUGAIO
printf("aio_aqueue: New job: %d... ", jobrefid);
#if DEBUGAIO > 0
if (debugaio > 2)
printf("aio_aqueue: New job: %d... ", jobrefid);
#endif
++jobrefid;
if (type != LIO_NOP) {
aiocbe->uaiocb.aio_lio_opcode = type;
}
opcode = aiocbe->uaiocb.aio_lio_opcode;
if (opcode == LIO_NOP) {
TAILQ_INSERT_HEAD(&aio_freejobs, aiocbe, list);
if (type == 0) {
@ -620,6 +728,10 @@ _aio_aqueue(struct proc *p, struct aiocb *job, int type) {
suword(&job->_aiocb_private.status, -1);
suword(&job->_aiocb_private.error, EINVAL);
}
#if DEBUGAIO > 0
if (debugaio > 0)
printf("aio_aqueue: invalid LIO op: %d\n", opcode);
#endif
return EINVAL;
}
@ -633,8 +745,9 @@ _aio_aqueue(struct proc *p, struct aiocb *job, int type) {
retryproc:
if (aiop = TAILQ_FIRST(&aio_freeproc)) {
#ifdef DEBUGAIO
printf("found a free AIO process\n");
#if DEBUGAIO > 0
if (debugaio > 0)
printf("found a free AIO process\n");
#endif
TAILQ_REMOVE(&aio_freeproc, aiop, list);
TAILQ_INSERT_TAIL(&aio_activeproc, aiop, list);
@ -642,20 +755,29 @@ _aio_aqueue(struct proc *p, struct aiocb *job, int type) {
TAILQ_INSERT_TAIL(&aiop->jobtorun, aiocbe, list);
TAILQ_INSERT_TAIL(&ki->kaio_jobqueue, aiocbe, plist);
aiocbe->jobstate = JOBST_JOBQPROC;
aiocbe->jobaioproc = aiop;
wakeup(aiop->aioproc);
} else if ((num_aio_procs < max_aio_procs) &&
(ki->kaio_active_count < ki->kaio_maxactive_count)) {
#if DEBUGAIO > 0
if (debugaio > 1) {
printf("aio_aqueue: starting new proc: num_aio_procs(%d), max_aio_procs(%d)\n", num_aio_procs, max_aio_procs);
printf(" ki->kaio_active_count(%d), ki->kaio_maxactive_count(%d)\n", ki->kaio_active_count, ki->kaio_maxactive_count);
}
#endif
if (error = aio_newproc()) {
#ifdef DEBUGAIO
printf("aio_aqueue: problem sleeping for starting proc: %d\n",
error);
#if DEBUGAIO > 0
if (debugaio > 0)
printf("aio_aqueue: problem sleeping for starting proc: %d\n",
error);
#endif
}
goto retryproc;
} else {
#ifdef DEBUGAIO
printf("queuing to global queue\n");
#if DEBUGAIO > 0
if (debugaio > 0)
printf("queuing to global queue\n");
#endif
TAILQ_INSERT_TAIL(&aio_jobs, aiocbe, list);
TAILQ_INSERT_TAIL(&ki->kaio_jobqueue, aiocbe, plist);
@ -702,6 +824,11 @@ aio_return(struct proc *p, struct aio_return_args *uap, int *retval) {
if (jobref == -1)
return EINVAL;
#if DEBUGAIO > 0
if (debugaio > 0)
printf("aio_return: jobref: %d\n", jobref);
#endif
for (cb = TAILQ_FIRST(&ki->kaio_jobdone);
cb;
@ -808,19 +935,14 @@ aio_suspend(struct proc *p, struct aio_suspend_args *uap, int *retval) {
for(i=0;i<uap->nent;i++) {
cbp = (struct aiocb *) fuword((caddr_t) &cbptr[i]);
#ifdef DEBUGAIO
printf("cbp: %x\n", cbp);
#if DEBUGAIO > 1
if (debugaio > 2)
printf("cbp: %x\n", cbp);
#endif
joblist[i] = fuword(&cbp->_aiocb_private.kernelinfo);
cbptr++;
}
#ifdef DEBUGAIO
printf("Suspend, timeout: %d clocks, jobs:", timo);
for(i=0;i<uap->nent;i++)
printf(" %d", joblist[i]);
printf("\n");
#endif
while (1) {
for (cb = TAILQ_FIRST(&ki->kaio_jobdone);
@ -834,31 +956,45 @@ aio_suspend(struct proc *p, struct aio_suspend_args *uap, int *retval) {
}
}
aio_marksuspend(p, uap->nent, joblist, 1);
#ifdef DEBUGAIO
printf("Suspending -- waiting for all I/O's to complete: ");
#if DEBUGAIO > 0
if (debugaio > 0) {
printf("Suspend, timeout: %d clocks, jobs:", timo);
for(i=0;i<uap->nent;i++)
printf(" %d", joblist[i]);
printf("\n");
}
#endif
aio_marksuspend(p, uap->nent, joblist, 1);
#if DEBUGAIO > 0
if (debugaio > 2) {
printf("Suspending -- waiting for all I/O's to complete: ");
for(i=0;i<uap->nent;i++)
printf(" %d", joblist[i]);
printf("\n");
}
#endif
error = tsleep(p, PRIBIO|PCATCH, "aiospn", timo);
aio_marksuspend(p, uap->nent, joblist, 0);
if (error == EINTR) {
#ifdef DEBUGAIO
printf(" signal\n");
#if DEBUGAIO > 0
if (debugaio > 2)
printf(" signal\n");
#endif
free(joblist, M_TEMP);
return EINTR;
} else if (error == EWOULDBLOCK) {
#ifdef DEBUGAIO
printf(" timeout\n");
#if DEBUGAIO > 0
if (debugaio > 2)
printf(" timeout\n");
#endif
free(joblist, M_TEMP);
return EAGAIN;
}
#ifdef DEBUGAIO
printf("\n");
#if DEBUGAIO > 0
if (debugaio > 2)
printf("\n");
#endif
}
@ -940,6 +1076,10 @@ aio_read(struct proc *p, struct aio_read_args *uap, int *retval) {
pmodes = fuword(&uap->aiocbp->_aiocb_private.privatemodes);
if ((pmodes & AIO_PMODE_SYNC) == 0) {
#if DEBUGAIO > 1
if (debugaio > 2)
printf("queueing aio_read\n");
#endif
return aio_aqueue(p, (struct aiocb *) uap->aiocbp, LIO_READ);
}
@ -1015,6 +1155,10 @@ aio_write(struct proc *p, struct aio_write_args *uap, int *retval) {
*/
pmodes = fuword(&uap->aiocbp->_aiocb_private.privatemodes);
if ((pmodes & AIO_PMODE_SYNC) == 0) {
#if DEBUGAIO > 1
if (debugaio > 2)
printf("queing aio_write\n");
#endif
return aio_aqueue(p, (struct aiocb *) uap->aiocbp, LIO_WRITE);
}
@ -1076,29 +1220,48 @@ lio_listio(struct proc *p, struct lio_listio_args *uap, int *retval) {
int error, runningcode;
int i;
if ((uap->mode != LIO_NOWAIT) && (uap->mode != LIO_WAIT))
if ((uap->mode != LIO_NOWAIT) && (uap->mode != LIO_WAIT)) {
#if DEBUGAIO > 0
if (debugaio > 0)
printf("lio_listio: bad mode: %d\n", uap->mode);
#endif
return EINVAL;
}
nent = uap->nent;
if (nent > AIO_LISTIO_MAX)
if (nent > AIO_LISTIO_MAX) {
#if DEBUGAIO > 0
if (debugaio > 0)
printf("lio_listio: nent > AIO_LISTIO_MAX: %d > %d\n", nent, AIO_LISTIO_MAX);
#endif
return EINVAL;
}
if (p->p_aioinfo == NULL) {
aio_init_aioinfo(p);
}
if ((nent + num_queue_count) > max_queue_count)
if ((nent + num_queue_count) > max_queue_count) {
#if DEBUGAIO > 0
if (debugaio > 0)
printf("lio_listio: (nent(%d) + num_queue_count(%d)) > max_queue_count(%d)\n", nent, num_queue_count, max_queue_count);
#endif
return EAGAIN;
}
ki = p->p_aioinfo;
if ((nent + ki->kaio_queue_count) > ki->kaio_qallowed_count)
if ((nent + ki->kaio_queue_count) > ki->kaio_qallowed_count) {
#if DEBUGAIO > 0
if (debugaio > 0)
printf("lio_listio: (nent(%d) + ki->kaio_queue_count(%d)) > ki->kaio_qallowed_count(%d)\n", nent, ki->kaio_queue_count, ki->kaio_qallowed_count);
#endif
return EAGAIN;
}
/*
* reserve resources, remember that we have to unwind part of them sometimes
*/
num_queue_count += nent;
ki->kaio_queue_count += nent;
*/
nentqueued = 0;
/*
@ -1109,14 +1272,30 @@ lio_listio(struct proc *p, struct lio_listio_args *uap, int *retval) {
cbptr = uap->acb_list;
for(i = 0; i < uap->nent; i++) {
iocb = (struct aiocb *) fuword((caddr_t) &cbptr[i]);
error = aio_aqueue(p, iocb, 0);
error = _aio_aqueue(p, iocb, 0);
if (error == 0)
nentqueued++;
}
if (nentqueued == 0)
/*
* If we haven't queued any, then just return error
*/
if (nentqueued == 0) {
#if DEBUGAIO > 0
if (debugaio > 0)
printf("lio_listio: none queued\n");
#endif
return EIO;
}
#if DEBUGAIO > 0
if (debugaio > 0)
printf("lio_listio: %d queued\n", nentqueued);
#endif
/*
* Calculate the appropriate error return
*/
runningcode = 0;
if (nentqueued != nent)
runningcode = EIO;
@ -1127,14 +1306,25 @@ lio_listio(struct proc *p, struct lio_listio_args *uap, int *retval) {
int found;
int jobref, command, status;
/*
* Fetch address of the control buf pointer in user space
*/
iocb = (struct aiocb *) fuword((caddr_t) &cbptr[i]);
/*
* Fetch the associated command from user space
*/
command = fuword(&iocb->aio_lio_opcode);
if (command == LIO_NOP)
continue;
/*
* If the status shows error or complete, then skip this entry.
*/
status = fuword(&iocb->_aiocb_private.status);
if (status == -1)
if (status != 0)
continue;
jobref = fuword(&iocb->_aiocb_private.kernelinfo);
found = 0;
@ -1150,6 +1340,9 @@ lio_listio(struct proc *p, struct lio_listio_args *uap, int *retval) {
break;
}
/*
* If all I/Os have been disposed of, then we can return
*/
if (i == uap->nent) {
return runningcode;
}