📄 jobs.c
字号:
/* * Process and job control *//* * Reworked/Rewritten version of Eric Gisin's/Ron Natalie's code by * Larry Bouzane (larry@cs.mun.ca) and hacked again by * Michael Rendell (michael@cs.mun.ca) * * The interface to the rest of the shell should probably be changed * to allow use of vfork() when available but that would be way too much * work :) * * Notes regarding the copious ifdefs: * - JOB_SIGS is independent of JOBS - it is defined if there are modern * signal and wait routines available. This is prefered, even when * JOBS is not defined, since the shell will not otherwise notice when * background jobs die until the shell waits for a foreground process * to die. * - TTY_PGRP defined iff JOBS is defined - defined if there are tty * process groups * - NEED_PGRP_SYNC defined iff JOBS is defined - see comment below */#include "sh.h"#include "ksh_stat.h"#include "ksh_wait.h"#include "ksh_times.h"#include "tty.h"/* Start of system configuration stuff *//* We keep CHILD_MAX zombie processes around (exact value isn't critical) */#ifndef CHILD_MAX# if defined(HAVE_SYSCONF) && defined(_SC_CHILD_MAX)# define CHILD_MAX sysconf(_SC_CHILD_MAX)# else /* _SC_CHILD_MAX */# ifdef _POSIX_CHILD_MAX# define CHILD_MAX ((_POSIX_CHILD_MAX) * 2)# else /* _POSIX_CHILD_MAX */# define CHILD_MAX 20# endif /* _POSIX_CHILD_MAX */# endif /* _SC_CHILD_MAX */#endif /* !CHILD_MAX */#ifdef JOBS# if defined(HAVE_TCSETPGRP) || defined(TIOCSPGRP)# define TTY_PGRP# endif# ifdef BSD_PGRP# define setpgid setpgrp# define getpgID() getpgrp(0)# else# define getpgID() getpgrp()# endif# if defined(TTY_PGRP) && !defined(HAVE_TCSETPGRP)int tcsetpgrp ARGS((int fd, pid_t grp));int tcgetpgrp ARGS((int fd));inttcsetpgrp(fd, grp) int fd; pid_t grp;{ return ioctl(fd, TIOCSPGRP, &grp);}inttcgetpgrp(fd) int fd;{ int r, grp; if ((r = ioctl(fd, TIOCGPGRP, &grp)) < 0) return r; return grp;}# endif /* !HAVE_TCSETPGRP && TIOCSPGRP */#else /* JOBS *//* These so we can use ifdef xxx instead of if defined(JOBS) && defined(xxx) */# undef TTY_PGRP# undef NEED_PGRP_SYNC#endif /* JOBS *//* End of system configuration stuff *//* Order important! */#define PRUNNING 0#define PEXITED 1#define PSIGNALLED 2#define PSTOPPED 3typedef struct proc Proc;struct proc { Proc *next; /* next process in pipeline (if any) */ int state; WAIT_T status; /* wait status */ pid_t pid; /* process id */ char command[48]; /* process command string */};/* Notify/print flag - j_print() argument */#define JP_NONE 0 /* don't print anything */#define JP_SHORT 1 /* print signals processes were killed by */#define JP_MEDIUM 2 /* print [job-num] -/+ command */#define JP_LONG 3 /* print [job-num] -/+ pid command */#define JP_PGRP 4 /* print pgrp *//* put_job() flags */#define PJ_ON_FRONT 0 /* at very front */#define PJ_PAST_STOPPED 1 /* just past any stopped jobs *//* Job.flags values */#define JF_STARTED 0x001 /* set when all processes in job are started */#define JF_WAITING 0x002 /* set if j_waitj() is waiting on job */#define JF_W_ASYNCNOTIFY 0x004 /* set if waiting and async notification ok */#define JF_XXCOM 0x008 /* set for `command` jobs */#define JF_FG 0x010 /* running in foreground (also has tty pgrp) */#define JF_SAVEDTTY 0x020 /* j->ttystate is valid */#define JF_CHANGED 0x040 /* process has changed state */#define JF_KNOWN 0x080 /* $! referenced */#define JF_ZOMBIE 0x100 /* known, unwaited process */#define JF_REMOVE 0x200 /* flaged for removal (j_jobs()/j_noityf()) */#define JF_USETTYMODE 0x400 /* tty mode saved if process exits normally */#define JF_SAVEDTTYPGRP 0x800 /* j->saved_ttypgrp is valid */typedef struct job Job;struct job { Job *next; /* next job in list */ int job; /* job number: %n */ int flags; /* see JF_* */ int state; /* job state */ int status; /* exit status of last process */ pid_t pgrp; /* process group of job */ pid_t ppid; /* pid of process that forked job */ INT32 age; /* number of jobs started */ clock_t systime; /* system time used by job */ clock_t usrtime; /* user time used by job */ Proc *proc_list; /* process list */ Proc *last_proc; /* last process in list */#ifdef KSH Coproc_id coproc_id; /* 0 or id of coprocess output pipe */#endif /* KSH */#ifdef TTY_PGRP TTY_state ttystate; /* saved tty state for stopped jobs */ pid_t saved_ttypgrp; /* saved tty process group for stopped jobs */#endif /* TTY_PGRP */};/* Flags for j_waitj() */#define JW_NONE 0x00#define JW_INTERRUPT 0x01 /* ^C will stop the wait */#define JW_ASYNCNOTIFY 0x02 /* asynchronous notification during wait ok */#define JW_STOPPEDWAIT 0x04 /* wait even if job stopped *//* Error codes for j_lookup() */#define JL_OK 0#define JL_NOSUCH 1 /* no such job */#define JL_AMBIG 2 /* %foo or %?foo is ambiguous */#define JL_INVALID 3 /* non-pid, non-% job id */static const char *const lookup_msgs[] = { null, "no such job", "ambiguous", "argument must be %job or process id", (char *) 0 };clock_t j_systime, j_usrtime; /* user and system time of last j_waitjed job */static Job *job_list; /* job list */static Job *last_job;static Job *async_job;static pid_t async_pid;static int nzombie; /* # of zombies owned by this process */static INT32 njobs; /* # of jobs started */static int child_max; /* CHILD_MAX */#ifdef JOB_SIGS/* held_sigchld is set if sigchld occurs before a job is completely started */static int held_sigchld;#endif /* JOB_SIGS */#ifdef JOBSstatic struct shf *shl_j;#endif /* JOBS */#ifdef NEED_PGRP_SYNC/* On some systems, the kernel doesn't count zombie processes when checking * if a process group is valid, which can cause problems in creating the * pipeline "cmd1 | cmd2": if cmd1 can die (and go into the zombie state) * before cmd2 is started, the kernel doesn't allow the setpgid() for cmd2 * to succeed. Solution is to create a pipe between the parent and the first * process; the first process doesn't do anything until the pipe is closed * and the parent doesn't close the pipe until all the processes are started. */static int j_sync_pipe[2];static int j_sync_open;#endif /* NEED_PGRP_SYNC */#ifdef TTY_PGRPstatic int ttypgrp_ok; /* set if can use tty pgrps */static pid_t restore_ttypgrp = -1;static pid_t our_pgrp;static int const tt_sigs[] = { SIGTSTP, SIGTTIN, SIGTTOU };#endif /* TTY_PGRP */static void j_set_async ARGS((Job *j));static void j_startjob ARGS((Job *j));static int j_waitj ARGS((Job *j, int flags, const char *where));static RETSIGTYPE j_sigchld ARGS((int sig));static void j_print ARGS((Job *j, int how, struct shf *shf));static Job *j_lookup ARGS((const char *cp, int *ecodep));static Job *new_job ARGS((void));static Proc *new_proc ARGS((void));static void check_job ARGS((Job *j));static void put_job ARGS((Job *j, int where));static void remove_job ARGS((Job *j, const char *where));static void kill_job ARGS((Job *j));static void fill_command ARGS((char *c, int len, struct op *t));/* initialize job control */voidj_init(mflagset) int mflagset;{ child_max = CHILD_MAX; /* so syscon() isn't always being called */#ifdef JOB_SIGS sigemptyset(&sm_default); sigprocmask(SIG_SETMASK, &sm_default, (sigset_t *) 0); sigemptyset(&sm_sigchld); sigaddset(&sm_sigchld, SIGCHLD); setsig(&sigtraps[SIGCHLD], j_sigchld, SS_RESTORE_ORIG|SS_FORCE|SS_SHTRAP);#else /* JOB_SIGS */ /* Make sure SIGCHLD isn't ignored - can do odd things under SYSV */ setsig(&sigtraps[SIGCHLD], SIG_DFL, SS_RESTORE_ORIG|SS_FORCE);#endif /* JOB_SIGS */#ifdef JOBS if (!mflagset && Flag(FTALKING)) Flag(FMONITOR) = 1; /* shl_j is used to do asynchronous notification (used in * an interrupt handler, so need a distinct shf) */ shl_j = shf_fdopen(2, SHF_WR, (struct shf *) 0);# ifdef TTY_PGRP if (Flag(FMONITOR) || Flag(FTALKING)) { int i; /* the TF_SHELL_USES test is a kludge that lets us know if * if the signals have been changed by the shell. */ for (i = NELEM(tt_sigs); --i >= 0; ) { sigtraps[tt_sigs[i]].flags |= TF_SHELL_USES; /* j_change() sets this to SS_RESTORE_DFL if FMONITOR */ setsig(&sigtraps[tt_sigs[i]], SIG_IGN, SS_RESTORE_IGN|SS_FORCE); } }# endif /* TTY_PGRP */ /* j_change() calls tty_init() */ if (Flag(FMONITOR)) j_change(); else#endif /* JOBS */ if (Flag(FTALKING)) tty_init(TRUE);}/* job cleanup before shell exit */voidj_exit(){ /* kill stopped, and possibly running, jobs */ Job *j; int killed = 0; for (j = job_list; j != (Job *) 0; j = j->next) { if (j->ppid == procpid && (j->state == PSTOPPED || (j->state == PRUNNING && ((j->flags & JF_FG) || (Flag(FLOGIN) && !Flag(FNOHUP) && procpid == kshpid))))) { killed = 1; killpg(j->pgrp, SIGHUP);#ifdef JOBS if (j->state == PSTOPPED) killpg(j->pgrp, SIGCONT);#endif /* JOBS */ } } if (killed) sleep(1); j_notify();#ifdef JOBS# ifdef TTY_PGRP if (kshpid == procpid && restore_ttypgrp >= 0) { /* Need to restore the tty pgrp to what it was when the * shell started up, so that the process that started us * will be able to access the tty when we are done. * Also need to restore our process group in case we are * about to do an exec so that both our parent and the * process we are to become will be able to access the tty. */ tcsetpgrp(tty_fd, restore_ttypgrp); setpgid(0, restore_ttypgrp); }# endif /* TTY_PGRP */ if (Flag(FMONITOR)) { Flag(FMONITOR) = 0; j_change(); }#endif /* JOBS */}#ifdef JOBS/* turn job control on or off according to Flag(FMONITOR) */voidj_change(){ int i; if (Flag(FMONITOR)) { /* Don't call get_tty() 'til we own the tty process group */ tty_init(FALSE);# ifdef TTY_PGRP /* no controlling tty, no SIGT* */ ttypgrp_ok = tty_fd >= 0 && tty_devtty; if (ttypgrp_ok && (our_pgrp = getpgID()) < 0) { warningf(FALSE, "j_init: getpgrp() failed: %s", strerror(errno)); ttypgrp_ok = 0; } if (ttypgrp_ok) { setsig(&sigtraps[SIGTTIN], SIG_DFL, SS_RESTORE_ORIG|SS_FORCE); /* wait to be given tty (POSIX.1, B.2, job control) */ while (1) { pid_t ttypgrp; if ((ttypgrp = tcgetpgrp(tty_fd)) < 0) { warningf(FALSE, "j_init: tcgetpgrp() failed: %s", strerror(errno)); ttypgrp_ok = 0; break; } if (ttypgrp == our_pgrp) break; kill(0, SIGTTIN); } } for (i = NELEM(tt_sigs); --i >= 0; ) setsig(&sigtraps[tt_sigs[i]], SIG_IGN, SS_RESTORE_DFL|SS_FORCE); if (ttypgrp_ok && our_pgrp != kshpid) { if (setpgid(0, kshpid) < 0) { warningf(FALSE, "j_init: setpgid() failed: %s", strerror(errno)); ttypgrp_ok = 0; } else { if (tcsetpgrp(tty_fd, kshpid) < 0) { warningf(FALSE, "j_init: tcsetpgrp() failed: %s", strerror(errno)); ttypgrp_ok = 0; } else restore_ttypgrp = our_pgrp; our_pgrp = kshpid; } }# if defined(NTTYDISC) && defined(TIOCSETD) && !defined(HAVE_TERMIOS_H) && !defined(HAVE_TERMIO_H) if (ttypgrp_ok) { int ldisc = NTTYDISC; if (ioctl(tty_fd, TIOCSETD, &ldisc) < 0) warningf(FALSE, "j_init: can't set new line discipline: %s", strerror(errno)); }# endif /* NTTYDISC && TIOCSETD */ if (!ttypgrp_ok) warningf(FALSE, "warning: won't have full job control");# endif /* TTY_PGRP */ if (tty_fd >= 0) get_tty(tty_fd, &tty_state); } else {# ifdef TTY_PGRP ttypgrp_ok = 0; if (Flag(FTALKING)) for (i = NELEM(tt_sigs); --i >= 0; ) setsig(&sigtraps[tt_sigs[i]], SIG_IGN, SS_RESTORE_IGN|SS_FORCE); else for (i = NELEM(tt_sigs); --i >= 0; ) { if (sigtraps[tt_sigs[i]].flags & (TF_ORIG_IGN |TF_ORIG_DFL)) setsig(&sigtraps[tt_sigs[i]], (sigtraps[tt_sigs[i]].flags & TF_ORIG_IGN) ? SIG_IGN : SIG_DFL, SS_RESTORE_ORIG|SS_FORCE); }# endif /* TTY_PGRP */ if (!Flag(FTALKING)) tty_close(); }}#endif /* JOBS *//* execute tree in child subprocess */intexchild(t, flags, close_fd) struct op *t; int flags; int close_fd; /* used if XPCLOSE or XCCLOSE */{ static Proc *last_proc; /* for pipelines */ int i;#ifdef JOB_SIGS sigset_t omask;#endif /* JOB_SIGS */ Proc *p; Job *j; int rv = 0; int forksleep; int ischild; if (flags & XEXEC) /* Clear XFORK|XPCLOSE|XCCLOSE|XCOPROC|XPIPEO|XPIPEI|XXCOM|XBGND * (also done in another execute() below) */ return execute(t, flags & (XEXEC | XERROK));#ifdef JOB_SIGS /* no SIGCHLD's while messing with job and process lists */ sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);#endif /* JOB_SIGS */ p = new_proc(); p->next = (Proc *) 0; p->state = PRUNNING; WSTATUS(p->status) = 0; p->pid = 0; /* link process into jobs list */ if (flags&XPIPEI) { /* continuing with a pipe */ if (!last_job) internal_errorf(1, "exchild: XPIPEI and no last_job - pid %d", (int) procpid); j = last_job; last_proc->next = p; last_proc = p; } else {#ifdef NEED_PGRP_SYNC if (j_sync_open) { /* should never happen */ j_sync_open = 0; closepipe(j_sync_pipe); } /* don't do the sync pipe business if there is no pipeline */ if (flags & XPIPEO) { openpipe(j_sync_pipe); j_sync_open = 1; }#endif /* NEED_PGRP_SYNC */ j = new_job(); /* fills in j->job */ /* we don't consider XXCOM's foreground since they don't get * tty process group and we don't save or restore tty modes. */ j->flags = (flags & XXCOM) ? JF_XXCOM : ((flags & XBGND) ? 0 : (JF_FG|JF_USETTYMODE)); j->usrtime = j->systime = 0; j->state = PRUNNING; j->pgrp = 0; j->ppid = procpid; j->age = ++njobs; j->proc_list = p;#ifdef KSH j->coproc_id = 0;#endif /* KSH */ last_job = j; last_proc = p; put_job(j, PJ_PAST_STOPPED); } fill_command(p->command, sizeof(p->command), t); /* create child process */ forksleep = 1; while ((i = fork()) < 0 && errno == EAGAIN && forksleep < 32) { if (intrsig) /* allow user to ^C out... */ break; sleep(forksleep); forksleep <<= 1; } if (i < 0) { kill_job(j); remove_job(j, "fork failed");#ifdef NEED_PGRP_SYNC if (j_sync_open) { closepipe(j_sync_pipe); j_sync_open = 0; }#endif /* NEED_PGRP_SYNC */#ifdef JOB_SIGS sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);#endif /* JOB_SIGS */ errorf("cannot fork - try again"); } ischild = i == 0; if (ischild) p->pid = procpid = getpid(); else p->pid = i;#ifdef JOBS /* job control set up */ if (Flag(FMONITOR) && !(flags&XXCOM)) { int dotty = 0;# ifdef NEED_PGRP_SYNC int first_child_sync = 0;# endif /* NEED_PGRP_SYNC */# ifdef NEED_PGRP_SYNC if (j_sync_open) { /* * The Parent closes 0, keeps 1 open 'til the whole * pipeline is started. The First child closes 1, * keeps 0 open (reads from it). The remaining * children just have to close 1 (parent has already * closeed 0). */ if (j->pgrp == 0) { /* First process */ close(j_sync_pipe[ischild]); j_sync_pipe[ischild] = -1; first_child_sync = ischild; } else if (ischild) { j_sync_open = 0; closepipe(j_sync_pipe); } }# endif /* NEED_PGRP_SYNC */ if (j->pgrp == 0) { /* First process */ j->pgrp = p->pid; dotty = 1; } /* set pgrp in both parent and child to deal with race * condition */ setpgid(p->pid, j->pgrp);# ifdef TTY_PGRP /* YYY: should this be if (ttypgrp_ok && ischild && !(flags&XBGND)) tcsetpgrp(tty_fd, j->pgrp); instead? (see also YYY below) */ if (ttypgrp_ok && dotty && !(flags & XBGND)) tcsetpgrp(tty_fd, j->pgrp);# endif /* TTY_PGRP */# ifdef NEED_PGRP_SYNC if (first_child_sync) { char c; while (read(j_sync_pipe[0], &c, 1) == -1 && errno == EINTR) ; close(j_sync_pipe[0]); j_sync_open = 0; }# endif /* NEED_PGRP_SYNC */ }#endif /* JOBS */ /* used to close pipe input fd */ if (close_fd >= 0 && (((flags & XPCLOSE) && !ischild) || ((flags & XCCLOSE) && ischild))) close(close_fd); if (ischild) { /* child */#ifdef KSH /* Do this before restoring signal */ if (flags & XCOPROC) coproc_cleanup(FALSE);#endif /* KSH */#ifdef JOB_SIGS sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);#endif /* JOB_SIGS */ cleanup_parents_env();#ifdef TTY_PGRP /* If FMONITOR or FTALKING is set, these signals are ignored, * if neither FMONITOR nor FTALKING are set, the signals have * their inherited values. */ if (Flag(FMONITOR) && !(flags & XXCOM)) { for (i = NELEM(tt_sigs); --i >= 0; ) setsig(&sigtraps[tt_sigs[i]], SIG_DFL, SS_RESTORE_DFL|SS_FORCE); }#endif /* TTY_PGRP */#ifdef HAVE_NICE if (Flag(FBGNICE) && (flags & XBGND)) nice(4);#endif /* HAVE_NICE */ if ((flags & XBGND) && !Flag(FMONITOR)) { setsig(&sigtraps[SIGINT], SIG_IGN, SS_RESTORE_IGN|SS_FORCE); setsig(&sigtraps[SIGQUIT], SIG_IGN, SS_RESTORE_IGN|SS_FORCE); if (!(flags & (XPIPEI | XCOPROC))) { int fd = open("/dev/null", 0); (void) ksh_dup2(fd, 0, TRUE); close(fd);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -