/* $OpenBSD: kern_proc.c,v 1.88 2020/09/26 15:15:22 kettenis Exp $ */ /* $NetBSD: kern_proc.c,v 1.14 1996/02/09 18:59:41 christos Exp $ */ /* * Copyright (c) 1982, 1986, 1989, 1991, 1993 * The Regents of the University of California. All rights reserved. * * 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. 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_proc.c 8.4 (Berkeley) 1/4/94 */ #include <sys/param.h> #include <sys/systm.h> #include <sys/kernel.h> #include <sys/proc.h> #include <sys/buf.h> #include <sys/acct.h> #include <sys/wait.h> #include <sys/rwlock.h> #include <ufs/ufs/quota.h> #include <sys/uio.h> #include <sys/malloc.h> #include <sys/mbuf.h> #include <sys/ioctl.h> #include <sys/tty.h> #include <sys/signalvar.h> #include <sys/pool.h> #include <sys/vnode.h> struct rwlock uidinfolk; #define UIHASH(uid) (&uihashtbl[(uid) & uihash]) LIST_HEAD(uihashhead, uidinfo) *uihashtbl; u_long uihash; /* size of hash table - 1 */ /* * Other process lists */ struct tidhashhead *tidhashtbl; u_long tidhash; struct pidhashhead *pidhashtbl; u_long pidhash; struct pgrphashhead *pgrphashtbl; u_long pgrphash; struct processlist allprocess; struct processlist zombprocess; struct proclist allproc; struct pool proc_pool; struct pool process_pool; struct pool rusage_pool; struct pool ucred_pool; struct pool pgrp_pool; struct pool session_pool; void pgdelete(struct pgrp *); void fixjobc(struct process *, struct pgrp *, int); static void orphanpg(struct pgrp *); #ifdef DEBUG void pgrpdump(void); #endif /* * Initialize global process hashing structures. */ void procinit(void) { LIST_INIT(&allprocess); LIST_INIT(&zombprocess); LIST_INIT(&allproc); rw_init(&uidinfolk, "uidinfo"); tidhashtbl = hashinit(maxthread / 4, M_PROC, M_NOWAIT, &tidhash); pidhashtbl = hashinit(maxprocess / 4, M_PROC, M_NOWAIT, &pidhash); pgrphashtbl = hashinit(maxprocess / 4, M_PROC, M_NOWAIT, &pgrphash); uihashtbl = hashinit(maxprocess / 16, M_PROC, M_NOWAIT, &uihash); if (!tidhashtbl || !pidhashtbl || !pgrphashtbl || !uihashtbl) panic("procinit: malloc"); pool_init(&proc_pool, sizeof(struct proc), 0, IPL_NONE, PR_WAITOK, "procpl", NULL); pool_init(&process_pool, sizeof(struct process), 0, IPL_NONE, PR_WAITOK, "processpl", NULL); pool_init(&rusage_pool, sizeof(struct rusage), 0, IPL_NONE, PR_WAITOK, "zombiepl", NULL); pool_init(&ucred_pool, sizeof(struct ucred), 0, IPL_MPFLOOR, 0, "ucredpl", NULL); pool_init(&pgrp_pool, sizeof(struct pgrp), 0, IPL_NONE, PR_WAITOK, "pgrppl", NULL); pool_init(&session_pool, sizeof(struct session), 0, IPL_NONE, PR_WAITOK, "sessionpl", NULL); } /* * This returns with `uidinfolk' held: caller must call uid_release() * after making whatever change they needed. */ struct uidinfo * uid_find(uid_t uid) { struct uidinfo *uip, *nuip; struct uihashhead *uipp; uipp = UIHASH(uid); rw_enter_write(&uidinfolk); LIST_FOREACH(uip, uipp, ui_hash) if (uip->ui_uid == uid) break; if (uip) return (uip); rw_exit_write(&uidinfolk); nuip = malloc(sizeof(*nuip), M_PROC, M_WAITOK|M_ZERO); rw_enter_write(&uidinfolk); LIST_FOREACH(uip, uipp, ui_hash) if (uip->ui_uid == uid) break; if (uip) { free(nuip, M_PROC, sizeof(*nuip)); return (uip); } nuip->ui_uid = uid; LIST_INSERT_HEAD(uipp, nuip, ui_hash); return (nuip); } void uid_release(struct uidinfo *uip) { rw_exit_write(&uidinfolk); } /* * Change the count associated with number of threads * a given user is using. */ int chgproccnt(uid_t uid, int diff) { struct uidinfo *uip; long count; uip = uid_find(uid); count = (uip->ui_proccnt += diff); uid_release(uip); if (count < 0) panic("chgproccnt: procs < 0"); return count; } /* * Is pr an inferior of parent? */ int inferior(struct process *pr, struct process *parent) { for (; pr != parent; pr = pr->ps_pptr) if (pr->ps_pid == 0 || pr->ps_pid == 1) return (0); return (1); } /* * Locate a proc (thread) by number */ struct proc * tfind(pid_t tid) { struct proc *p; LIST_FOREACH(p, TIDHASH(tid), p_hash) if (p->p_tid == tid) return (p); return (NULL); } /* * Locate a process by number */ struct process * prfind(pid_t pid) { struct process *pr; LIST_FOREACH(pr, PIDHASH(pid), ps_hash) if (pr->ps_pid == pid) return (pr); return (NULL); } /* * Locate a process group by number */ struct pgrp * pgfind(pid_t pgid) { struct pgrp *pgrp; LIST_FOREACH(pgrp, PGRPHASH(pgid), pg_hash) if (pgrp->pg_id == pgid) return (pgrp); return (NULL); } /* * Locate a zombie process */ struct process * zombiefind(pid_t pid) { struct process *pr; LIST_FOREACH(pr, &zombprocess, ps_list) if (pr->ps_pid == pid) return (pr); return (NULL); } /* * Move process to a new process group. If a session is provided * then it's a new session to contain this process group; otherwise * the process is staying within its existing session. */ void enternewpgrp(struct process *pr, struct pgrp *pgrp, struct session *newsess) { #ifdef DIAGNOSTIC if (SESS_LEADER(pr)) panic("%s: session leader attempted setpgrp", __func__); #endif if (newsess != NULL) { /* * New session. Initialize it completely */ timeout_set(&newsess->s_verauthto, zapverauth, newsess); newsess->s_leader = pr; newsess->s_count = 1; newsess->s_ttyvp = NULL; newsess->s_ttyp = NULL; memcpy(newsess->s_login, pr->ps_session->s_login, sizeof(newsess->s_login)); atomic_clearbits_int(&pr->ps_flags, PS_CONTROLT); pgrp->pg_session = newsess; #ifdef DIAGNOSTIC if (pr != curproc->p_p) panic("%s: mksession but not curproc", __func__); #endif } else { pgrp->pg_session = pr->ps_session; pgrp->pg_session->s_count++; } pgrp->pg_id = pr->ps_pid; LIST_INIT(&pgrp->pg_members); LIST_INIT(&pgrp->pg_sigiolst); LIST_INSERT_HEAD(PGRPHASH(pr->ps_pid), pgrp, pg_hash); pgrp->pg_jobc = 0; enterthispgrp(pr, pgrp); } /* * move process to an existing process group */ void enterthispgrp(struct process *pr, struct pgrp *pgrp) { struct pgrp *savepgrp = pr->ps_pgrp; /* * Adjust eligibility of affected pgrps to participate in job control. * Increment eligibility counts before decrementing, otherwise we * could reach 0 spuriously during the first call. */ fixjobc(pr, pgrp, 1); fixjobc(pr, savepgrp, 0); LIST_REMOVE(pr, ps_pglist); pr->ps_pgrp = pgrp; LIST_INSERT_HEAD(&pgrp->pg_members, pr, ps_pglist); if (LIST_EMPTY(&savepgrp->pg_members)) pgdelete(savepgrp); } /* * remove process from process group */ void leavepgrp(struct process *pr) { if (pr->ps_session->s_verauthppid == pr->ps_pid) zapverauth(pr->ps_session); LIST_REMOVE(pr, ps_pglist); if (LIST_EMPTY(&pr->ps_pgrp->pg_members)) pgdelete(pr->ps_pgrp); pr->ps_pgrp = 0; } /* * delete a process group */ void pgdelete(struct pgrp *pgrp) { sigio_freelist(&pgrp->pg_sigiolst); if (pgrp->pg_session->s_ttyp != NULL && pgrp->pg_session->s_ttyp->t_pgrp == pgrp) pgrp->pg_session->s_ttyp->t_pgrp = NULL; LIST_REMOVE(pgrp, pg_hash); SESSRELE(pgrp->pg_session); pool_put(&pgrp_pool, pgrp); } void zapverauth(void *v) { struct session *sess = v; sess->s_verauthuid = 0; sess->s_verauthppid = 0; } /* * Adjust pgrp jobc counters when specified process changes process group. * We count the number of processes in each process group that "qualify" * the group for terminal job control (those with a parent in a different * process group of the same session). If that count reaches zero, the * process group becomes orphaned. Check both the specified process' * process group and that of its children. * entering == 0 => pr is leaving specified group. * entering == 1 => pr is entering specified group. * XXX need proctree lock */ void fixjobc(struct process *pr, struct pgrp *pgrp, int entering) { struct pgrp *hispgrp; struct session *mysession = pgrp->pg_session; /* * Check pr's parent to see whether pr qualifies its own process * group; if so, adjust count for pr's process group. */ if ((hispgrp = pr->ps_pptr->ps_pgrp) != pgrp && hispgrp->pg_session == mysession) { if (entering) pgrp->pg_jobc++; else if (--pgrp->pg_jobc == 0) orphanpg(pgrp); } /* * Check this process' children to see whether they qualify * their process groups; if so, adjust counts for children's * process groups. */ LIST_FOREACH(pr, &pr->ps_children, ps_sibling) if ((hispgrp = pr->ps_pgrp) != pgrp && hispgrp->pg_session == mysession && (pr->ps_flags & PS_ZOMBIE) == 0) { if (entering) hispgrp->pg_jobc++; else if (--hispgrp->pg_jobc == 0) orphanpg(hispgrp); } } void killjobc(struct process *pr) { if (SESS_LEADER(pr)) { struct session *sp = pr->ps_session; if (sp->s_ttyvp) { struct vnode *ovp; /* * Controlling process. * Signal foreground pgrp, * drain controlling terminal * and revoke access to controlling terminal. */ if (sp->s_ttyp->t_session == sp) { if (sp->s_ttyp->t_pgrp) pgsignal(sp->s_ttyp->t_pgrp, SIGHUP, 1); ttywait(sp->s_ttyp); /* * The tty could have been revoked * if we blocked. */ if (sp->s_ttyvp) VOP_REVOKE(sp->s_ttyvp, REVOKEALL); } ovp = sp->s_ttyvp; sp->s_ttyvp = NULL; if (ovp) vrele(ovp); /* * 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(pr, pr->ps_pgrp, 0); } /* * A process group has become orphaned; * if there are any stopped processes in the group, * hang-up all process in that group. */ static void orphanpg(struct pgrp *pg) { struct process *pr; LIST_FOREACH(pr, &pg->pg_members, ps_pglist) { if (pr->ps_mainproc->p_stat == SSTOP) { LIST_FOREACH(pr, &pg->pg_members, ps_pglist) { prsignal(pr, SIGHUP); prsignal(pr, SIGCONT); } return; } } } #ifdef DDB void proc_printit(struct proc *p, const char *modif, int (*pr)(const char *, ...) __attribute__((__format__(__kprintf__,1,2)))) { static const char *const pstat[] = { "idle", "run", "sleep", "stop", "zombie", "dead", "onproc" }; char pstbuf[5]; const char *pst = pstbuf; if (p->p_stat < 1 || p->p_stat > sizeof(pstat) / sizeof(pstat[0])) snprintf(pstbuf, sizeof(pstbuf), "%d", p->p_stat); else pst = pstat[(int)p->p_stat - 1]; (*pr)("PROC (%s) pid=%d stat=%s\n", p->p_p->ps_comm, p->p_tid, pst); (*pr)(" flags process=%b proc=%b\n", p->p_p->ps_flags, PS_BITS, p->p_flag, P_BITS); (*pr)(" pri=%u, usrpri=%u, nice=%d\n", p->p_runpri, p->p_usrpri, p->p_p->ps_nice); (*pr)(" forw=%p, list=%p,%p\n", TAILQ_NEXT(p, p_runq), p->p_list.le_next, p->p_list.le_prev); (*pr)(" process=%p user=%p, vmspace=%p\n", p->p_p, p->p_addr, p->p_vmspace); (*pr)(" estcpu=%u, cpticks=%d, pctcpu=%u.%u\n", p->p_estcpu, p->p_cpticks, p->p_pctcpu / 100, p->p_pctcpu % 100); (*pr)(" user=%u, sys=%u, intr=%u\n", p->p_uticks, p->p_sticks, p->p_iticks); } #include <machine/db_machdep.h> #include <ddb/db_output.h> void db_kill_cmd(db_expr_t addr, int have_addr, db_expr_t count, char *modif) { struct process *pr; struct proc *p; pr = prfind(addr); if (pr == NULL) { db_printf("%ld: No such process", addr); return; } p = TAILQ_FIRST(&pr->ps_threads); /* Send uncatchable SIGABRT for coredump */ sigabort(p); } void db_show_all_procs(db_expr_t addr, int haddr, db_expr_t count, char *modif) { char *mode; int skipzomb = 0; int has_kernel_lock = 0; struct proc *p; struct process *pr, *ppr; if (modif[0] == 0) modif[0] = 'n'; /* default == normal mode */ mode = "mawno"; while (*mode && *mode != modif[0]) mode++; if (*mode == 0 || *mode == 'm') { db_printf("usage: show all procs [/a] [/n] [/w]\n"); db_printf("\t/a == show process address info\n"); db_printf("\t/n == show normal process info [default]\n"); db_printf("\t/w == show process pgrp/wait info\n"); db_printf("\t/o == show normal info for non-idle SONPROC\n"); return; } pr = LIST_FIRST(&allprocess); switch (*mode) { case 'a': db_printf(" TID %-9s %18s %18s %18s\n", "COMMAND", "STRUCT PROC *", "UAREA *", "VMSPACE/VM_MAP"); break; case 'n': db_printf(" PID %6s %5s %5s S %10s %-12s %-15s\n", "TID", "PPID", "UID", "FLAGS", "WAIT", "COMMAND"); break; case 'w': db_printf(" TID %-15s %-5s %18s %s\n", "COMMAND", "PGRP", "WAIT-CHANNEL", "WAIT-MSG"); break; case 'o': skipzomb = 1; db_printf(" TID %5s %5s %10s %10s %3s %-30s\n", "PID", "UID", "PRFLAGS", "PFLAGS", "CPU", "COMMAND"); break; } while (pr != NULL) { ppr = pr->ps_pptr; TAILQ_FOREACH(p, &pr->ps_threads, p_thr_link) { #ifdef MULTIPROCESSOR if (__mp_lock_held(&kernel_lock, p->p_cpu)) has_kernel_lock = 1; else has_kernel_lock = 0; #endif if (p->p_stat) { if (*mode == 'o') { if (p->p_stat != SONPROC) continue; if (p->p_cpu != NULL && p->p_cpu-> ci_schedstate.spc_idleproc == p) continue; } if (*mode == 'n') { db_printf("%c%5d ", (p == curproc ? '*' : ' '), pr->ps_pid); } else { db_printf("%c%6d ", (p == curproc ? '*' : ' '), p->p_tid); } switch (*mode) { case 'a': db_printf("%-9.9s %18p %18p %18p\n", pr->ps_comm, p, p->p_addr, p->p_vmspace); break; case 'n': db_printf("%6d %5d %5d %d %#10x " "%-12.12s %-15s\n", p->p_tid, ppr ? ppr->ps_pid : -1, pr->ps_ucred->cr_ruid, p->p_stat, p->p_flag | pr->ps_flags, (p->p_wchan && p->p_wmesg) ? p->p_wmesg : "", pr->ps_comm); break; case 'w': db_printf("%-15s %-5d %18p %s\n", pr->ps_comm, (pr->ps_pgrp ? pr->ps_pgrp->pg_id : -1), p->p_wchan, (p->p_wchan && p->p_wmesg) ? p->p_wmesg : ""); break; case 'o': db_printf("%5d %5d %#10x %#10x %3d" "%c %-31s\n", pr->ps_pid, pr->ps_ucred->cr_ruid, pr->ps_flags, p->p_flag, CPU_INFO_UNIT(p->p_cpu), has_kernel_lock ? 'K' : ' ', pr->ps_comm); break; } } } pr = LIST_NEXT(pr, ps_list); if (pr == NULL && skipzomb == 0) { skipzomb = 1; pr = LIST_FIRST(&zombprocess); } } } #endif #ifdef DEBUG void pgrpdump(void) { struct pgrp *pgrp; struct process *pr; int i; for (i = 0; i <= pgrphash; i++) { if (!LIST_EMPTY(&pgrphashtbl[i])) { printf("\tindx %d\n", i); LIST_FOREACH(pgrp, &pgrphashtbl[i], pg_hash) { printf("\tpgrp %p, pgid %d, sess %p, sesscnt %d, mem %p\n", pgrp, pgrp->pg_id, pgrp->pg_session, pgrp->pg_session->s_count, LIST_FIRST(&pgrp->pg_members)); LIST_FOREACH(pr, &pgrp->pg_members, ps_pglist) { printf("\t\tpid %d addr %p pgrp %p\n", pr->ps_pid, pr, pr->ps_pgrp); } } } } } #endif /* DEBUG */