OS Interface 1 • Learn OS interface before OS internal. • Learn programming with OS system calls (the ultimate API). • UNIX as an example • A subset of common UNIX System calls – For a complete list of system calls on solaris, try “man -s 2 intro” on program.cs.fsu.edu. – The subset breaks down into three areas: • Process Management: fork(), exec(), exit(), wait() • InterProcess Communication (IPC): pipe(), signal(). • File and I/O System calls: open(), creat(), read(), write(), lseek(), close(), unlink(), dup(), and stat() – A good reference book: • W. Richard Stevens, “Advanced Programming in the UNIX environment,” Addison-Wesley. Process management • Examining processes in UNIX – The ps command displays standard process attributes. – /proc directory contains more information – top, vmstat commands examine CPU and memory usage statistics. Process management system calls • Process creation: – Reasons: • User needs to run a program • OS needs to create a process to provide a service • One process starts another process – In UNIX, a process is created by duplicating the image of the calling process (parent process). • System call: fork(); Process creation system call: fork() – create a new process. The new process (child) is an exact copy of the calling process (parent). Examples (example1.cpp, example2.cpp): #include <sys/types.h> #include <unistd.h> #include <iostream> using namespace std; main() { fork(); cout << “111111\n”; } #include <sys/types.h> #include <unistd.h> #include <iostream> using namespace std; main() { getchar(); cout << “1111111\n”; fork(); getchar(); cout << “2222222\n”; } • Points to note: – fork() is called once, but returns two times • Once in the parent • Once in the child • Parent and child are totally independent (e.g modifying a variable in one process has no effects on the other process). – How to distinguish between parent and child • Return 0 in child • Return a positive number (process id) in parent – If the process creation fails, return –1; • All UNIX system calls return –1 when the the calls fail. • You should always check the return value of each system call. – See example3.c and example4.cpp #include <sys/types.h> #include <unistd.h> main() { int pid; if ((pid = fork()) == 0) { // child process printf(“I am child, my pid = %d\n”, getpid()); } else { // parent process printf(“I just made a child, pid = %d\n”, pid ); } } example3.c #include <sys/types.h> #include <unistd.h> #include <iostream> using namespace std; main() { int val; val = 10; if (fork()) val = 20; cout << “val = “ << val << “\n”; } example4.cpp What is the value of I after ‘I = fork();’ • Three cases • What is the purpose for creating processes? – In most cases, to run commands. – fork() itself does not quite do it: the source code of the child process must be in the source code of the parent process. – We need something like ‘run_command’ routine that can execute pre-compiled commands. – In UNIX, this is done by the exec family system calls. The exec family system calls • Load a new executable into the current process image. • The current process image is erased (no return if the call is successful). • For more details, ‘man –a exec’ • An example: execute the executable path. int execv(const char *path, char *const argv[]); #include <stdlib.h> #include <unistd.h> #include <iostream> #include <stdio.h> Using namespace std; main() { char command[100]; char *argv[10]; strcpy(command, “/bin/ls”); argv[0] = command; argv[1] = “/”; argv[2] = NULL; execv(command, argv); cout << “finished\n”; } example5.cpp (“/bin/ls /”) • The argv is set exactly the same as the regular C/C++ programs. • argv[0] must always be the same as the command. • The last command argument must be followed by a NULL pointer. #include <stdlib.h> #include <unistd.h> #include <iostream> #include <stdio.h> Using namespace std; main() { char command[100]; char *argv[10]; strcpy(command, “/bin/ls”); argv[0] = command; argv[1] = “/”; argv[2] = NULL; if (execv(command, argv) != -1) cout << “successful!!\n”; else cout << “failed n”; } Any comment about this program? • The exec family system calls destroy the parent process image. •What if we want to keep the original process image (e.g. to run multiple commands?) • Fork an extra image to be destroyed by the exec system call. #include <stdlib.h> #include <unistd.h> #include <iostream.h> #include <stdio.h> runcommand(char *command, char *argv[]) { if (fork() == 0) { // child process execv(command, argv); } // parent process // continue } Run using a child process • Terminate process: exit #include <stdlib.h> void exit(int status); • UNIX will call this routine automatically if it is not called by the user. • The status in exit can be examined by the parent process. • UNIX convention: • All successful execution exits with code 0. • Default exit code when the end of the main() function is reached. • Exit code != 0 indicates abnormal execution. • Wait for a child process to stop: wait, waitpid #include <sys/types.h> #include <sys/wait.h> pid_t wait(int *stat_loc); pid_t waitpid(pid_t pid, int *status, int options); #include <stdlib.h> #include <unistd.h> #include <iostream.h> #include <stdio.h> runforeground(char *command, char *argv[]) { int pid, status, rpid; if ((pid = fork()) == 0) { // child process execv(command, argv); } rpid = wait(&status); while ((rpid!= pid) && (rpid != -1)) rpid = wait(&status); } Foreground running #include <stdlib.h> #include <unistd.h> #include <iostream.h> #include <stdio.h> runbackground(char *command, char *argv[]) { if (fork() == 0) { // child process execv(command, argv); } // parent process // continue } Background running • Exit/wait • child exit (10); parent wait(&stat_loc); • The status in exit can be examined in the wait. See example6.c, example7.c • status = WEXITSTATUS(stat_loc) • status = 0 for successful execution. • status != 0 for failed execution. • stat_loc also carries other information • WIFSIGNALED(stat_loc) • whether this process is killed by a signal (e.g ctrl-C). • For more information, ‘man –a wait’ wait • What are the <defunct> processes in ps? – The process has exited. – But it has not been waited upon. • The entry in the process table is still occupied – This is for passing exit status to the parent. • The wait system call in the parent/or care taker removes the entry from the process table. • Implication? Make sure to have wait in your code if you create a process in a program. • Process manipulation system calls fork() -- duplicate current process execv() -- run a new executable file in the current process wait() -- wait for the child process to finish exit() -- terminate process • For each system call above, name one thing that is different from a typical routine call?