Processes and memory management
This session is dedicated to the study of processes and their memory in Operating Systems.You don't need to submit source files or reports for this session. For this session, you will need to log into a PC running Linux. If you are at Eurecom, simply log on a PC of rooms 52 or 53. If you were to use another PC running GNU/Linux, I cannot guarantee that the lab works in the same way (outputs could be different, manual pages could be different, etc.).
I. Basics
- Open the slides Processes
- Without reading the slides, are you able to cite the three default streams of a Linux process?
- Reproduce all commands given in the lecture. When a "cmd" is given, imagine a concrete command that could be used instead of "cmd". Find at least, for each "cmd" of the slides, one concrete command that works and one that fails.
- Write a short C program that generates an error, and thats prints to stderr a text corresponding to this error. Compile, start your program with a redirection of error stream to a file. Do you see the expected error message in this file once the program has executed?
II. Memory of processes
This exercise studies the relation between the memory area for global variables, the heap, and the stack. This lab uses C pointers, so, if you don't feel at ease with them, you may first read this page on C pointers, until section 4 included. You may also only read the two first sections, and come back to this page whenever necessary..
- Create a file with the following code (name it: proc1.c):
- Compile this file to an executable named "proc1".
- Copy proc1.c to proc2.c. Now, work with proc2.c. Uncomment the line with the call to printf. Compile, and execute. What happens? Clearly explain the problem.
- Let us now use a debugguer to better investigate this situation. A well-known command-line debugger is gdb. gdb can be used to investigate the content of the memory of a program at execution, and manipulate this memory if necessary. Beware: you may get an output different from the one I provide, depending on the gdb version you use.
- Now, let us continue to work again with proc2.c. First, you need to recompile proc2.c with the debug symbols:
- Now, execute the executable with gdb as follows:
- Put a break point before calling computeRandom(). For this, first list the lines of your program, and select the correct line:
- You can now run the program:
- The program is stopped, let us investigate the value of variable randomValue:
- Let us now execute the program one line of code at a time (i.e., we do a step-by-step execution):
- Let us now add a breakpoint to the line after the call to rand() (our objective is now to debug rand()):
- Let us now exit the function call and go to the next instruction after the function call (i.e., the printf("Hello world\n")), and let us also add a breakpoint to the printf after:
- Now, we would like to fix this issue without modifying both calls to printf nor modifying the definition of randomValue. For this, probably you will need to allocate memory on the heap with malloc. Implement, and test with gdb. Do not forget to test the return value of malloc and to use free to disallocate memory after use. Last but not least, use another file to do this (proc3.c).
- Generate a random string, and print it. The random string should be generated in a function called computeRandomString() and the random string shall be printed by main(). As done in the program generating a random number, use two ways to return the random String from computeRandomString() to main.
- BONUS work. Modify your previous program (let us call it procSender) to simply send the random string to stdout. Then, make a program (let us call it procReceiver) than reads a String from stdin and prints it to stdout. Once the two programs have been successfully compiled, you should be able to execute something like:
- BONUS work. Now, let us assume that procReceiver can automatically indent a C program. If you were to do something like:
int * randomValue; int computeRandom( int maxValue ) { int myRand = rand() % maxValue; randomValue = &myRand; return myRand; } int main( int argc, char*argv[] ) { srand(time(NULL)); /* Seed initialization */ int returned = computeRandom(20); //printf("Hello world!\n"); printf("The random value via the pointer is: %d\n", *randomValue); printf("The returned random value is: %d\n", returned) }
$ gcc -Wall -o proc1 proc1.c
The compiler should issue warnings and errors. Correct them, and execute the program. Remember the result you get.
(Help me!)
There is a semicolon missing, as well as external h files to include. You can find out which files should be included by using "man" on the external function calls.
(Help me!)
The pointer refers to a element on the stack. After returning from computeRandom(), the memory on the stack used for storing the call to computeRandom still contains the random value. Yet, if another function is called before the random value is used, then the stack is erased by the other function call.
$ gcc -Wall -g -o proc2 proc2.c
$ gdb proc2
You can quit gdb by typing quit. Also, to restart from the beginning of the code, you can type :
(gdb) jump _start
Don't do it now, we still need to investigate the memory of your program.
(gdb) list 1 ... 2 ... 3 ... 4 5 int * randomValue; 6 7 int computeRandom( int maxValue ) { 8 int myRand = rand() % maxValue; 9 randomValue = &myRand; 10 return myRand; (gdb) list 11 } 12 13 14 int main( int argc, char*argv[] ) { 15 srand(time(NULL)); /* Seed initialization */ 16 17 int returned = computeRandom(20); 18 19 printf("Hello world\n"); 20 (gdb) list 21 printf("The random value via the pointer is: %d\n", *randomValue); 22 printf("The returned random value is: %d\n", returned); 23 24 }
Note: you can also list the code around a given line, e.g. line 9, by typing:
(gdb) list 9 4 5 int * randomValue; 6 7 int computeRandom( int maxValue ) { 8 int myRand = rand() % maxValue; 9 randomValue = &myRand; 10 return myRand; 11 } 12 13
We now know that the breakpoint should be at line 17 (but do check on your code at what line the breakpoint must be set):
(gdb) break 17 Breakpoint 1 at 0x125d: file proc2.c, line 17.
(gdb) run Starting program: /homes/standard/tmp/proc2 Breakpoint 1, main (argc=1, argv=0x7fffffffe9a8) at proc2.c:17 17 int returned = computeRandom(20);
(gdb) print randomValue $1 = (int *) 0x0
Thus, randomValue points to address 0, also called null. Obviously, the value at address 0x0 is not accessible:
(gdb) print *randomValue Cannot access memory at address 0x0
(gdb) step computeRandom (maxValue=32767) at proc2.c:7 7 int computeRandom( int maxValue ) {After a first step, the program is now executoing the computeRandom function. Let us see the list of called functions (e.g., an excerpt of the stack):
(gdb) bt #0 computeRandom (maxValue=32767) at proc2.c:7 #1 0x0000555555555267 in main (argc=1, argv=0x7fffffffe9a8) at proc2.c:17As expected, first, function main was called, and now the program is currently executing function computeRandom.
(gdb) break 9 Breakpoint 2 at 0x555555555215: file proc2.c, line 9.We can now go to the next breakpoint:
(gdb) continue Continuing. Breakpoint 2, computeRandom (maxValue=20) at proc2.c:9 9 randomValue = &myRand;Let's check the value of variable: randomValue still points to the null address, and myRand should contain a random value between 0 and 20:
(gdb) print randomValue $2 = (int *) 0x0 (gdb) print myRand $3 = 17Let us now go one step ahead, and let us check again to the value of both variables:
(gdb) step ... (gdb) print randomValue $4 = (int *) 0x7fffffffe874 (gdb) print *randomValue $5 = 17 p &myRand $6 = (int *) 0x7fffffffe874 (gdb) p &maxValue $7 = (int *) 0x7fffffffe86cWe can now print a partial view of the stack. "x" means memory, 8 for 8 words ("w"), "d" is for decimal:
(gdb) x/8dw 0x7fffffffe86c 0x7fffffffe86c: 20 -5994 17 474799616 0x7fffffffe87c: -473136351 -5968 32767 1431655015
(gdb) break 19 Breakpoint 3 at 0x55555555526a: file proc2.c, line 19. (gdb) break 21 Breakpoint 4 at 0x555555555276: file proc2.c, line 21.
We can now go to before the first printf, and so, we exit the computeRandom function:
(gdb) continue Continuing. Breakpoint 3, main (argc=1, argv=0x7fffffffe9a8) at proc2.c:19 19 printf("Hello world\n");
Let's see if the previous values used in computeRandom are still in memory:
(gdb) x/8dw 0x7fffffffe86c 0x7fffffffe86c: 20 -5994 17 474799616 0x7fffffffe87c: -473136351 -5968 32767 1431655015Stack data are still unchanged even after the program has left the computeRandom function. If we call the first function, e.g. printf(), then we will see that the stack has been modified by this function call:
(gdb) continue Continuing. Hello world Breakpoint 4, main (argc=1, argv=0x7fffffffe9a8) at proc2.c:21 21 printf("The random value via the pointer is: %d\n", *randomValue);
Now, let us print the content of the stack:
(gdb) x/8dw 0x7fffffffe86c 0x7fffffffe86c: 32767 1431654656 21845 -5728 0x7fffffffe87c: 32767 0 0 1431655030
$ procSender > procReceiver
$ cat toto.c > procReceiverthen procReceiver would print toto.c after indentation, assuming toto.c is not correctly indented. Program this and test.