diff --git a/exercise/ex8-26/Makefile b/exercise/ex8-26/Makefile new file mode 100644 index 0000000..349a326 --- /dev/null +++ b/exercise/ex8-26/Makefile @@ -0,0 +1,42 @@ +# Makefile for ex8-26 + +# mofaph@gmail.com +# 2013-7-7 + +CC = gcc +CFLAGS = -Wall + +PROGRAM += t +PROGRAM += t-job + +OBJS += ex8-26.o +OBJS += random_fork.o +OBJS += job.o + +TOBJS += t-job.o + +PHONY += all +PHONY += clean +PHONY += TAGS + +.PHONY: $(PHONY) + +all: $(PROGRAM) + +t: $(OBJS) + $(CC) $(CFLAGS) $^ -o $@ + +t-job: t-job.o job.o + $(CC) $(CFLAGS) $^ -o $@ + +ex8-26.o: shellex.c + $(CC) $(CFLAGS) -c $< -o $@ + +t-job.o: t-job.c job.h +job.o: job.c job.h + +TAGS: + find . -type f -name "*.[ch]" -print | xargs etags - + +clean: + rm -f $(PROGRAM) $(OBJS) $(TOBJS) diff --git a/exercise/ex8-26/job.c b/exercise/ex8-26/job.c new file mode 100644 index 0000000..58f957e --- /dev/null +++ b/exercise/ex8-26/job.c @@ -0,0 +1,216 @@ +/* + * 8.26 + * + * mofaph@gmail.com + * 2013-7-14 + * + * 这个文件包含了用来处理作业控制的代码 + */ + +#include +#include +#include + +#include "job.h" + +/* + * 作业控制列表从 1 开始计数,第 0 个位置是前台进程组 + */ + +static const int beg = 1; +static int max, end; +static struct job **job; + +/* empty: 1, otherwise: 0 */ +int is_empty(struct job *job_list[], int len) +{ + int i; + for (i = beg; i < len; i++) { + if (job_list[i]) + return 0; + } + return 1; +} + +void init_job(void) +{ + /* Background */ + max = 2; + end = 1; + job = malloc(sizeof(struct job *) * max); + if (!job) { + fprintf(stderr, "init_job: malloc error\n"); + exit(-1); + } + + /* Foreground */ + job[0] = malloc(sizeof(struct job)); + if (!job[0]) { + fprintf(stderr, "init_job: init foreground process failed!\n"); + free_job(); + exit(-1); + } + job[0]->command = NULL; +} + +void free_job(void) +{ + int i; + for (i = 0; i < end; i++) + free(job[i]); + free(job); +} + +int add_job(pid_t pgid, int state, char *command, int len) +{ + struct job *j = malloc(sizeof(struct job)); + if (!j) + goto malloc_failed; + j->pgid = pgid; + j->state = state; + j->command = malloc(len); + if (!j->command) + goto malloc_failed; + memmove(j->command, command, len); + + if (end != beg && is_empty(job, end)) + end = beg; + if (end+1 == max) { + struct job **old_job = job; + int new_max = max * 3 / 2; + job = realloc(old_job, new_max*sizeof(struct job *)); + if (!job) { + job = old_job; + free(j->command); + goto malloc_failed; + } + max = new_max; + } + job[end] = j; + end += 1; + printf("add_job: job list %d long now\n", end); + + return 0; + +malloc_failed: + perror("malloc"); + fprintf(stderr, "add_job: malloc failed!\n"); + free(j); /* 释放 NULL 是无害的 */ + return -1; +} + +int find_job(pid_t pgid) +{ + int i; + for (i = beg; i < end; i++) + if (job[i] && job[i]->pgid == pgid) + return i; + return -1; +} + +void delete_job(pid_t pgid) +{ + int jid = find_job(pgid); + if (jid >= beg) { + free(job[jid]); + job[jid] = NULL; + } +} + +void update_job(pid_t pgid, int state) +{ + int jid = find_job(pgid); + if (beg <= jid && jid < end) { + job[jid]->state = state; + printf("[%d] %d %s\n", jid, (int)pgid, job[jid]->command); + } else { + printf("%d: No such process\n", (int)pgid); + } +} + +pid_t get_pgid(int jid) +{ + if (jid < end && job[jid]) + return job[jid]->pgid; + return -1; +} + +void print_job(int i) +{ + printf("[%d] %d", i, (int)job[i]->pgid); + char *job_state; + if (job[i]->state == JOB_RUNNING) + job_state = "Running"; + else if (job[i]->state == JOB_STOPPED) + job_state = "Stopped"; + else if (job[i]->state == JOB_DONE) + job_state = "Done"; + else + job_state = "Unkown"; + printf(" %s %s\n", job_state, job[i]->command); +} + +void print_job_by_pgid(pid_t pgid) +{ + int jid = find_job(pgid); + print_job(jid); +} + +void print_all_job(void) +{ + int i; + for (i = beg; i < end; i++) { + if (!job[i]) + continue; + print_job(i); + if (job[i]->state == JOB_DONE) + delete_job(job[i]->pgid); + } +} + +void print_foreground(void) +{ + if (job[0]->command) + printf("%s\n", job[0]->command); +} + +void set_foreground(pid_t pgid, char *command, int len) +{ + job[0]->pgid = pgid; + job[0]->state = JOB_RUNNING; + job[0]->command = malloc(len); + if (!job[0]->command) { + perror("malloc"); + exit(-1); + } + memmove(job[0]->command, command, len); +} + +pid_t foreground_pgid(void) +{ + return job[0]->pgid; +} + +void get_foreground_command(char *command) +{ + int len = strlen(job[0]->command) + 1; + memmove(command, job[0]->command, len); +} + +void move_to_background(pid_t pgid, int state, char *command, int len) +{ + int jid = find_job(pgid); + if (jid < 0) + add_job(pgid, state, command, len); + else + update_job(pgid, state); +} + +void move_to_foreground(pid_t pgid) +{ + int jid = find_job(pgid); + if (jid >= beg) + *job[0] = *job[jid]; + else + printf("%d: No such process\n", (int)pgid); +} diff --git a/exercise/ex8-26/job.h b/exercise/ex8-26/job.h new file mode 100644 index 0000000..17d0164 --- /dev/null +++ b/exercise/ex8-26/job.h @@ -0,0 +1,43 @@ +/* + * 8.26 + * + * mofaph@gmail.com + * 2013-7-14 + */ + +#ifndef __job_h +#define __job_h + +#include + +#define JOB_RUNNING 0 +#define JOB_STOPPED 1 +#define JOB_DONE 2 + +struct job { + pid_t pgid; /* 进程组 ID */ + int state; /* 作业状态 */ + char *command; /* 运行作业的命令 */ +}; + +extern void init_job(void); +extern int add_job(pid_t pgid, int state, char *command, int len); +extern void delete_job(pid_t pgid); +extern int find_job(pid_t pgid); +extern void free_job(void); +extern void update_job(pid_t pgid, int state); + +extern pid_t get_pgid(int jid); + +extern void print_job(int i); +extern void print_job_by_pgid(pid_t pgid); +extern void print_all_job(void); + +extern pid_t foreground_pgid(void); +extern void get_foreground_command(char *command); +extern void print_foreground(void); +extern void set_foreground(pid_t pgid, char *command, int len); +extern void move_to_background(pid_t pgid, int state, char *command, int len); +extern void move_to_foreground(pid_t pgid); + +#endif /* __job_h */ diff --git a/exercise/ex8-26/random_fork.c b/exercise/ex8-26/random_fork.c new file mode 100644 index 0000000..d1ea96b --- /dev/null +++ b/exercise/ex8-26/random_fork.c @@ -0,0 +1,62 @@ +/* + * p521 -- code/ecf/rfork.c + * + * 下面的代码包含了一个暴露竞争的简便技巧 + * + * 像 procmask2.c 那样的竞争难以发现,因为它们依赖于内核相关的调度决策。在一次 + * fork() 调用之后,有些内核调度子进程先运行,而有些内核调度父进程先运行。如果你 + * 要在后一种系统上运行 procmask1.c 的代码,它绝不会失败,无论你测试多少遍。但是 + * 一旦在前一种系统上运行这段代码,那么竞争就会暴露出来,代码会失败。 + * + * 下面的代码是一个 fork() 的包装函数,它随机地决定父进程和子进程执行的顺序。父进 + * 程和子进程扔一枚硬币来决定谁会休眠,因而给另一个进程被调度的机会。 + * + * 如果我们运行这个代码多次,那么我们就有极高的概率会测试到父子进程执行的两种顺序, + * 无论这个特定内核的调度策略是什么样子的。 + */ + +#include +#include +#include +#include +#include + +/* Sleep for a random period between [0, MAX_SLEEP] us. */ +#define MAX_SLEEP 100000 + +/* Macro that maps val into the range [0, RAND_MAX] */ +#define CONVERT(val) (((double)val)/(double)RAND_MAX) + +pid_t random_fork(void) +{ + static struct timeval time; + unsigned bool, secs; + pid_t pid; + + /* Generate a different seed each time the function is called */ + gettimeofday(&time, NULL); + srand(time.tv_usec); + + /* Determine whether to sleep in parent of child and for how long */ + bool = (unsigned)(CONVERT(rand()) + 0.5); + secs = (unsigned)(CONVERT(rand()) * MAX_SLEEP); + + /* Call the real fork function */ + if ((pid = fork()) < 0) + return pid; + + /* Randomly decide to sleep in the parent or the child */ + if (pid == 0) { /* Child */ + if (bool) { + usleep(secs); + } + } + else { /* Parent */ + if (!bool) { + usleep(secs); + } + } + + /* Return the PID like a normal fork call */ + return pid; +} diff --git a/exercise/ex8-26/shellex.c b/exercise/ex8-26/shellex.c new file mode 100644 index 0000000..7ab4fe1 --- /dev/null +++ b/exercise/ex8-26/shellex.c @@ -0,0 +1,280 @@ +/* + * 8.26 + * + * mofaph@gmail.com + * 2013-7-7 + * + * unix> make + * unix> ./t + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "job.h" + +extern pid_t random_fork(void); + +#define MAXLINE 4096 +#define MAXARGS 128 + +extern char **environ; + +void reap_child(int signo) +{ + for (;;) { + /* We don't care child's terminated status */ + int status; + int ret = waitpid(-1, &status, WNOHANG); + if (ret == 0) { /* child didn't terminated, return immediately */ + break; + } else if (ret < 0 && errno == ECHILD) /* no child */ { + break; + } else if (ret > 0) { + if (WIFSIGNALED(status)) + printf("Job %d terminated by signal: %s\n", ret, strsignal(WTERMSIG(status))); + delete_job(ret); + } else { + /* nothing */; + } + } +} + +void wait_for_job(pid_t pgid) +{ + int status; + int ret = waitpid(-pgid, &status, WUNTRACED); + if (ret == -1) { + perror("waitpid"); + return; + } + /* Child stopped or terminated */ + if (WIFSTOPPED(status)) { + printf("Job %d stopped by signal: %s\n", (int)pgid, strsignal(WSTOPSIG(status))); + } else if (WIFSIGNALED(status)) { + printf("Job %d terminated by signal: %s\n", (int)pgid, strsignal(WTERMSIG(status))); + delete_job(pgid); + } else { + delete_job(pgid); + } +} + +/* If first arg is a builtin command, run it and return true */ +int builtin_command(char **argv) +{ + if (!strcmp(argv[0], "quit")) { + exit(0); + } else if (!strcmp(argv[0], "bg") || !strcmp(argv[0], "fg")) { + int jid; + pid_t pgid; + char *p = argv[1]; + if (*p == '%') { + p += 1; + jid = atoi(p); + pgid = get_pgid(jid); + if (pgid < 0) { + fprintf(stderr, "%d: No such job\n", jid); + return 1; + } + } else { + pgid = atoi(p); + } + kill(-pgid, SIGCONT); + if (!strcmp(argv[0], "fg")) { + move_to_foreground(pgid); + print_foreground(); + wait_for_job(pgid); + } else { + update_job(pgid, JOB_RUNNING); + } + return 1; + } else if (!strcmp(argv[0], "jobs")) { + print_all_job(); + return 1; + } else if (!strcmp(argv[0], "&")) { /* Ignore singleton & */ + return 1; + } else { + return 0; /* Not a builtin command */ + } +} + +/* parseline - Parse the command line and build the argv array */ +int parseline(char *buf, char **argv) +{ + char *delim; /* Points to first space delimiter */ + int argc; /* Number of args */ + int bg; /* Background job? */ + + buf[strlen(buf)-1] = ' '; /* Replace trailing '\n' with space */ + while (*buf && (*buf == ' ')) /* Ignore leading spaces */ + buf++; + + /* Build the argv list */ + argc = 0; + while ((delim = strchr(buf, ' '))) { + argv[argc++] = buf; + *delim = '\0'; + buf = delim + 1; + while (*buf && (*buf == ' ')) /* Ignore spaces */ + buf++; + } + argv[argc] = NULL; + + if (argc == 0) /* Ignore blank line */ + return 1; + + /* Should the job run in the background? */ + if ((bg = (*argv[argc-1] == '&')) != 0) + argv[--argc] = NULL; + + return bg; +} + +void run_the_job(char *filename, char *argv[], char *env[]) +{ + int ret = setpgid(0, 0); + if (ret < 0) { + fprintf(stderr, "Failed to set group process id\n"); + exit(-1); + } + if (execve(argv[0], argv, env) < 0) { + printf("%s: Command not found.\n", argv[0]); + exit(0); + } +} + +/* eval - Evaluate a command line */ +void eval(char *cmdline) +{ + char buf[MAXLINE]; /* Holds modified command line */ + strcpy(buf, cmdline); + char *argv[MAXARGS]; /* Argument list execve() */ + int bg = parseline(buf, argv); /* Should the job run in bg or fg? */ + if (argv[0] == NULL) + return; /* Ignore empty lines */ + if (builtin_command(argv)) + return; + + pid_t pid = random_fork(); + if (pid < 0) { + perror("random_fork"); + exit(0); + } + + /* Child runs user job */ + if (pid == 0) + run_the_job(argv[0], argv, environ); + + /* Parent waits for foreground job to terminate */ + int ret = setpgid(pid, pid); + if (ret < 0 && errno != EPERM && errno != EACCES) + return; + char buf2[MAXLINE]; + strcpy(buf2, cmdline); + int len2 = strlen(buf2); + buf2[len2] = '\0'; + if (!bg) { + set_foreground(pid, buf2, len2); + wait_for_job(pid); + } else { + printf("%d %s", pid, cmdline); + pid_t ret = waitpid(pid, NULL, WNOHANG | WUNTRACED); + if (ret == 0) + move_to_background(pid, JOB_RUNNING, buf2, len2); + else if (ret == pid) + move_to_background(pid, JOB_STOPPED, buf2, len2); + } +} + +void stop_handler(int signo) +{ + pid_t pgid = foreground_pgid(); + char command[MAXLINE]; + get_foreground_command(command); + int len = strlen(command) + 1; + kill(-(int)pgid, SIGSTOP); + if (pgid > 0) + move_to_background(pgid, JOB_STOPPED, command, len); + fprintf(stderr, "move job %d to background\n", pgid); +} + +void interrupt_handler(int signo) +{ + pid_t pgid = foreground_pgid(); + kill(-(int)pgid, SIGINT); +} + +void install_signal_handler(void) +{ + int ret; + + /* Install SIGTSTP handler */ + struct sigaction new, old; + new.sa_handler = stop_handler; + sigemptyset(&new.sa_mask); + new.sa_flags = SA_RESTART; + ret = sigaction(SIGTSTP, &new, &old); + if (ret < 0) { + fprintf(stderr, + "Warning: Failed to install SIGTSTP handler, " + "Leave to default handler\n"); + } + + /* Install SIGINT handler */ + memset(&new, 0, sizeof(new)); + new.sa_handler = interrupt_handler; + sigemptyset(&new.sa_mask); + new.sa_flags = SA_RESTART; + ret = sigaction(SIGINT, &new, &old); + if (ret < 0) { + fprintf(stderr, + "Warning: Failed to install SIGINT handler, " + "Leave to default handler\n"); + } + + /* Install SIGCHLD handler */ + memset(&new, 0, sizeof(new)); + new.sa_handler = reap_child; + sigemptyset(&new.sa_mask); + new.sa_flags = SA_RESTART; + ret = sigaction(SIGCHLD, &new, &old); + if (ret < 0) { + fprintf(stderr, + "Warning: Failed to install SIGCHLD handler, " + "Leave to default handler"); + } +} + +int main(void) +{ + init_job(); + install_signal_handler(); + + for (;;) { + /* Read */ + printf("> "); + char cmdline[MAXLINE]; /* Command line */ + char *ret = fgets(cmdline, MAXLINE, stdin); + if (ret == NULL) { + if (ferror(stdin)) { + perror("fgets"); + free_job(); + return -1; + } + } + if (feof(stdin)) { + free_job(); + return 0; + } + + /* Evaluate */ + eval(cmdline); + } +} diff --git a/exercise/ex8-26/t-job.c b/exercise/ex8-26/t-job.c new file mode 100644 index 0000000..ed4ab2b --- /dev/null +++ b/exercise/ex8-26/t-job.c @@ -0,0 +1,76 @@ +/* + * 8.26 + * + * mofaph@gmail.com + * 2013-7-14 + * + * 测试 job.c + * + * unix> cc -Wall t-job.c job.c -o t-job + */ + +#include +#include + +#include "job.h" + +#define MAXLINE 1024 + +int main(void) +{ + char buf[MAXLINE]; + init_job(); + + pid_t pgid; + int state; + char command[MAXLINE]; + do { + printf("[A]dd/[D]elete/[P]rint/[Q]uit/[S]et_fg/[F]g/[B]g/[p]rint_fg?\n"); + fgets(buf, sizeof(buf), stdin); + switch (buf[0]) { + case 'A': + printf("pgid state command: "); + fflush(stdin); + scanf("%d %d %s", &pgid, &state, command); + add_job(pgid, state, command, MAXLINE); + printf("Add job %d to job list\n", (int)pgid); + break; + case 'D': + print_all_job(); + printf("JID? "); + scanf("%d", &pgid); + delete_job((int)pgid); + break; + case 'P': + print_all_job(); + break; + case 'S': + printf("pgid command: "); + fflush(stdin); + scanf("%d %s", &pgid, command); + set_foreground(pgid, command, MAXLINE); + break; + case 'p': + print_foreground(); + break; + case 'F': + print_all_job(); + printf("JID? "); + scanf("%d", &pgid); + move_to_foreground(pgid); + break; + case 'B': + printf("pgid state command: "); + fflush(stdin); + scanf("%d %d %s", &pgid, &state, command); + int len = strlen(command) + 1; + move_to_background(pgid, state, command, len); + break; + default: + break; + } + } while (buf[0] != 'Q'); + + free_job(); + return 0; +}