570 likes | 709 Views
Mymake, a simplified Make Program. Poject1. Overview. How does a make work? what does a makefile contain? How to implement make program? How to implement commands in a make program?. Make – the working mechanism.
E N D
Overview • How does a make work? • what does a makefile contain? • How to implement make program? • How to implement commands in a make program?
Make – the working mechanism • Update a program automatically by checking dependency relationships among different pieces of the program according to the rules specified in a makefile • Work in two phases • Read makefiles to build dependency graph of all targets and their prerequisites • Use the dependency graph to determine what targets will need to be update and carry out corresponding commands • Default target for make is the first target specified in a makefile • Rules related to the to be made target will be invoked, others will not be processed
Makefile – its components • Five kinds : explicit rules, implicit rules, variables, directives, and comment • In this project, only explicit rules and comment will be considered. (making life easier) • Rules take the following format: • Target : Prerequisite … • Tab Command • … • All prerequisites must be processed before the target could be made
Implement Make- Phase1 • Scan/read makefile line by line • Remove comments • Save information of all targets • Save information of all prerequisites information • Build the dependency graph • Adjacent matrix (small or very dense graph) • Adjacent list (sparse graph) • Static array Don't need worry about possible memory leakage problems
Implement Make - Graph Representation Adjacent list 3 1 2 1 2 Adjacent matrix-list 3 2 2 3 1 Adjacent matrix 2 3 2 3 1 2 1 1 1 3 2 3
Implement Make - Phase2 • Invoke rules and carry out commands • Target is made after all its prerequisites • Make recursively checks a target's all prerequisites # Makefile Case 1 : Using explict rules main: main.o f1.o f2.o gcc -o main main.o f1.o f2.o main.o: main.c f1.h f2.h gcc -c main.c -o main.o f1.o: f1.c f1.h gcc -c f1.c -o f1.o f2.o: f2.c f2.h gcc -c f2.c -o f2.o clean: rm *.o main main f1.o f2.o main.o f1.c f2.c f1.h f2.h main.c
Implement Make – Traverse dependency graph • Depth First traversal (DFS) • Breadth First traversal (BFS) • DFS should be used in this case. Recursive version ---------------------------------------------------------- DFS(u): Mark u as “Explored” and add u to R For each edge (u,v) incident to u If v is not marked “Explored” then Recursively invoke DFS(v) Endif Endfor Non-Recursive Stack version ---------------------------------------------------------- DFS(u): Initialize S to be a stack with one elements While S is not empty Take a node u from S If Explored[u] = false then Set Explored[u] = true For each edge (u,v) incident to u Add v to the stack S Endfor Endif Endwhile
Implement Make – Modify DFS graph traversal • A cycle must be handled correctly to avoid infinite loop. • A target may not always carry out commands associated with it. Recursive version ---------------------------------------------------------- DFS(u): Mark u as “Made” For each edge (u,v) incident to u If v is not marked “Made” Recursively invoke DFS(v) Endif Endfor If u is need to be made then Carry out the commands associated with u Endif
Implement Make – Cases that a target is needed to be updated • A target does not exist (it is a file). • A target exists and one of its prerequisite is newer than it. (check st_ctime field)
Implement Make – Emulate GNU make • Try to follow the behavior of the GNU make. • Print out error information to indicate what kind of error it might be. • If there are some cases that your make program can not handle, you must make sure it will not crash your make program. (or report as bugs in your README for the project)
Commands to be supported • A simple command with/without arguments • Multiple commands with/without arguments separated by ';' • The 'cd' command (making sense in multiple commands) • Multiple piped commands: cmd1 | cmd2 | cmd3 | cmd4 • A command to be executed in background ('&') • A command with redirected I/O ('>' and '<') • No need to handle combinations of the forms • Support an environment variable MYMAKEPATH storing the paths to search for the commands.
System Calls • fork() • exec() • Overwrites current process with a new one • 6 different versions • http://www.opengroup.org/onlinepubs/000095399/functions/exec.html • We use execv() • int execv(const char *path, char *const argv[]) • path is full path to the file • argv[] is a list of arguments • First arg is the name of the executable • Last arg is a null pointer • wait(), waitpid()
Simple Command int simplecmd(char *argv[]) { int ret = 0 ; if ((pid = fork()) == 0) { if (execv(argv[0], argv) == -1) { perror(“execv”); exit(-1); } } if (pid == -1) { perror(“fork”); ret = -1; } if ( waitpid(pid, &stat, 0) == -1) { perror(“waitpid”); ret = -1; } return ret; }
Background Command • Difference between background and foreground command. Parent process Child process wait() fork() Parent process Child process fork()
cd Command • Two ways to implement this command • chdir(const char *path) • Change the current work directory • Maintain a variable to store a relative path used for searching for files, cd will change the value of this variable • cd's behavior in GNU make • Considering the following case: • all : • cd bak; ls -l • ls -l • Only the first “ls -l” will be influence by the “cd bak” • Whenever you change the current directory using chdir, you need to change it back.
I/O redirection • Everything in UNIX is a file, and I/O streams are no exception • I/O streams are written and read like files • Each I/O stream has a file descriptor associated with it • I/O redirection is handled through file descriptors • By default each process is created with 3 file descriptors • 0 stdin, 1 stdout, 2 stderr • To redirect I/O means to replace them with files • dup(fd) • Copies a fd and insert it into the first empty file descriptor slot
fork() file descriptors file descriptors 0 stdin 1 stdout 2 stderr 0 stdin 1 stdout 2 stderr shell Input Redirection shell
open(input_file, O_RDONLY) file descriptors file descriptors 0 stdin 1 stdout 2 stderr 0 stdin 1 stdout 2 stderr Input Redirection shell shell
open(input_file, O_RDONLY) file descriptors file descriptors 0 stdin 1 stdout 2 stderr 0 stdin 1 stdout 2 stderr 3 input_file Input Redirection shell shell
file descriptors file descriptors 0 stdin 1 stdout 2 stderr 0 stdin 1 stdout 2 stderr 3 input_file Input Redirection close(0) shell shell
dup(3) file descriptors file descriptors 0 stdin 1 stdout 2 stderr 0 input_file 1 stdout 2 stderr 3 input_file Input Redirection shell shell
file descriptors file descriptors 0 stdin 1 stdout 2 stderr 0 input_file 1 stdout 2 stderr 3 input_file Input Redirection close(3) shell shell
fork() file descriptors file descriptors 0 stdin 1 stdout 2 stderr 0 stdin 1 stdout 2 stderr shell Output Redirection shell
open(output_file, O_RDWR | O_CREAT | O_TRUC) file descriptors file descriptors 0 stdin 1 stdout 2 stderr 0 stdin 1 stdout 2 stderr Output Redirection shell shell
open(output_file, O_RDWR | O_CREAT | O_TRUC) file descriptors file descriptors 0 stdin 1 stdout 2 stderr 0 stdin 1 stdout 2 stderr 3 output_file Output Redirection shell shell
file descriptors file descriptors 0 stdin 1 stdout 2 stderr 0 stdin 1 stdout 2 stderr 3 output_file Output Redirection shell shell close(1)
dup(3) file descriptors file descriptors 0 stdin 1 stdout 2 stderr 0 stdin 1 2 stderr 3 output_file Output Redirection shell shell
file descriptors file descriptors 0 stdin 1 stdout 2 stderr 0 stdin 1 output_file 2 stderr 3 output_file Output Redirection dup(3) shell shell
file descriptors file descriptors 0 stdin 1 stdout 2 stderr 0 stdin 1 output_file 2 stderr 3 output_file Output Redirection close(3) shell shell
I/O redirection (“>” “<”) • More < 123 fd = open(123, o_rdonly) close(0) dup(fd) close(fd) • ls > 123 fd = open(123, O_RDWR | O_CREAT | O_TRUC) close(1) dup(fd) close(fd)
Multipile Piped Commands • Pipes are for Interprocess Communication IPC • ps aux | more • Output of ps is the input of more • int pipe(int filedes[2]); • Creates a pair of file descriptors • File descriptors actually refer to a kernel FIFO data structure • Filedes[1] is for writing • Filedes[0] is for reading • Data written to filedes[1] can be read from filedes [0]
file descriptors 0 stdin 1 stdout 2 stderr Pipe int p1_to_p2[2]; pipe(p1_to_p2); shell
file descriptors 0 stdin 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] Pipe int p1_to_p2[2]; pipe(p1_to_p2); shell
fork() int p1_to_p2[2]; pipe(p1_to_p2); file descriptors file descriptors 0 stdin 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] 0 stdin 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] shell Pipe int p1_to_p2[2]; pipe(p1_to_p2); shell
fork() int p1_to_p2[2]; pipe(p1_to_p2); file descriptors file descriptors file descriptors 0 stdin 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] 0 stdin 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] 0 stdin 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] shell Pipe int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); shell shell
file descriptors file descriptors file descriptors 0 stdin 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] 0 stdin 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] 0 stdin 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] Pipe int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); shell shell shell
file descriptors file descriptors file descriptors 0 stdin 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] 0 stdin 1 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] 0 stdin 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] dup(4) Pipe int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); shell shell shell
file descriptors file descriptors file descriptors 0 stdin 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] 0 stdin 1 p1_to_p2[1] 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] 0 stdin 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] dup(4) Pipe int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); shell shell shell
file descriptors file descriptors file descriptors 0 stdin 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] 0 stdin 1 p1_to_p2[1] 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] 0 stdin 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] Pipe int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); shell shell shell
file descriptors file descriptors file descriptors 0 stdin 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] 0 stdin 1 p1_to_p2[1] 2 stderr 0 stdin 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] Pipe int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); shell shell shell
file descriptors file descriptors file descriptors 0 stdin 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] 0 stdin 1 p1_to_p2[1] 2 stderr execvp(bin_path, argv) 0 stdin 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] Pipe int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); shell shell shell
file descriptors file descriptors file descriptors 0 stdin 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] 0 stdin 1 p1_to_p2[1] 2 stderr execvp(bin_path, argv) 0 stdin 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] Pipe int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); shell shell shell
file descriptors file descriptors file descriptors dup(3) 0 stdin 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] 0 stdin 1 p1_to_p2[1] 2 stderr execvp(bin_path, argv) 0 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] Pipe int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); shell shell shell
file descriptors file descriptors file descriptors dup(3) 0 stdin 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] 0 stdin 1 p1_to_p2[1] 2 stderr execvp(bin_path, argv) 0 p1_to_p2[0] 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] Pipe int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); shell shell shell
file descriptors file descriptors file descriptors 0 stdin 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] 0 stdin 1 p1_to_p2[1] 2 stderr execvp(bin_path, argv) 0 p1_to_p2[0] 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] Pipe int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); shell shell shell
file descriptors file descriptors file descriptors 0 stdin 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] 0 stdin 1 p1_to_p2[1] 2 stderr execvp(bin_path, argv) 0 p1_to_p2[0] 1 stdout 2 stderr Pipe int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); shell shell shell
file descriptors file descriptors file descriptors 0 stdin 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] 0 stdin 1 p1_to_p2[1] 2 stderr execvp(bin_path, argv) 0 p1_to_p2[0] 1 stdout 2 stderr execvp(bin_path, argv) Pipe int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); shell shell shell
file descriptors file descriptors file descriptors 0 stdin 1 stdout 2 stderr 3 p1_to_p2[0] 4 p1_to_p2[1] 0 stdin 1 p1_to_p2[1] 2 stderr execvp(bin_path, argv) 0 p1_to_p2[0] 1 stdout 2 stderr execvp(bin_path, argv) Pipe int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); shell shell shell
file descriptors file descriptors file descriptors 0 stdin 1 stdout 2 stderr 0 stdin 1 p1_to_p2[1] 2 stderr execvp(bin_path, argv) 0 p1_to_p2[0] 1 stdout 2 stderr execvp(bin_path, argv) Pipe int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); int p1_to_p2[2]; pipe(p1_to_p2); shell shell shell