mirror of
https://git.FreeBSD.org/src.git
synced 2024-12-16 10:20:30 +00:00
2d8acc0f4a
1) Start using TSM. Struct procs continue to point to upages structure, after being freed. Struct vmspace continues to point to pte object and kva space for kstack. u_map is now superfluous. 2) vm_map's don't need to be reference counted. They always exist either in the kernel or in a vmspace. The vmspaces are managed by reference counts. 3) Remove the "wired" vm_map nonsense. 4) No need to keep a cache of kernel stack kva's. 5) Get rid of strange looking ++var, and change to var++. 6) Change more data structures to use our "zone" allocator. Added struct proc, struct vmspace and struct vnode. This saves a significant amount of kva space and physical memory. Additionally, this enables TSM for the zone managed memory. 7) Keep ioopt disabled for now. 8) Remove the now bogus "single use" map concept. 9) Use generation counts or id's for data structures residing in TSM, where it allows us to avoid unneeded restart overhead during traversals, where blocking might occur. 10) Account better for memory deficits, so the pageout daemon will be able to make enough memory available (experimental.) 11) Fix some vnode locking problems. (From Tor, I think.) 12) Add a check in ufs_lookup, to avoid lots of unneeded calls to bcmp. (experimental.) 13) Significantly shrink, cleanup, and make slightly faster the vm_fault.c code. Use generation counts, get rid of unneded collpase operations, and clean up the cluster code. 14) Make vm_zone more suitable for TSM. This commit is partially as a result of discussions and contributions from other people, including DG, Tor Egge, PHK, and probably others that I have forgotten to attribute (so let me know, if I forgot.) This is not the infamous, final cleanup of the vnode stuff, but a necessary step. Vnode mgmt should be correct, but things might still change, and there is still some missing stuff (like ioopt, and physical backing of non-merged cache files, debugging of layering concepts.)
600 lines
15 KiB
C
600 lines
15 KiB
C
/*
|
|
* Copyright (c) 1982, 1986, 1989, 1991, 1993
|
|
* The Regents of the University of California. All rights reserved.
|
|
* (c) UNIX System Laboratories, Inc.
|
|
* All or some portions of this file are derived from material licensed
|
|
* to the University of California by American Telephone and Telegraph
|
|
* Co. or Unix System Laboratories, Inc. and are reproduced herein with
|
|
* the permission of UNIX System Laboratories, Inc.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed by the University of
|
|
* California, Berkeley and its contributors.
|
|
* 4. Neither the name of the University nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
* @(#)kern_exit.c 8.7 (Berkeley) 2/12/94
|
|
* $Id: kern_exit.c,v 1.64 1997/12/16 17:40:14 eivind Exp $
|
|
*/
|
|
|
|
#include "opt_compat.h"
|
|
#include "opt_ktrace.h"
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/sysproto.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/pioctl.h>
|
|
#include <sys/tty.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/vnode.h>
|
|
#include <sys/resourcevar.h>
|
|
#include <sys/signalvar.h>
|
|
#include <sys/ptrace.h>
|
|
#include <sys/acct.h> /* for acct_process() function prototype */
|
|
#include <sys/filedesc.h>
|
|
#include <sys/shm.h>
|
|
#include <sys/sem.h>
|
|
#include <sys/aio.h>
|
|
|
|
#ifdef COMPAT_43
|
|
#include <machine/reg.h>
|
|
#include <machine/psl.h>
|
|
#endif
|
|
#include <machine/limits.h> /* for UCHAR_MAX = typeof(p_priority)_MAX */
|
|
|
|
#include <vm/vm.h>
|
|
#include <vm/vm_param.h>
|
|
#include <sys/lock.h>
|
|
#include <vm/pmap.h>
|
|
#include <vm/vm_map.h>
|
|
#include <vm/vm_zone.h>
|
|
|
|
static MALLOC_DEFINE(M_ZOMBIE, "zombie", "zombie proc status");
|
|
|
|
static int wait1 __P((struct proc *, struct wait_args *, int));
|
|
|
|
/*
|
|
* callout list for things to do at exit time
|
|
*/
|
|
typedef struct exit_list_element {
|
|
struct exit_list_element *next;
|
|
exitlist_fn function;
|
|
} *ele_p;
|
|
|
|
static ele_p exit_list;
|
|
|
|
/*
|
|
* exit --
|
|
* Death of process.
|
|
*/
|
|
void
|
|
exit(p, uap)
|
|
struct proc *p;
|
|
struct rexit_args /* {
|
|
int rval;
|
|
} */ *uap;
|
|
{
|
|
|
|
exit1(p, W_EXITCODE(uap->rval, 0));
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
/*
|
|
* Exit: deallocate address space and other resources, change proc state
|
|
* to zombie, and unlink proc from allproc and parent's lists. Save exit
|
|
* status and rusage for wait(). Check for child processes and orphan them.
|
|
*/
|
|
void
|
|
exit1(p, rv)
|
|
register struct proc *p;
|
|
int rv;
|
|
{
|
|
register struct proc *q, *nq;
|
|
register struct vmspace *vm;
|
|
ele_p ep = exit_list;
|
|
|
|
if (p->p_pid == 1) {
|
|
printf("init died (signal %d, exit %d)\n",
|
|
WTERMSIG(rv), WEXITSTATUS(rv));
|
|
panic("Going nowhere without my init!");
|
|
}
|
|
|
|
aio_proc_rundown(p);
|
|
|
|
/* are we a task leader? */
|
|
if(p == p->p_leader) {
|
|
struct kill_args killArgs;
|
|
killArgs.signum = SIGKILL;
|
|
q = p->p_peers;
|
|
while(q) {
|
|
killArgs.pid = q->p_pid;
|
|
/*
|
|
* The interface for kill is better
|
|
* than the internal signal
|
|
*/
|
|
kill(p, &killArgs);
|
|
nq = q;
|
|
q = q->p_peers;
|
|
/*
|
|
* orphan the threads so we don't mess up
|
|
* when they call exit
|
|
*/
|
|
nq->p_peers = 0;
|
|
nq->p_leader = nq;
|
|
}
|
|
|
|
/* otherwise are we a peer? */
|
|
} else if(p->p_peers) {
|
|
q = p->p_leader;
|
|
while(q->p_peers != p)
|
|
q = q->p_peers;
|
|
q->p_peers = p->p_peers;
|
|
}
|
|
|
|
#ifdef PGINPROF
|
|
vmsizmon();
|
|
#endif
|
|
STOPEVENT(p, S_EXIT, rv);
|
|
|
|
/*
|
|
* Check if any LKMs need anything done at process exit.
|
|
* e.g. SYSV IPC stuff
|
|
* XXX what if one of these generates an error?
|
|
*/
|
|
while (ep) {
|
|
(*ep->function)(p);
|
|
ep = ep->next;
|
|
}
|
|
|
|
if (p->p_flag & P_PROFIL)
|
|
stopprofclock(p);
|
|
MALLOC(p->p_ru, struct rusage *, sizeof(struct rusage),
|
|
M_ZOMBIE, M_WAITOK);
|
|
/*
|
|
* If parent is waiting for us to exit or exec,
|
|
* P_PPWAIT is set; we will wakeup the parent below.
|
|
*/
|
|
p->p_flag &= ~(P_TRACED | P_PPWAIT);
|
|
p->p_flag |= P_WEXIT;
|
|
p->p_sigignore = ~0;
|
|
p->p_siglist = 0;
|
|
if (timerisset(&p->p_realtimer.it_value))
|
|
untimeout(realitexpire, (caddr_t)p, p->p_ithandle);
|
|
|
|
/*
|
|
* Close open files and release open-file table.
|
|
* This may block!
|
|
*/
|
|
fdfree(p);
|
|
|
|
/*
|
|
* XXX Shutdown SYSV semaphores
|
|
*/
|
|
semexit(p);
|
|
|
|
/* The next two chunks should probably be moved to vmspace_exit. */
|
|
vm = p->p_vmspace;
|
|
/*
|
|
* Release user portion of address space.
|
|
* This releases references to vnodes,
|
|
* which could cause I/O if the file has been unlinked.
|
|
* Need to do this early enough that we can still sleep.
|
|
* Can't free the entire vmspace as the kernel stack
|
|
* may be mapped within that space also.
|
|
*/
|
|
if (vm->vm_refcnt == 1) {
|
|
if (vm->vm_shm)
|
|
shmexit(p);
|
|
pmap_remove_pages(&vm->vm_pmap, VM_MIN_ADDRESS,
|
|
VM_MAXUSER_ADDRESS);
|
|
(void) vm_map_remove(&vm->vm_map, VM_MIN_ADDRESS,
|
|
VM_MAXUSER_ADDRESS);
|
|
}
|
|
|
|
if (SESS_LEADER(p)) {
|
|
register struct session *sp = p->p_session;
|
|
|
|
if (sp->s_ttyvp) {
|
|
/*
|
|
* Controlling process.
|
|
* Signal foreground pgrp,
|
|
* drain controlling terminal
|
|
* and revoke access to controlling terminal.
|
|
*/
|
|
if (sp->s_ttyp && (sp->s_ttyp->t_session == sp)) {
|
|
if (sp->s_ttyp->t_pgrp)
|
|
pgsignal(sp->s_ttyp->t_pgrp, SIGHUP, 1);
|
|
(void) ttywait(sp->s_ttyp);
|
|
/*
|
|
* The tty could have been revoked
|
|
* if we blocked.
|
|
*/
|
|
if (sp->s_ttyvp)
|
|
VOP_REVOKE(sp->s_ttyvp, REVOKEALL);
|
|
}
|
|
if (sp->s_ttyvp)
|
|
vrele(sp->s_ttyvp);
|
|
sp->s_ttyvp = NULL;
|
|
/*
|
|
* s_ttyp is not zero'd; we use this to indicate
|
|
* that the session once had a controlling terminal.
|
|
* (for logging and informational purposes)
|
|
*/
|
|
}
|
|
sp->s_leader = NULL;
|
|
}
|
|
fixjobc(p, p->p_pgrp, 0);
|
|
if (p->p_limit->p_refcnt > 1 &&
|
|
(p->p_limit->p_lflags & PL_SHAREMOD) == 0) {
|
|
p->p_limit->p_refcnt--;
|
|
p->p_limit = limcopy(p->p_limit);
|
|
}
|
|
p->p_rlimit[RLIMIT_FSIZE].rlim_cur = RLIM_INFINITY;
|
|
(void)acct_process(p);
|
|
#ifdef KTRACE
|
|
/*
|
|
* release trace file
|
|
*/
|
|
p->p_traceflag = 0; /* don't trace the vrele() */
|
|
if (p->p_tracep)
|
|
vrele(p->p_tracep);
|
|
#endif
|
|
/*
|
|
* Remove proc from allproc queue and pidhash chain.
|
|
* Place onto zombproc. Unlink from parent's child list.
|
|
*/
|
|
LIST_REMOVE(p, p_list);
|
|
LIST_INSERT_HEAD(&zombproc, p, p_list);
|
|
p->p_stat = SZOMB;
|
|
|
|
LIST_REMOVE(p, p_hash);
|
|
|
|
q = p->p_children.lh_first;
|
|
if (q) /* only need this if any child is S_ZOMB */
|
|
wakeup((caddr_t) initproc);
|
|
for (; q != 0; q = nq) {
|
|
nq = q->p_sibling.le_next;
|
|
LIST_REMOVE(q, p_sibling);
|
|
LIST_INSERT_HEAD(&initproc->p_children, q, p_sibling);
|
|
q->p_pptr = initproc;
|
|
/*
|
|
* Traced processes are killed
|
|
* since their existence means someone is screwing up.
|
|
*/
|
|
if (q->p_flag & P_TRACED) {
|
|
q->p_flag &= ~P_TRACED;
|
|
psignal(q, SIGKILL);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Save exit status and final rusage info, adding in child rusage
|
|
* info and self times.
|
|
*/
|
|
p->p_xstat = rv;
|
|
*p->p_ru = p->p_stats->p_ru;
|
|
calcru(p, &p->p_ru->ru_utime, &p->p_ru->ru_stime, NULL);
|
|
ruadd(p->p_ru, &p->p_stats->p_cru);
|
|
|
|
/*
|
|
* Notify parent that we're gone. If parent has the P_NOCLDWAIT
|
|
* flag set, notify process 1 instead (and hope it will handle
|
|
* this situation).
|
|
*/
|
|
if (p->p_pptr->p_flag & P_NOCLDWAIT) {
|
|
struct proc *pp = p->p_pptr;
|
|
proc_reparent(p, initproc);
|
|
/*
|
|
* If this was the last child of our parent, notify
|
|
* parent, so in case he was wait(2)ing, he will
|
|
* continue.
|
|
*/
|
|
if (LIST_EMPTY(&pp->p_children))
|
|
wakeup((caddr_t)pp);
|
|
}
|
|
|
|
psignal(p->p_pptr, SIGCHLD);
|
|
wakeup((caddr_t)p->p_pptr);
|
|
#if defined(tahoe)
|
|
/* move this to cpu_exit */
|
|
p->p_addr->u_pcb.pcb_savacc.faddr = (float *)NULL;
|
|
#endif
|
|
/*
|
|
* Clear curproc after we've done all operations
|
|
* that could block, and before tearing down the rest
|
|
* of the process state that might be used from clock, etc.
|
|
* Also, can't clear curproc while we're still runnable,
|
|
* as we're not on a run queue (we are current, just not
|
|
* a proper proc any longer!).
|
|
*
|
|
* Other substructures are freed from wait().
|
|
*/
|
|
curproc = NULL;
|
|
if (--p->p_limit->p_refcnt == 0) {
|
|
FREE(p->p_limit, M_SUBPROC);
|
|
p->p_limit = NULL;
|
|
}
|
|
|
|
/*
|
|
* Finally, call machine-dependent code to release the remaining
|
|
* resources including address space, the kernel stack and pcb.
|
|
* The address space is released by "vmspace_free(p->p_vmspace)";
|
|
* This is machine-dependent, as we may have to change stacks
|
|
* or ensure that the current one isn't reallocated before we
|
|
* finish. cpu_exit will end with a call to cpu_switch(), finishing
|
|
* our execution (pun intended).
|
|
*/
|
|
cpu_exit(p);
|
|
}
|
|
|
|
#ifdef COMPAT_43
|
|
#if defined(hp300) || defined(luna68k)
|
|
#include <machine/frame.h>
|
|
#define GETPS(rp) ((struct frame *)(rp))->f_sr
|
|
#else
|
|
#define GETPS(rp) (rp)[PS]
|
|
#endif
|
|
|
|
int
|
|
owait(p, uap)
|
|
struct proc *p;
|
|
register struct owait_args /* {
|
|
int dummy;
|
|
} */ *uap;
|
|
{
|
|
struct wait_args w;
|
|
|
|
#ifdef PSL_ALLCC
|
|
if ((GETPS(p->p_md.md_regs) & PSL_ALLCC) != PSL_ALLCC) {
|
|
w.options = 0;
|
|
w.rusage = NULL;
|
|
} else {
|
|
w.options = p->p_md.md_regs[R0];
|
|
w.rusage = (struct rusage *)p->p_md.md_regs[R1];
|
|
}
|
|
#else
|
|
w.options = 0;
|
|
w.rusage = NULL;
|
|
#endif
|
|
w.pid = WAIT_ANY;
|
|
w.status = NULL;
|
|
return (wait1(p, &w, 1));
|
|
}
|
|
#endif /* COMPAT_43 */
|
|
|
|
int
|
|
wait4(p, uap)
|
|
struct proc *p;
|
|
struct wait_args *uap;
|
|
{
|
|
|
|
return (wait1(p, uap, 0));
|
|
}
|
|
|
|
static int
|
|
wait1(q, uap, compat)
|
|
register struct proc *q;
|
|
register struct wait_args /* {
|
|
int pid;
|
|
int *status;
|
|
int options;
|
|
struct rusage *rusage;
|
|
} */ *uap;
|
|
int compat;
|
|
{
|
|
register int nfound;
|
|
register struct proc *p, *t;
|
|
int status, error;
|
|
|
|
if (uap->pid == 0)
|
|
uap->pid = -q->p_pgid;
|
|
if (uap->options &~ (WUNTRACED|WNOHANG))
|
|
return (EINVAL);
|
|
loop:
|
|
nfound = 0;
|
|
for (p = q->p_children.lh_first; p != 0; p = p->p_sibling.le_next) {
|
|
if (uap->pid != WAIT_ANY &&
|
|
p->p_pid != uap->pid && p->p_pgid != -uap->pid)
|
|
continue;
|
|
nfound++;
|
|
if (p->p_stat == SZOMB) {
|
|
/* charge childs scheduling cpu usage to parent */
|
|
if (curproc->p_pid != 1) {
|
|
curproc->p_estcpu = min(curproc->p_estcpu +
|
|
p->p_estcpu, UCHAR_MAX);
|
|
}
|
|
|
|
q->p_retval[0] = p->p_pid;
|
|
#ifdef COMPAT_43
|
|
if (compat)
|
|
q->p_retval[1] = p->p_xstat;
|
|
else
|
|
#endif
|
|
if (uap->status) {
|
|
status = p->p_xstat; /* convert to int */
|
|
if ((error = copyout((caddr_t)&status,
|
|
(caddr_t)uap->status, sizeof(status))))
|
|
return (error);
|
|
}
|
|
if (uap->rusage && (error = copyout((caddr_t)p->p_ru,
|
|
(caddr_t)uap->rusage, sizeof (struct rusage))))
|
|
return (error);
|
|
/*
|
|
* If we got the child via a ptrace 'attach',
|
|
* we need to give it back to the old parent.
|
|
*/
|
|
if (p->p_oppid && (t = pfind(p->p_oppid))) {
|
|
p->p_oppid = 0;
|
|
proc_reparent(p, t);
|
|
psignal(t, SIGCHLD);
|
|
wakeup((caddr_t)t);
|
|
return (0);
|
|
}
|
|
p->p_xstat = 0;
|
|
ruadd(&q->p_stats->p_cru, p->p_ru);
|
|
FREE(p->p_ru, M_ZOMBIE);
|
|
p->p_ru = NULL;
|
|
|
|
/*
|
|
* Decrement the count of procs running with this uid.
|
|
*/
|
|
(void)chgproccnt(p->p_cred->p_ruid, -1);
|
|
|
|
/*
|
|
* Release reference to text vnode
|
|
*/
|
|
if (p->p_textvp)
|
|
vrele(p->p_textvp);
|
|
|
|
/*
|
|
* Free up credentials.
|
|
*/
|
|
if (--p->p_cred->p_refcnt == 0) {
|
|
crfree(p->p_cred->pc_ucred);
|
|
FREE(p->p_cred, M_SUBPROC);
|
|
p->p_cred = NULL;
|
|
}
|
|
|
|
/*
|
|
* Finally finished with old proc entry.
|
|
* Unlink it from its process group and free it.
|
|
*/
|
|
leavepgrp(p);
|
|
LIST_REMOVE(p, p_list); /* off zombproc */
|
|
LIST_REMOVE(p, p_sibling);
|
|
|
|
/*
|
|
* Give machine-dependent layer a chance
|
|
* to free anything that cpu_exit couldn't
|
|
* release while still running in process context.
|
|
*/
|
|
cpu_wait(p);
|
|
zfree(proc_zone, p);
|
|
nprocs--;
|
|
return (0);
|
|
}
|
|
if (p->p_stat == SSTOP && (p->p_flag & P_WAITED) == 0 &&
|
|
(p->p_flag & P_TRACED || uap->options & WUNTRACED)) {
|
|
p->p_flag |= P_WAITED;
|
|
q->p_retval[0] = p->p_pid;
|
|
#ifdef COMPAT_43
|
|
if (compat) {
|
|
q->p_retval[1] = W_STOPCODE(p->p_xstat);
|
|
error = 0;
|
|
} else
|
|
#endif
|
|
if (uap->status) {
|
|
status = W_STOPCODE(p->p_xstat);
|
|
error = copyout((caddr_t)&status,
|
|
(caddr_t)uap->status, sizeof(status));
|
|
} else
|
|
error = 0;
|
|
return (error);
|
|
}
|
|
}
|
|
if (nfound == 0)
|
|
return (ECHILD);
|
|
if (uap->options & WNOHANG) {
|
|
q->p_retval[0] = 0;
|
|
return (0);
|
|
}
|
|
if ((error = tsleep((caddr_t)q, PWAIT | PCATCH, "wait", 0)))
|
|
return (error);
|
|
goto loop;
|
|
}
|
|
|
|
/*
|
|
* make process 'parent' the new parent of process 'child'.
|
|
*/
|
|
void
|
|
proc_reparent(child, parent)
|
|
register struct proc *child;
|
|
register struct proc *parent;
|
|
{
|
|
|
|
if (child->p_pptr == parent)
|
|
return;
|
|
|
|
LIST_REMOVE(child, p_sibling);
|
|
LIST_INSERT_HEAD(&parent->p_children, child, p_sibling);
|
|
child->p_pptr = parent;
|
|
}
|
|
|
|
/*
|
|
* The next two functions are to handle adding/deleting items on the
|
|
* exit callout list
|
|
*
|
|
* at_exit():
|
|
* Take the arguments given and put them onto the exit callout list,
|
|
* However first make sure that it's not already there.
|
|
* returns 0 on success.
|
|
*/
|
|
int
|
|
at_exit(function)
|
|
exitlist_fn function;
|
|
{
|
|
ele_p ep;
|
|
|
|
/* Be noisy if the programmer has lost track of things */
|
|
if (rm_at_exit(function))
|
|
printf("exit callout entry already present\n");
|
|
ep = malloc(sizeof(*ep), M_TEMP, M_NOWAIT);
|
|
if (ep == NULL)
|
|
return (ENOMEM);
|
|
ep->next = exit_list;
|
|
ep->function = function;
|
|
exit_list = ep;
|
|
return (0);
|
|
}
|
|
/*
|
|
* Scan the exit callout list for the given items and remove them.
|
|
* Returns the number of items removed.
|
|
* Logically this can only be 0 or 1.
|
|
*/
|
|
int
|
|
rm_at_exit(function)
|
|
exitlist_fn function;
|
|
{
|
|
ele_p *epp, ep;
|
|
int count;
|
|
|
|
count = 0;
|
|
epp = &exit_list;
|
|
ep = *epp;
|
|
while (ep) {
|
|
if (ep->function == function) {
|
|
*epp = ep->next;
|
|
free(ep, M_TEMP);
|
|
count++;
|
|
} else {
|
|
epp = &ep->next;
|
|
}
|
|
ep = *epp;
|
|
}
|
|
return (count);
|
|
}
|