COMP 3000 Lab 3 2011
A few guidelines:
- Submit your solutions for both Part A and Part B via WebCT by Monday, October 24th at 11:30 PM (note the extension).
- Please answer using a single text file. Do not submit doc, docx, pdf, or other formats. Also, please do not submit an archive (zip, tarball, rar) of multiple text files, just upload one. (Please don't just cut and paste your work into a text box on webct.)
- Show all your work. Short answers are not sufficient; you should list any websites or individuals you consult and should document any experiments you conducted. For any question that you could just answer without any external reference or experiment, write "(no work required)" after your answer.
- All your codes should compile and run. Partial code fragments or explanations will not be sufficient.
Part A
- Compile run-program.c with the command gcc -g run-program.c -o run-program and use it to answer the following questions.
- Is the line "Is this line printed?" printed when you execute ./run-program /bin/ls -a /tmp? Why? (1 mark)
- Change the program to use execve instead of execvp. What is the difference between the two system calls? (1 mark)
- Linux signals is a simple form of IPC that is used for a variety of purposes.
- What signal is generated when a program attempts to dereference an invalid pointer? Give a simple program that generates such a signal.(1 mark)
- How would you send a signal to a process to have it pause execution? Resume? (1 mark)
- What command can lower the priority of an already running process? (1 mark)
- Create run-program-reverse.c that is the same as run-program.c except that it interprets its arguments in reverse order. Hence, ./run-program-reverse /tmp -a /bin/ls will do the same as ./run-program /bin/ls -a /tmp.(2 marks)
- Create sanitize-env.c that does the same as run-program.c except it exec's the program with all environment variables stripped except the PATH and TERM variables (assuming they are defined). Be sure to document how you tested your program. (2 marks)
Part B
- Create a simple program receive-usr1.c that uses sigaction in order to print "Ouch!" when you send the program the USR1 signal. Note that your program should not consume much CPU time while waiting (i.e., do not poll for the signal).(2 marks)
- Create a simple program nocrash.c that accesses an invalid pointer but then prints "Oops, my mistake" rather than crash.(2 marks)
- Create a program run-program-dots.c that works the same as run-program.c, except that it prints one dot every second while a given program runs. Note that it should stop printing dots once the exec'd program terminates. Your solution should use the fork(), sleep(), execve(), and sigaction() calls.(2 marks)
- What is the difference between the PR and NI columns in the top command? Explain in the context of running a CPU-intensive program. (Hint: you should try running a CPU intensive program or two and observe these columns...) (1 mark)
Program Listings
/* run-program.c */ #include <unistd.h> #include <stdio.h> int main( int argc, char *argv[], char *envp[] ) { if( argc < 2 ) { printf( "Insufficient arguments.\n" ); return -1; } execvp( argv[1], argv + 1 ); printf( "Is this line printed?\n" ); return 0; }
Answers
Part A
-
- "is this line printed?" is not printed because execvp/execve replaces the program binary with a different one.
- execve( argv[1], argv + 1 , envp);
execve requires an environment argument which may be different from the existing one, where as execvp runs the program using the current environment. Another difference is that execvp searches the PATH environment variable for folders containing binary files, if it can't find the file in the current working directory.
-
- SIGSEGV or SIGBUS is raised for invalid memory references. SIGBUS is low level device specific signal. The following program generates a SIGSEGV in my computer : int *a; *a = 10;
- SIGSTOP for pausing, and SIGCONT for continuing. You can use "kill -SIGWHATEVER pid" from the terminal, or from code. For ex : pause-and-cont.c
- renice
- run-program-reverse.c
- sanitize-env.c
(Part B)
- Compile and run receive-usr1.c and issue "kill -SIGUSR1 pid"
- Use gdb to figure out what signal is thrown for invalid pointer accesses, then catch that. nocrash.c
- run-program-dots.c
- NI is the nice value of a process, which can be changed by the user. It ranges from -20 to 19 and is used as an input to the scheduler. Depending on all other processes and available resources, the scheduler decides the priority of a process and displays its calculated value in the PR column.
Answers : Program Listings
/* pause-and-cont.c */ #include<stdio.h> #include<signal.h> int main(int argc, char * argv[], char *envp[]) { int child = fork(); if(child == 0) { execvp("/usr/bin/yes",argv); } else { kill(child,SIGSTOP); sleep(4); kill(child,SIGCONT); sleep(4); kill(child,SIGKILL); } return 0; }
/* run-program-reverse.c */ #include <errno.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> int main( int argc, char **argv, char *envp[] ) { char **newenv; int i = 0; if( argc < 2 ) { printf( "Insufficient arguments.\n" ); return -1; } newenv = (char**)malloc(argc*sizeof(char**)); for(i = 0; i < argc; i++) { newenv[i] = (char*)malloc(sizeof(argv[argc-i-1])); strcpy(newenv[i],argv[argc-i-1]); } newenv[argc-1] = NULL; if(execvp(newenv[0],newenv + 1) == -1) printf("error! = %s",strerror(errno)); printf( "Is this line printed?\n" ); return 0; }
/* sanitize-env.c */ #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main( int argc, char *argv[], char *envp[] ) { extern char **environ; int i = 0; char *path = getenv("PATH"); char *term = getenv("TERM"); if(clearenv()) exit(-1); setenv("PATH",path,1); setenv("TERM",term,1); //Check now. while(environ[i] != NULL) { printf("environ[%d] = %s\n",i,environ[i++]); } if( argc < 2 ) { printf( "Insufficient arguments.\n" ); return -1; } execvp(argv[1], argv + 1); printf( "Is this line printed?\n" ); return 0; }
/* receive-usr1.c */ #include <sys/wait.h> #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <signal.h> int child; void sig_handler(int signum) { if(signum == SIGUSR1) printf("Ouch!.\n"); fflush(stdout); } int main() { struct sigaction act; sigset_t set; act.sa_handler = sig_handler; act.sa_flags = 0; sigemptyset(&act.sa_mask); sigaction(SIGUSR1, &act, NULL); sigfillset(&set);//Block on all signals sigdelset(&set,SIGUSR1);//except this one sigsuspend(&set);//now wait. return 0; }
/* nocrash.c */ #include <sys/wait.h> #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <signal.h> int child; void sig_handler(int signum) { if(signum == SIGSEGV) printf("Oops, my mistake.\n"); fflush(stdout); exit(1); } int main() { int *a; struct sigaction act; act.sa_handler = sig_handler; act.sa_flags = 0; sigemptyset(&act.sa_mask); sigaction(SIGSEGV, &act, NULL); sleep(1); *a = 10; // Make a mistake. return 0; }
/* run-program-dots.c */ #include <sys/wait.h> #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <signal.h> int child; void sigchild_handler(int signum) { exit(0); } int main() { struct sigaction act; act.sa_handler = sigchild_handler; act.sa_flags = 0; sigemptyset(&act.sa_mask); child = fork(); if(child == 0) { execve("program",NULL,NULL); return 0; } else { sigaction(SIGCHLD, &act, NULL); while(1) { printf("."); fflush(stdout); sleep(1); } return 0; } } where my program.c is #include<stdlib.h> int main() { sleep(8); return 0; }