Browse Source

Update master from bsys-ws17-template/hw4

Manuel Vögele 8 years ago
parent
commit
5f994a00ac

+ 4
- 0
.travis.yml View File

@@ -1,6 +1,10 @@
1 1
 language: rust
2 2
 rust: stable
3 3
 cache: cargo
4
+before_install:
5
+  - sudo add-apt-repository -y ppa:duggan/bats
6
+  - sudo apt-get update
7
+  - sudo apt-get -y install bats
4 8
 before_script: (cargo install rustfmt || true)
5 9
 sudo: false
6 10
 git:

+ 3
- 0
ci/run-travis.sh View File

@@ -14,6 +14,9 @@ MY_PATH="$(dirname "$0")"
14 14
 # check that everything compiles and all tests pass
15 15
 "$MY_PATH/test-rust.sh"
16 16
 
17
+# after compiles run bats tests
18
+"$MY_PATH/test-bats.sh"
19
+
17 20
 # file existence
18 21
 echo "=== Checking for Missing Files ======================================="
19 22
 "$MY_PATH/check-files.py" || true

+ 1
- 1
ci/test-bats.sh View File

@@ -5,7 +5,7 @@
5 5
 # Exit script on the first error
6 6
 set -o errexit -o nounset
7 7
 
8
-current_hw=$(find . -type d -name 'hw*' | sort | tail -n1)
8
+current_hw=$(find . -type f | grep "hw./task./Cargo\.toml" | sort | tail -n1 | cut -d / -f -2)
9 9
 
10 10
 while IFS= read -r -d '' taskdir; do
11 11
 

+ 27
- 0
files/hw4.txt View File

@@ -0,0 +1,27 @@
1
+./hw4/README.md
2
+
3
+./hw4/simu1/ANSWERS.md
4
+./hw4/simu1/QUESTIONS.md
5
+./hw4/simu1/paging-multilevel-translate.py
6
+./hw4/simu1/README-paging-multilevel-translate.md
7
+
8
+./hw4/simu2/ANSWERS.md
9
+./hw4/simu2/QUESTIONS.md
10
+./hw4/simu2/Makefile
11
+./hw4/simu2/mem.c
12
+./hw4/simu2/README-mem-vmstat.md
13
+
14
+./hw4/simu3/ANSWERS.md
15
+./hw4/simu3/QUESTIONS.md
16
+./hw4/simu3/README-paging-policy.md
17
+./hw4/simu3/paging-policy.py
18
+
19
+./hw4/task1/Cargo.lock
20
+./hw4/task1/Cargo.toml
21
+./hw4/task1/src/main.rs
22
+./hw4/task1/src/pstree.rs
23
+./hw4/task1/src/readproc.rs
24
+./hw4/task1/src/unit_test_readproc.rs
25
+./hw4/task1/src/unit_test_pstree.rs
26
+./hw4/task1/tests/output.bats
27
+

+ 28
- 0
hw4/README.md View File

@@ -0,0 +1,28 @@
1
+# hw4
2
+
3
+To fulfill **hw4** you have to solve:
4
+
5
+- task1
6
+- simu1
7
+- simu2
8
+- simu3
9
+
10
+Optional are (bonus +2P):
11
+
12
+- task2 (Homework in OSTEP Chapter 19 in **Rust**)
13
+
14
+## Pull-Reuest
15
+
16
+Please merge any accepted reviews into your branch. If you are ready with the homework, all tests run, please create a pull request named **hw4**.
17
+
18
+## Credits for hw4
19
+
20
+| Task     | max. Credits | Comment |
21
+| -------- | ------------ | ------- |
22
+| task1    | 2            |         |
23
+| simu1    | 0.5          |         |
24
+| simu2    | 1            |         |
25
+| simu3    | 0.5          |         |
26
+| task2    | +2           |         |
27
+| Deadline | +1           |         |
28
+| =        | 7            |         |

+ 12
- 0
hw4/simu1/QUESTIONS.md View File

@@ -0,0 +1,12 @@
1
+# Questions 20-paging-multilevel-translate
2
+
3
+This fun little homework tests if you understand how a multi-level page table works. And yes, there is some debate over the use of the term “fun” in the previous sentence. The program is called, perhaps unsurprisingly: `paging-multilevel-translate.py`; see the README for details.
4
+Questions
5
+
6
+## Questions
7
+
8
+1. With a linear page table, you need a single register to locate the page table, assuming that hardware does the lookup upon a TLB miss. How many registers do you need to locate a two-level page table? A three-level table?
9
+
10
+1. Use the simulator to perform translations given random seeds 0, 1, and 2, and check your answers using the -c flag. How many memory references are needed to perform each lookup?
11
+
12
+1. Given your understanding of how cache memory works, how do you think memory references to the page table will behave in the cache? Will they lead to lots of cache hits (and thus fast accesses?) Or lots of misses (and thus slow accesses)? Explain your answer in detail!

+ 80
- 0
hw4/simu1/README-paging-multilevel-translate.md View File

@@ -0,0 +1,80 @@
1
+# README Paging: Multilevel Translate
2
+
3
+This fun little homework tests if you understand how a multi-level page table
4
+works. And yes, there is some debate over the use of the term fun in the
5
+previous sentence. The program is called: `paging-multilevel-translate.py`
6
+
7
+Some basic assumptions:
8
+
9
+- The page size is an unrealistically-small 32 bytes
10
+- The virtual address space for the process in question (assume there is only
11
+  one) is 1024 pages, or 32 KB
12
+- physical memory consists of 128 pages
13
+
14
+Thus, a virtual address needs 15 bits (5 for the offset, 10 for the VPN). A
15
+physical address requires 12 bits (5 offset, 7 for the PFN).
16
+
17
+The system assumes a multi-level page table. Thus, the upper five bits of a
18
+virtual address are used to index into a page directory; the page directory
19
+entry (PDE), if valid, points to a page of the page table. Each page table page
20
+holds 32 page-table entries (PTEs). Each PTE, if valid, holds the desired
21
+translation (physical frame number, or PFN) of the virtual page in question.
22
+
23
+The format of a PTE is thus:
24
+
25
+```text
26
+  VALID | PFN6 ... PFN0
27
+```
28
+
29
+and is thus 8 bits or 1 byte.
30
+
31
+The format of a PDE is essentially identical:
32
+
33
+```text
34
+  VALID | PT6 ... PT0
35
+```
36
+
37
+You are given two pieces of information to begin with.
38
+
39
+First, you are given the value of the page directory base register (PDBR), which
40
+tells you which page the page directory is located upon.
41
+
42
+Second, you are given a complete dump of each page of memory. A page dump looks
43
+like this:
44
+
45
+```text
46
+    page 0: 08 00 01 15 11 1d 1d 1c 01 17 15 14 16 1b 13 0b ...
47
+    page 1: 19 05 1e 13 02 16 1e 0c 15 09 06 16 00 19 10 03 ...
48
+    page 2: 1d 07 11 1b 12 05 07 1e 09 1a 18 17 16 18 1a 01 ...
49
+    ...
50
+```
51
+
52
+which shows the 32 bytes found on pages 0, 1, 2, and so forth. The first byte
53
+(0th byte) on page 0 has the value 0x08, the second is 0x00, the third 0x01, and
54
+so forth.
55
+
56
+You are then given a list of virtual addresses to translate.
57
+
58
+Use the PDBR to find the relevant page table entries for this virtual page. Then
59
+find if it is valid. If so, use the translation to form a final physical
60
+address. Using this address, you can find the VALUE that the memory reference is
61
+looking for.
62
+
63
+Of course, the virtual address may not be valid and thus generate a fault.
64
+
65
+Some useful options:
66
+
67
+- Change the seed to get different problems, as always:
68
+  ```text
69
+    -s SEED, --seed=SEED       the random seed
70
+  ```
71
+
72
+- Change the number of virtual addresses generated to do more translations for a given memory dump.
73
+  ```text
74
+    -n NUM, --addresses=NUM    number of virtual addresses to generate
75
+  ```
76
+
77
+- Use -c (or --solve) to show the solutions.
78
+  ```text
79
+    -c, --solve                compute answers for me
80
+  ```

+ 265
- 0
hw4/simu1/paging-multilevel-translate.py View File

@@ -0,0 +1,265 @@
1
+#! /usr/bin/env python
2
+
3
+import sys
4
+from optparse import OptionParser
5
+import random
6
+import math
7
+
8
+def convert(size):
9
+    length = len(size)
10
+    lastchar = size[length-1]
11
+    if (lastchar == 'k') or (lastchar == 'K'):
12
+        m = 1024
13
+        nsize = int(size[0:length-1]) * m
14
+    elif (lastchar == 'm') or (lastchar == 'M'):
15
+        m = 1024*1024
16
+        nsize = int(size[0:length-1]) * m
17
+    elif (lastchar == 'g') or (lastchar == 'G'):
18
+        m = 1024*1024*1024
19
+        nsize = int(size[0:length-1]) * m
20
+    else:
21
+        nsize = int(size)
22
+    return nsize
23
+
24
+def roundup(size):
25
+    value = 1.0
26
+    while value < size:
27
+        value = value * 2.0
28
+    return value
29
+
30
+    
31
+class OS:
32
+    def __init__(self):
33
+        # 4k phys memory (128 pages)
34
+        self.pageSize  = 32
35
+        self.physPages = 128
36
+        self.physMem   = self.pageSize * self.physPages
37
+        self.vaPages   = 1024
38
+        self.vaSize    = self.pageSize * self.vaPages
39
+        self.pteSize   = 1
40
+        self.pageBits  = 5 # log of page size
41
+
42
+        # os tracks
43
+        self.usedPages      = []
44
+        self.usedPagesCount = 0
45
+        self.maxPageCount   = self.physMem / self.pageSize
46
+
47
+        # no pages used (yet)
48
+        for i in range(0, self.maxPageCount):
49
+            self.usedPages.append(0)
50
+
51
+        # set contents of memory to 0, too
52
+        self.memory = []
53
+        for i in range(0, self.physMem):
54
+            self.memory.append(0)
55
+
56
+        # associative array of pdbr's (indexed by PID)
57
+        self.pdbr = {}
58
+
59
+        # mask is 11111 00000 00000 --> 0111 1100 0000 0000 
60
+        self.PDE_MASK    = 0x7c00
61
+        self.PDE_SHIFT   = 10
62
+
63
+        # 00000 11111 00000 -> 000 0011 1110 0000
64
+        self.PTE_MASK    = 0x03e0
65
+        self.PTE_SHIFT   = 5
66
+
67
+        self.VPN_MASK    = self.PDE_MASK | self.PTE_MASK
68
+        self.VPN_SHIFT   = self.PTE_SHIFT
69
+
70
+        # grabs the last five bits of a virtual address
71
+        self.OFFSET_MASK = 0x1f
72
+
73
+    def findFree(self):
74
+        assert(self.usedPagesCount < self.maxPageCount)
75
+        look = int(random.random() * self.maxPageCount)
76
+        while self.usedPages[look] == 1:
77
+            look = int(random.random() * self.maxPageCount)
78
+        self.usedPagesCount = self.usedPagesCount + 1
79
+        self.usedPages[look] = 1
80
+        return look
81
+
82
+    def initPageDir(self, whichPage):
83
+        whichByte = whichPage << self.pageBits
84
+        for i in range(whichByte, whichByte + self.pageSize):
85
+            self.memory[i] = 0x7f
86
+
87
+    def initPageTablePage(self, whichPage):
88
+        self.initPageDir(whichPage)
89
+
90
+    def getPageTableEntry(self, virtualAddr, ptePage, printStuff):
91
+        pteBits = (virtualAddr & self.PTE_MASK) >> self.PTE_SHIFT
92
+        pteAddr = (ptePage << self.pageBits) | pteBits
93
+        pte     = self.memory[pteAddr]
94
+        valid   = (pte & 0x80) >> 7
95
+        pfn     = (pte & 0x7f)
96
+        if printStuff == True:
97
+            print '    --> pte index:0x%x [decimal %d] pte contents:0x%x (valid %d, pfn 0x%02x [decimal %d])' % \
98
+                  (pteBits, pteBits, pte, valid, pfn, pfn)
99
+        return (valid, pfn, pteAddr)
100
+
101
+    def getPageDirEntry(self, pid, virtualAddr, printStuff):
102
+        pageDir = self.pdbr[pid]
103
+        pdeBits = (virtualAddr & self.PDE_MASK) >> self.PDE_SHIFT
104
+        pdeAddr = (pageDir << self.pageBits) | pdeBits
105
+        pde     = self.memory[pdeAddr]
106
+        valid   = (pde & 0x80) >> 7
107
+        ptPtr   = (pde & 0x7f)
108
+        if printStuff == True:
109
+            print '  --> pde index:0x%x [decimal %d] pde contents:0x%x (valid %d, pfn 0x%02x [decimal %d])' % \
110
+                  (pdeBits, pdeBits, pde, valid, ptPtr, ptPtr)
111
+        return (valid, ptPtr, pdeAddr)
112
+
113
+    def setPageTableEntry(self, pteAddr, physicalPage):
114
+        self.memory[pteAddr] = 0x80 | physicalPage
115
+
116
+    def setPageDirEntry(self, pdeAddr, physicalPage):
117
+        self.memory[pdeAddr] = 0x80 | physicalPage
118
+        
119
+    def allocVirtualPage(self, pid, virtualPage, physicalPage):
120
+        # make it into a virtual address, as everything uses this (and not VPN)
121
+        virtualAddr = virtualPage << self.pageBits
122
+        (valid, ptPtr, pdeAddr) = self.getPageDirEntry(pid, virtualAddr, False)
123
+        if valid == 0:
124
+            # must allocate a page of the page table now, and have the PD point to it
125
+            assert(ptPtr == 127)
126
+            ptePage = self.findFree()
127
+            self.setPageDirEntry(pdeAddr, ptePage)
128
+            self.initPageTablePage(ptePage)
129
+        else:
130
+            # otherwise, just extract page number of page table page
131
+            ptePage = ptPtr
132
+        # now, look up page table entry too, and mark it valid and fill in translation
133
+        (valid, pfn, pteAddr) = self.getPageTableEntry(virtualAddr, ptePage, False)
134
+        assert(valid == 0)
135
+        assert(pfn == 127)
136
+        self.setPageTableEntry(pteAddr, physicalPage)
137
+
138
+    # -2 -> PTE fault, -1 means PDE fault
139
+    def translate(self, pid, virtualAddr):
140
+        (valid, ptPtr, pdeAddr) = self.getPageDirEntry(pid, virtualAddr, True)
141
+        if valid == 1:
142
+            ptePage = ptPtr
143
+            (valid, pfn, pteAddr) = self.getPageTableEntry(virtualAddr, ptePage, True)
144
+            if valid == 1:
145
+                offset = (virtualAddr & self.OFFSET_MASK)
146
+                paddr  = (pfn << self.pageBits) | offset
147
+		# print '     --> pfn: %02x  offset: %x' % (pfn, offset)
148
+                return paddr
149
+            else:
150
+                return -2
151
+        return -1
152
+
153
+    def fillPage(self, whichPage):
154
+        for j in range(0, self.pageSize):
155
+            self.memory[(whichPage * self.pageSize) + j] = int(random.random() * 31)
156
+
157
+    def procAlloc(self, pid, numPages):
158
+        # need a PDBR: find one somewhere in memory
159
+        pageDir = self.findFree()
160
+        # print '**ALLOCATE** page dir', pageDir
161
+        self.pdbr[pid] = pageDir
162
+        self.initPageDir(pageDir)
163
+
164
+        used = {}
165
+        for vp in range(0, self.vaPages):
166
+            used[vp] = 0
167
+        allocatedVPs = []
168
+        
169
+        for vp in range(0, numPages):
170
+            vp = int(random.random() * self.vaPages)
171
+            while used[vp] == 1:
172
+                vp = int(random.random() * self.vaPages)
173
+            assert(used[vp] == 0)
174
+            used[vp] = 1
175
+            allocatedVPs.append(vp)
176
+            pp = self.findFree()
177
+            # print '**ALLOCATE** page', pp
178
+            # print '  trying to map vp:%08x to pp:%08x' % (vp, pp)
179
+            self.allocVirtualPage(pid, vp, pp)
180
+            self.fillPage(pp)
181
+        return allocatedVPs
182
+
183
+    def dumpPage(self, whichPage):
184
+        i = whichPage
185
+        for j in range(0, self.pageSize):
186
+            print self.memory[(i * self.pageSize) + j],
187
+        print ''
188
+
189
+    def memoryDump(self):
190
+        for i in range(0, self.physMem / self.pageSize):
191
+            print 'page %3d:' %  i, 
192
+            for j in range(0, self.pageSize):
193
+                print '%02x' % self.memory[(i * self.pageSize) + j],
194
+            print ''
195
+
196
+    def getPDBR(self, pid):
197
+        return self.pdbr[pid]
198
+
199
+    def getValue(self, addr):
200
+        return self.memory[addr]
201
+
202
+# allocate some processes in memory
203
+# allocate some multi-level page tables in memory
204
+# make a bit of a mystery:
205
+# can examine PDBR (PFN of current proc's page directory)
206
+# can examine contents of any page
207
+# fill pages with values too
208
+# ask: when given
209
+#   LOAD VA, R1
210
+# what will final value will be loaded into R1?
211
+
212
+#
213
+# main program
214
+#
215
+parser = OptionParser()
216
+parser.add_option('-s', '--seed', default=0, help='the random seed', action='store', type='int', dest='seed')
217
+parser.add_option('-a', '--allocated', default=64, help='number of virtual pages allocated',
218
+                  action='store', type='int', dest='allocated')
219
+parser.add_option('-n', '--addresses', default=10, help='number of virtual addresses to generate',
220
+                  action='store', type='int', dest='num')
221
+parser.add_option('-c', '--solve', help='compute answers for me', action='store_true', default=False, dest='solve')
222
+
223
+
224
+(options, args) = parser.parse_args()
225
+
226
+print 'ARG seed', options.seed
227
+print 'ARG allocated',  options.allocated
228
+print 'ARG num',  options.num
229
+print ""
230
+
231
+random.seed(options.seed)
232
+
233
+# do the work now
234
+os = OS()
235
+used = os.procAlloc(1, options.allocated)
236
+
237
+os.memoryDump()
238
+
239
+print '\nPDBR:', os.getPDBR(1), ' (decimal) [This means the page directory is held in this page]\n'
240
+
241
+for i in range(0, options.num):
242
+    if (random.random() * 100) > 50.0 or i >= len(used):
243
+        vaddr = int(random.random() * 1024 * 32)
244
+    else:
245
+        vaddr = (used[i] << 5) | int(random.random() * 32)
246
+    if options.solve == True:
247
+        print 'Virtual Address %04x:' % vaddr
248
+        r = os.translate(1, vaddr)
249
+        if r > -1:
250
+            print '      --> Translates to Physical Address 0x%03x --> Value: %02x' % (r, os.getValue(r))
251
+        elif r == -1:
252
+            print '      --> Fault (page directory entry not valid)'
253
+        else:
254
+            print '      --> Fault (page table entry not valid)'
255
+    else:
256
+        print 'Virtual Address %04x: Translates To What Physical Address (And Fetches what Value)? Or Fault?' % vaddr
257
+
258
+print ''
259
+
260
+exit(0)
261
+
262
+
263
+
264
+
265
+

+ 6
- 0
hw4/simu2/Makefile View File

@@ -0,0 +1,6 @@
1
+
2
+all: mem
3
+
4
+mem: mem.c
5
+	gcc -o mem mem.c -Wall -O
6
+

+ 21
- 0
hw4/simu2/QUESTIONS.md View File

@@ -0,0 +1,21 @@
1
+# Questions 21-Physical-Memory-Mechanisms
2
+
3
+This homework introduces you to a new tool, **vmstat**, and how it can be used to get a sense of what your computer is doing with regards to memory, CPU, and I/O usage (with a focus on memory and swapping).
4
+
5
+Read the associated README and examine the code in mem.c before proceeding to the exercises and questions below.
6
+
7
+## Questions
8
+
9
+1. First, open two separate terminal connections to the same machine, so that you can easily run something in one window and the other. Now, in one window, run `vmstat 1`, which shows statistics about machine usage every second. Read the man page, the associated README, and any other information you need so that you can understand its output. Leave this window running **vmstat** for the rest of the exercises below.
10
+
11
+   Now, we will run the program `mem.c` but with very little memory usage. This can be accomplished by typing `./mem 1` (which uses only 1 MB of memory). How do the CPU usage statistics change when running mem? Do the numbers in the user time column make sense? How does this change when running more than one instance of mem at once?
12
+
13
+2. Let’s now start looking at some of the memory statistics while running mem. We’ll focus on two columns: swpd (the amount of virtual memory used) and free (the amount of idle memory). Run `./mem 1024` (which allocates 1024 MB) and watch how these values change. Then kill the running program (by typing control-c) and watch again how the values change. What do you notice about the values? In particular, how does the free column change when the program exits? Does the amount of free memory increase by the expected amount when mem exits?
14
+
15
+3. We’ll next look at the swap columns (si and so), which indicate how much swapping is taking place to and from the disk. Of course, to activate these, you’ll need to run mem with large amounts of memory. First, examine how much free memory is on your Linux system (for example, by typing cat `/proc/meminfo`; type **man proc** for details on the */proc* file system and the types of information you can find there). One of the first entries in `/proc/meminfo` is the total amount of memory in your system. Let’s assume it’s something like 8 GB of memory; if so, start by running mem 4000 (about 4 GB) and watching the swap in/out columns. Do they ever give non-zero values? Then, try with 5000, 6000, etc. What happens to these values as the program enters the second loop (and beyond), as compared to the first loop? How much data (total) are swapped in and out during the second, third, and subsequent loops? (do the numbers make sense?)
16
+
17
+4. Do the same experiments as above, but now watch the other statistics (such as CPU utilization, and block I/O statistics). How do they change when mem is running?
18
+
19
+5. Now let’s examine performance. Pick an input for mem that comfortably fits in memory (say 4000 if the amount of memory on the system is 8 GB). How long does loop 0 take (and subsequent loops 1, 2, etc.)? Now pick a size comfortably beyond the size of memory (say 12000 again assuming 8 GB of memory). How long do the loops take here? How do the bandwidth numbers compare? How different is performance when constantly swapping versus fitting everything comfortably in memory? Can you make a graph, with the size of memory used by mem on the x-axis, and the bandwidth of accessing said memory on the y-axis? Finally, how does the performance of the first loop compare to that of subsequent loops, for both the case where everything fits in memory and where it doesn’t?
20
+
21
+6. Swap space isn’t infinite. You can use the tool **/sbin/swapon** with the -s flag to see how much swap space is available. What happens if you try to run mem with increasingly large values, beyond what seems to be available in swap? At what point does the memory allocation fail?

+ 92
- 0
hw4/simu2/README-mem-vmstat.md View File

@@ -0,0 +1,92 @@
1
+# README
2
+
3
+In this homework, you'll be investigating swap performance with a simple
4
+program found in `mem.c. The program is really simple: it just allocates an
5
+array of integers of a certain size, and then proceeds to loop through it
6
+(repeatedly), incrementing each value in the array.
7
+
8
+Type "make" to build it (and look at the file Makefile for details about how
9
+the build works).
10
+
11
+Then, type "./mem" followed by a number to run it. The number is the size (in
12
+MB) of the array. Thus, to run with a small array (size 1 MB):
13
+
14
+```text
15
+prompt> ./mem 1
16
+```
17
+
18
+and to run with a larger array (size 1 GB):
19
+
20
+```text
21
+prompt> ./mem 1024
22
+```
23
+
24
+The program prints out the time it takes to go through each loop as well as
25
+the bandwidth (in MB/s). Bandwidth is particularly interesting to know as it
26
+gives you a sense of how fast the system you're using can move through data;
27
+on modern systems, this is likely in the GB/s range.
28
+
29
+Here is what the output looks like for a typical run:
30
+
31
+```text
32
+prompt> ./mem 1000
33
+allocating 1048576000 bytes (1000.00 MB)
34
+  number of integers in array: 262144000
35
+loop 0 in 448.11 ms (bandwidth: 2231.61 MB/s)
36
+loop 1 in 345.38 ms (bandwidth: 2895.38 MB/s)
37
+loop 2 in 345.18 ms (bandwidth: 2897.07 MB/s)
38
+loop 3 in 345.23 ms (bandwidth: 2896.61 MB/s)
39
+^C
40
+prompt>
41
+```
42
+
43
+The program first tells you how much memory it allocated (in bytes, MB, and in
44
+the number of integers), and then starts looping through the array. The first
45
+loop (in the example above) took 448 milliseconds; because the program
46
+accessed the 1000 MB in just under half a second, the computed bandwidth is
47
+(not surprisingly) just over 2000 MB/s.
48
+
49
+The program continues by doing the same thing over and over, for loops 1, 2,
50
+etc.
51
+
52
+Important: to stop the program, you must kill it. This task is accomplished on
53
+Linux (and all Unix-based systems) by typing control-C (^C) as shown above.
54
+
55
+Note that when you run with small array sizes, each loop's performance numbers
56
+won't be printed. For example:
57
+
58
+```text
59
+prompt>  ./mem 1
60
+allocating 1048576 bytes (1.00 MB)
61
+  number of integers in array: 262144
62
+loop 0 in 0.71 ms (bandwidth: 1414.61 MB/s)
63
+loop 607 in 0.33 ms (bandwidth: 3039.35 MB/s)
64
+loop 1215 in 0.33 ms (bandwidth: 3030.57 MB/s)
65
+loop 1823 in 0.33 ms (bandwidth: 3039.35 MB/s)
66
+^C
67
+prompt>
68
+```
69
+
70
+In this case, the program only prints out a sample of outputs, so as not to
71
+flood the screen with too much output.
72
+
73
+The code itself is simple to understand. The first important part is a memory
74
+allocation:
75
+
76
+```c
77
+    // the big memory allocation happens here
78
+    int *x = malloc(size_in_bytes);
79
+```
80
+
81
+Then, the main loop begins:
82
+
83
+```c
84
+    while (1) {
85
+	x[i++] += 1; // main work of loop done here.
86
+```
87
+
88
+The rest is just timing and printing out information. See mem.c for details.
89
+
90
+Much of the homework revolves around using the tool vmstat to monitor what is
91
+happening with the system. Read the vmstat man page (type "man vmstat") for
92
+details on how it works, and what each column of output means.

+ 67
- 0
hw4/simu2/mem.c View File

@@ -0,0 +1,67 @@
1
+#include <stdio.h>
2
+#include <stdlib.h>
3
+#include <string.h>
4
+#include <assert.h>
5
+#include <sys/time.h>
6
+
7
+// Simple routine to return absolute time (in seconds).
8
+double Time_GetSeconds() {
9
+    struct timeval t;
10
+    int rc = gettimeofday(&t, NULL);
11
+    assert(rc == 0);
12
+    return (double) ((double)t.tv_sec + (double)t.tv_usec / 1e6);
13
+}
14
+
15
+// Program that allocates an array of ints of certain size,
16
+// and then proceeeds to update each int in a loop, forever.
17
+int main(int argc, char *argv[]) {
18
+    if (argc != 2) {
19
+	fprintf(stderr, "usage: spin <memory (MB)>\n");
20
+	exit(1);
21
+    }
22
+    long long int size = (long long int) atoi(argv[1]);
23
+    long long int size_in_bytes = size * 1024 * 1024;
24
+
25
+    printf("allocating %lld bytes (%.2f MB)\n", 
26
+	   size_in_bytes, size_in_bytes / (1024 * 1024.0));
27
+
28
+    // the big memory allocation happens here
29
+    int *x = malloc(size_in_bytes);
30
+    if (x == NULL) {
31
+	fprintf(stderr, "memory allocation failed\n");
32
+	exit(1);
33
+    }
34
+
35
+    long long int num_ints = size_in_bytes / sizeof(int);
36
+    printf("  number of integers in array: %lld\n", num_ints);
37
+
38
+    // now the main loop: each time through, touch each integer
39
+    // (and increment its value by 1).
40
+    int i = 0;
41
+    double time_since_last_print = 2.0; 
42
+    double t = Time_GetSeconds();
43
+    int loop_count = 0;
44
+    while (1) {
45
+	x[i++] += 1; // main work of loop done here.
46
+
47
+	// if we've gone through the whole loop, reset a bunch of stuff
48
+	// and then (perhaps) print out some statistics. 
49
+	if (i == num_ints) {
50
+	    double delta_time = Time_GetSeconds() - t;
51
+	    time_since_last_print += delta_time;
52
+	    if (time_since_last_print >= 0.2) { // only print every .2 seconds
53
+		printf("loop %d in %.2f ms (bandwidth: %.2f MB/s)\n", 
54
+		       loop_count, 1000 * delta_time, 
55
+		       size_in_bytes / (1024.0*1024.0*delta_time));
56
+		time_since_last_print = 0;
57
+	    }
58
+
59
+	    i = 0;
60
+	    t = Time_GetSeconds();
61
+	    loop_count++;
62
+	}
63
+    }
64
+
65
+    return 0;
66
+}
67
+

+ 19
- 0
hw4/simu3/QUESTIONS.md View File

@@ -0,0 +1,19 @@
1
+# Questions 20-paging-multilevel-translate
2
+
3
+This simulator, paging-policy.py, allows you to play around with different page-replacement policies. See the README for details.
4
+
5
+## Warmup
6
+
7
+Generate random addresses with the following arguments: -s 0 -n 10, -s 1 -n 10, and -s 2 -n 10. Change the policy from FIFO,to LRU, to OPT. Compute whether each access in said address traces are hits or misses.
8
+
9
+## Questions
10
+
11
+1. For a cache of size 5, generate worst-case address reference streams for each of the following policies: FIFO, LRU, and MRU (worst-case reference streams cause the most misses possible. For the worst case reference streams, how much bigger of a cache is needed to improve performance dramatically and approach OPT?
12
+
13
+1. Generate a random trace (use python or c or rust (single file, no project).
14
+    1. How would you expect the different policies to perform on such a trace?
15
+1. Now generate a trace with some locality.
16
+    1. How can you generate such a trace?
17
+    1. How does LRU perform on it?
18
+    1. How much better than RAND is LRU?
19
+    1. How does CLOCK do? How about CLOCK with different numbers of clock bits?

+ 128
- 0
hw4/simu3/README-paging-policy.md View File

@@ -0,0 +1,128 @@
1
+# README Paging: Policy
2
+
3
+This simulator, paging-policy.py, allows you to play around with different
4
+page-replacement policies. For example, let's examine how LRU performs with a
5
+series of page references with a cache of size 3:
6
+
7
+```text
8
+  0 1 2 0 1 3 0 3 1 2 1
9
+```
10
+
11
+To do so, run the simulator as follows:
12
+
13
+```text
14
+prompt> ./paging-policy.py --addresses=0,1,2,0,1,3,0,3,1,2,1
15
+                           --policy=LRU --cachesize=3 -c
16
+```text
17
+
18
+And what you would see is:
19
+
20
+```text
21
+ARG addresses 0,1,2,0,1,3,0,3,1,2,1
22
+ARG numaddrs 10
23
+ARG policy LRU
24
+ARG cachesize 3
25
+ARG maxpage 10
26
+ARG seed 0
27
+
28
+Solving...
29
+
30
+Access: 0 MISS LRU->      [br 0]<-MRU Replace:- [br Hits:0 Misses:1]
31
+Access: 1 MISS LRU->   [br 0, 1]<-MRU Replace:- [br Hits:0 Misses:2]
32
+Access: 2 MISS LRU->[br 0, 1, 2]<-MRU Replace:- [br Hits:0 Misses:3]
33
+Access: 0 HIT  LRU->[br 1, 2, 0]<-MRU Replace:- [br Hits:1 Misses:3]
34
+Access: 1 HIT  LRU->[br 2, 0, 1]<-MRU Replace:- [br Hits:2 Misses:3]
35
+Access: 3 MISS LRU->[br 0, 1, 3]<-MRU Replace:2 [br Hits:2 Misses:4]
36
+Access: 0 HIT  LRU->[br 1, 3, 0]<-MRU Replace:2 [br Hits:3 Misses:4]
37
+Access: 3 HIT  LRU->[br 1, 0, 3]<-MRU Replace:2 [br Hits:4 Misses:4]
38
+Access: 1 HIT  LRU->[br 0, 3, 1]<-MRU Replace:2 [br Hits:5 Misses:4]
39
+Access: 2 MISS LRU->[br 3, 1, 2]<-MRU Replace:0 [br Hits:5 Misses:5]
40
+Access: 1 HIT  LRU->[br 3, 2, 1]<-MRU Replace:0 [br Hits:6 Misses:5]
41
+```
42
+
43
+The complete set of possible arguments for paging-policy is listed on the
44
+following page, and includes a number of options for varying the policy, how
45
+addresses are specified/generated, and other important parameters such as the
46
+size of the cache.
47
+
48
+```text
49
+prompt> ./paging-policy.py --help
50
+Usage: paging-policy.py [options]
51
+
52
+Options:
53
+-h, --help      show this help message and exit
54
+-a ADDRESSES, --addresses=ADDRESSES
55
+                a set of comma-separated pages to access;
56
+                -1 means randomly generate
57
+-f ADDRESSFILE, --addressfile=ADDRESSFILE
58
+                a file with a bunch of addresses in it
59
+-n NUMADDRS, --numaddrs=NUMADDRS
60
+                if -a (--addresses) is -1, this is the
61
+                number of addrs to generate
62
+-p POLICY, --policy=POLICY
63
+                replacement policy: FIFO, LRU, LFU, OPT,
64
+                                    UNOPT, RAND, CLOCK
65
+-b CLOCKBITS, --clockbits=CLOCKBITS
66
+                for CLOCK policy, how many clock bits to use
67
+-C CACHESIZE, --cachesize=CACHESIZE
68
+                size of the page cache, in pages
69
+-m MAXPAGE, --maxpage=MAXPAGE
70
+                if randomly generating page accesses,
71
+                this is the max page number
72
+-s SEED, --seed=SEED  random number seed
73
+-N, --notrace   do not print out a detailed trace
74
+-c, --compute   compute answers for me
75
+```
76
+
77
+As usual, "-c" is used to solve a particular problem, whereas without it, the
78
+accesses are just listed (and the program does not tell you whether or not a
79
+particular access is a hit or miss).
80
+
81
+To generate a random problem, instead of using "-a/--addresses" to pass in
82
+some page references, you can instead pass in "-n/--numaddrs" as the number of
83
+addresses the program should randomly generate, with "-s/--seed" used to
84
+specify a different random seed. For example:
85
+
86
+```text
87
+prompt> ./paging-policy.py -s 10 -n 3
88
+.. .
89
+
90
+Assuming a replacement policy of FIFO, and a cache of size 3 pages,
91
+figure out whether each of the following page references hit or miss
92
+in the page cache.
93
+
94
+Access: 5  Hit/Miss?  State of Memory?
95
+Access: 4  Hit/Miss?  State of Memory?
96
+Access: 5  Hit/Miss?  State of Memory?
97
+```
98
+
99
+As you can see, in this example, we specify "-n 3" which means the program
100
+should generate 3 random page references, which it does: 5, 7, and 5. The
101
+random seed is also specified (10), which is what gets us those particular
102
+numbers. After working this out yourself, have the program solve the problem
103
+for you by passing in the same arguments but with "-c" (showing just the
104
+relevant part here):
105
+
106
+```text
107
+prompt> ./paging-policy.py -s 10 -n 3 -c
108
+...
109
+Solving...
110
+
111
+Access: 5 MISS FirstIn->   [br 5] <-Lastin Replace:- [br Hits:0 Misses:1]
112
+Access: 4 MISS FirstIn->[br 5, 4] <-Lastin Replace:- [br Hits:0 Misses:2]
113
+Access: 5 HIT  FirstIn->[br 5, 4] <-Lastin Replace:- [br Hits:1 Misses:2]
114
+```
115
+
116
+The default policy is FIFO, though others are available, including LRU, MRU,
117
+OPT (the optimal replacement policy, which peeks into the future to see what
118
+is best to replace), UNOPT (which is the pessimal replacement), RAND (which
119
+does random replacement), and CLOCK (which does the clock algorithm). The
120
+CLOCK algorithm also takes another argument (-b), which states how many bits
121
+should be kept per page; the more clock bits there are, the better the
122
+algorithm should be at determining which pages to keep in memory.
123
+
124
+Other options include: "-C/--cachesize" which changes the size of the page
125
+cache; "-m/--maxpage" which is the largest page number that will be used if
126
+the simulator is generating references for you; and "-f/--addressfile" which
127
+lets you specify a file with addresses in them, in case you wish to get traces
128
+from a real application or otherwise use a long trace as input.

+ 275
- 0
hw4/simu3/paging-policy.py View File

@@ -0,0 +1,275 @@
1
+#! /usr/bin/env python
2
+
3
+import sys
4
+from optparse import OptionParser
5
+import random
6
+import math
7
+
8
+def convert(size):
9
+    length = len(size)
10
+    lastchar = size[length-1]
11
+    if (lastchar == 'k') or (lastchar == 'K'):
12
+        m = 1024
13
+        nsize = int(size[0:length-1]) * m
14
+    elif (lastchar == 'm') or (lastchar == 'M'):
15
+        m = 1024*1024
16
+        nsize = int(size[0:length-1]) * m
17
+    elif (lastchar == 'g') or (lastchar == 'G'):
18
+        m = 1024*1024*1024
19
+        nsize = int(size[0:length-1]) * m
20
+    else:
21
+        nsize = int(size)
22
+    return nsize
23
+
24
+def hfunc(index):
25
+    if index == -1:
26
+        return 'MISS'
27
+    else:
28
+        return 'HIT '
29
+
30
+def vfunc(victim):
31
+    if victim == -1:
32
+        return '-'
33
+    else:
34
+        return str(victim)
35
+
36
+#
37
+# main program
38
+#
39
+parser = OptionParser()
40
+parser.add_option('-a', '--addresses', default='-1',   help='a set of comma-separated pages to access; -1 means randomly generate',  action='store', type='string', dest='addresses')
41
+parser.add_option('-f', '--addressfile', default='',   help='a file with a bunch of addresses in it',                                action='store', type='string', dest='addressfile')
42
+parser.add_option('-n', '--numaddrs', default='10',    help='if -a (--addresses) is -1, this is the number of addrs to generate',    action='store', type='string', dest='numaddrs')
43
+parser.add_option('-p', '--policy', default='FIFO',    help='replacement policy: FIFO, LRU, OPT, UNOPT, RAND, CLOCK',                action='store', type='string', dest='policy')
44
+parser.add_option('-b', '--clockbits', default=2,      help='for CLOCK policy, how many clock bits to use',                          action='store', type='int', dest='clockbits')
45
+parser.add_option('-C', '--cachesize', default='3',    help='size of the page cache, in pages',                                      action='store', type='string', dest='cachesize')
46
+parser.add_option('-m', '--maxpage', default='10',     help='if randomly generating page accesses, this is the max page number',     action='store', type='string', dest='maxpage')
47
+parser.add_option('-s', '--seed', default='0',         help='random number seed',                                                    action='store', type='string', dest='seed')
48
+parser.add_option('-N', '--notrace', default=False,    help='do not print out a detailed trace',                                     action='store_true', dest='notrace')
49
+parser.add_option('-c', '--compute', default=False,    help='compute answers for me',                                                action='store_true', dest='solve')
50
+
51
+(options, args) = parser.parse_args()
52
+
53
+print 'ARG addresses', options.addresses
54
+print 'ARG addressfile', options.addressfile
55
+print 'ARG numaddrs', options.numaddrs
56
+print 'ARG policy', options.policy
57
+print 'ARG clockbits', options.clockbits
58
+print 'ARG cachesize', options.cachesize
59
+print 'ARG maxpage', options.maxpage
60
+print 'ARG seed', options.seed
61
+print 'ARG notrace', options.notrace
62
+print ''
63
+
64
+addresses   = str(options.addresses)
65
+addressFile = str(options.addressfile)
66
+numaddrs    = int(options.numaddrs)
67
+cachesize   = int(options.cachesize)
68
+seed        = int(options.seed)
69
+maxpage     = int(options.maxpage)
70
+policy      = str(options.policy)
71
+notrace     = options.notrace
72
+clockbits   = int(options.clockbits)
73
+
74
+random.seed(seed)
75
+
76
+addrList = []
77
+if addressFile != '':
78
+    fd = open(addressFile)
79
+    for line in fd:
80
+        addrList.append(int(line))
81
+    fd.close()
82
+else:
83
+    if addresses == '-1':
84
+        # need to generate addresses
85
+        for i in range(0,numaddrs):
86
+            n = int(maxpage * random.random())
87
+            addrList.append(n)
88
+    else:
89
+        addrList = addresses.split(',')
90
+
91
+if options.solve == False:
92
+    print 'Assuming a replacement policy of %s, and a cache of size %d pages,' % (policy, cachesize)
93
+    print 'figure out whether each of the following page references hit or miss'
94
+    print 'in the page cache.\n'
95
+
96
+    for n in addrList:
97
+        print 'Access: %d  Hit/Miss?  State of Memory?' % int(n)
98
+    print ''
99
+
100
+else:
101
+    if notrace == False:
102
+        print 'Solving...\n'
103
+
104
+    # init memory structure
105
+    count = 0
106
+    memory = []
107
+    hits = 0
108
+    miss = 0
109
+
110
+    if policy == 'FIFO':
111
+        leftStr = 'FirstIn'
112
+        riteStr = 'Lastin '
113
+    elif policy == 'LRU':
114
+        leftStr = 'LRU'
115
+        riteStr = 'MRU'
116
+    elif policy == 'MRU':
117
+        leftStr = 'LRU'
118
+        riteStr = 'MRU'
119
+    elif policy == 'OPT' or policy == 'RAND' or policy == 'UNOPT' or policy == 'CLOCK':
120
+        leftStr = 'Left '
121
+        riteStr = 'Right'
122
+    else:
123
+        print 'Policy %s is not yet implemented' % policy
124
+        exit(1)
125
+
126
+    # track reference bits for clock
127
+    ref   = {}
128
+
129
+    cdebug = False
130
+
131
+    # need to generate addresses
132
+    addrIndex = 0
133
+    for nStr in addrList:
134
+        # first, lookup
135
+        n = int(nStr)
136
+        try:
137
+            idx = memory.index(n)
138
+            hits = hits + 1
139
+            if policy == 'LRU' or policy == 'MRU':
140
+                update = memory.remove(n)
141
+                memory.append(n) # puts it on MRU side
142
+        except:
143
+            idx = -1
144
+            miss = miss + 1
145
+
146
+        victim = -1        
147
+        if idx == -1:
148
+            # miss, replace?
149
+            # print 'BUG count, cachesize:', count, cachesize
150
+            if count == cachesize:
151
+                # must replace
152
+                if policy == 'FIFO' or policy == 'LRU':
153
+                    victim = memory.pop(0)
154
+                elif policy == 'MRU':
155
+                    victim = memory.pop(count-1)
156
+                elif policy == 'RAND':
157
+                    victim = memory.pop(int(random.random() * count))
158
+                elif policy == 'CLOCK':
159
+                    if cdebug:
160
+                        print 'REFERENCE TO PAGE', n
161
+                        print 'MEMORY ', memory
162
+                        print 'REF (b)', ref
163
+
164
+                    # hack: for now, do random
165
+                    # victim = memory.pop(int(random.random() * count))
166
+                    victim = -1
167
+                    while victim == -1:
168
+                        page = memory[int(random.random() * count)]
169
+                        if cdebug:
170
+                            print '  scan page:', page, ref[page]
171
+                        if ref[page] >= 1:
172
+                            ref[page] -= 1
173
+                        else:
174
+                            # this is our victim
175
+                            victim = page
176
+                            memory.remove(page)
177
+                            break
178
+
179
+                    # remove old page's ref count
180
+                    if page in memory:
181
+                        assert('BROKEN')
182
+                    del ref[victim]
183
+                    if cdebug:
184
+                        print 'VICTIM', page
185
+                        print 'LEN', len(memory)
186
+                        print 'MEM', memory
187
+                        print 'REF (a)', ref
188
+
189
+                elif policy == 'OPT':
190
+                    maxReplace  = -1
191
+                    replaceIdx  = -1
192
+                    replacePage = -1
193
+                    # print 'OPT: access %d, memory %s' % (n, memory) 
194
+                    # print 'OPT: replace from FUTURE (%s)' % addrList[addrIndex+1:]
195
+                    for pageIndex in range(0,count):
196
+                        page = memory[pageIndex]
197
+                        # now, have page 'page' at index 'pageIndex' in memory
198
+                        whenReferenced = len(addrList)
199
+                        # whenReferenced tells us when, in the future, this was referenced
200
+                        for futureIdx in range(addrIndex+1,len(addrList)):
201
+                            futurePage = int(addrList[futureIdx])
202
+                            if page == futurePage:
203
+                                whenReferenced = futureIdx
204
+                                break
205
+                        # print 'OPT: page %d is referenced at %d' % (page, whenReferenced)
206
+                        if whenReferenced >= maxReplace:
207
+                            # print 'OPT: ??? updating maxReplace (%d %d %d)' % (replaceIdx, replacePage, maxReplace)
208
+                            replaceIdx  = pageIndex
209
+                            replacePage = page
210
+                            maxReplace  = whenReferenced
211
+                            # print 'OPT: --> updating maxReplace (%d %d %d)' % (replaceIdx, replacePage, maxReplace)
212
+                    victim = memory.pop(replaceIdx)
213
+                    # print 'OPT: replacing page %d (idx:%d) because I saw it in future at %d' % (victim, replaceIdx, whenReferenced)
214
+                elif policy == 'UNOPT':
215
+                    minReplace  = len(addrList) + 1
216
+                    replaceIdx  = -1
217
+                    replacePage = -1
218
+                    for pageIndex in range(0,count):
219
+                        page = memory[pageIndex]
220
+                        # now, have page 'page' at index 'pageIndex' in memory
221
+                        whenReferenced = len(addrList)
222
+                        # whenReferenced tells us when, in the future, this was referenced
223
+                        for futureIdx in range(addrIndex+1,len(addrList)):
224
+                            futurePage = int(addrList[futureIdx])
225
+                            if page == futurePage:
226
+                                whenReferenced = futureIdx
227
+                                break
228
+                        if whenReferenced < minReplace:
229
+                            replaceIdx  = pageIndex
230
+                            replacePage = page
231
+                            minReplace  = whenReferenced
232
+                    victim = memory.pop(replaceIdx)
233
+            else:
234
+                # miss, but no replacement needed (cache not full)
235
+                victim = -1
236
+                count = count + 1
237
+
238
+            # now add to memory
239
+            memory.append(n)
240
+            if cdebug:
241
+                print 'LEN (a)', len(memory)
242
+            if victim != -1:
243
+                assert(victim not in memory)
244
+
245
+        # after miss processing, update reference bit
246
+        if n not in ref:
247
+            ref[n] = 1
248
+        else:
249
+            ref[n] += 1
250
+            if ref[n] > clockbits:
251
+                ref[n] = clockbits
252
+        
253
+        if cdebug:
254
+            print 'REF (a)', ref
255
+
256
+        if notrace == False:
257
+            print 'Access: %d  %s %s -> %12s <- %s Replaced:%s [Hits:%d Misses:%d]' % (n, hfunc(idx), leftStr, memory, riteStr, vfunc(victim), hits, miss)
258
+        addrIndex = addrIndex + 1
259
+        
260
+    print ''
261
+    print 'FINALSTATS hits %d   misses %d   hitrate %.2f' % (hits, miss, (100.0*float(hits))/(float(hits)+float(miss)))
262
+    print ''
263
+
264
+
265
+
266
+    
267
+    
268
+    
269
+
270
+
271
+
272
+
273
+
274
+
275
+

+ 277
- 0
hw4/task1/README.md View File

@@ -0,0 +1,277 @@
1
+# Homework hw4 task1
2
+
3
+- [Überblick](#%C3%BCberblick)
4
+- [Daten aus dem /proc Verzeichnis lesen](#daten-aus-dem-proc-verzeichnis-lesen)
5
+- [Externe Crate nutzen](#externe-crate-nutzen)
6
+- [Optionalen Parameter parsen](#optionalen-parameter-parsen)
7
+- [Aufgaben](#aufgaben)
8
+    - [Externe Crate in Dependencies eintragen](#externe-crate-in-dependencies-eintragen)
9
+    - [`readproc.rs`: Eigene Prozessinformationen auslesen](#readprocrs-eigene-prozessinformationen-auslesen)
10
+    - [`pstree.rs`: Prozesskette ausgeben](#pstreers-prozesskette-ausgeben)
11
+- [Kontrolle Ihres Repositories](#kontrolle-ihres-repositories)
12
+
13
+## Überblick
14
+
15
+Ziel dieser Aufgabe ist es, mittels des externen Crates `procinfo` einige
16
+Prozessverwaltungs-Informationen kennen zu lernen, die das Betriebssystem beim
17
+Ausführen der Prozesse verwaltet. Fertige Programme wie **ps**, **htop** oder
18
+**pmap** verwenden diese Informationen, um über das System und Tasks Auskunft zu
19
+geben.
20
+
21
+Die Funktionalität der Programms wird auf die 3 Module aufgeteilt:
22
+
23
+- `main.rs`: die Funktion `main()
24
+- `readproc.rs`: Handling der Funktionen, die angefragte Informationen aus dem
25
+  `proc/` Verzeichnis zu Verfügung stellen.
26
+- `pstree.rs`: Struct und Methoden, um einen 'pstree' darzustellen
27
+
28
+## Daten aus dem /proc Verzeichnis lesen
29
+
30
+"Das `/proc`-Verzeichnis ist kein wirkliches Dateisystem, sondern eine
31
+Schnittstelle zum Kernel. Die Dateien, die in diesem Verzeichnis liegen,
32
+benutzen keinen Speicherplatz auf der Platte, sind aber trotzdem les- und in
33
+manchen Fällen auch beschreibbar.
34
+
35
+Seinen Namen trägt dieses Verzeichnis daher, dass es für jeden laufenden Prozess
36
+ein Unterverzeichnis bereithält, das Informationen über diesen Prozess zur
37
+Verfügung stellt. Das Unterverzeichnis trägt als Namen die ProzessID (PID) des
38
+jeweiligen Prozesses. Es enthält unter anderem folgende Dateien:
39
+
40
+- `cmdline` Die Kommandozeile, mit der der Prozess gestartet wurde, mit allen
41
+  verwendeten Parametern.
42
+- `cwd` (current working directory) Ein symbolischer Link auf das Verzeichnis,
43
+  das beim Aufruf des Prozesses das aktuelle Arbeitsverzeichnis war.
44
+- `environ` Die komplette Umgebung des Prozesses (Variablen, Funktionen usw.)
45
+  sofern er eine Umgebung hat.
46
+- `exe` Ein symbolischer Link auf das aufgerufene Programm, dass den Prozess
47
+  ausmacht.
48
+- `root` Ein symbolischer Link auf das Verzeichnis, das für den Prozess das
49
+  Wurzelverzeichnis darstellt.
50
+
51
+Daneben finden sich weitere Informationen zu den verwendeten Ressourcen
52
+(Speicher, Libraries) und ein Unterverzeichnis **fd**, das die File-Descriptoren
53
+aller vom Prozess verwendeten Dateien enthält.
54
+
55
+Diese Information wird beispielsweise von den Programmen verwertet, die
56
+Prozess-Informationen ausgeben." (aus [Referenz][])
57
+
58
+Das `/proc` Verzeichnis steht nur unter Linux zu Verfügung. Daher müssen
59
+Programme, die auf `/proc` zugreifen auch auf einem Linux System erstellt und
60
+getestet werden.
61
+
62
+## Externe Crate nutzen
63
+
64
+Um nicht selbst im Programm auf die nötigen Dateien per File E/A im ´/proc´
65
+zugreifen zu müssen, wird zum Auslesen eine externe Crate benutzt. Die Crate
66
+*[procinfo][]* stellt dazu die nötigen Funktionen zu Verfügung. Die Funktionen
67
+liefern in einem Result eine Datenstruktur, aus der wir die benötigten
68
+Informationen komfortabel auslesen können. Verwenden Sie in Ihrem Programm
69
+soweit wie möglich die Funktionen des externen Crates *procinfo*, auch wenn in
70
+der Standard-Bibliothek ebenfalls Funktionen für einzelne Aufgaben zu Verfügung
71
+stehen.
72
+
73
+## Optionalen Parameter parsen
74
+
75
+Der Optionale Parameter PID (u32) entscheidet darüber, ob Ihr Programm die
76
+Funktionen des Moduls `readproc.rs` oder `pstree.rs` verwendet. Wird kein
77
+Parameter angegeben, so werden die Funktionen des Moduls `readproc.rs` benutzt.
78
+
79
+## Aufgaben
80
+
81
+### Externe Crate in Dependencies eintragen
82
+
83
+- Benutzen Sie für die folgenden Aufgaben das *[procinfo][]* Crate.
84
+- Fügen Sie dazu den notwendigen Eintrag unter `[dependencies]` in
85
+  **Cargo.toml** hinzu.
86
+- Um auf die nötige Funktionen des Crate zugreifen zu können schreiben Sie NUR
87
+  in **main.rs** bitte folgenden Zeilen an den Beginn der Datei:
88
+
89
+ ```Rust
90
+ extern crate procinfo;
91
+ ```
92
+
93
+Benutzen Sie in Ihren Modulen `readproc.rs` und `pstree.rs` die `use` Anweisung
94
+geeignet, um auf die nötigen Funktionen des Crate *procinfo* in den Modulen
95
+zugreifen zu können.
96
+
97
+In der Crate *procinfo* wird als Return ein Result Typ mit nur dem OK Typ
98
+benutzt. Der Err Typ ist scheinbar nicht im Result enthalten. Wenn man
99
+allerdings die Info der Standardbibliothek zu [io::Result][] liest, erfährt man,
100
+dass es sich hier um einen Alias handelt, der komfortableres Arbeiten mit Errors
101
+erlaubt. Dazu kommen wir aber erst in späteren Kapiteln, wenn wir uns dem `?`
102
+Makro nähern. Daher bitte hier in der Aufgabe noch keine Makros wie `try` und
103
+`?` benutzen.
104
+
105
+### `readproc.rs`: Eigene Prozessinformationen auslesen
106
+
107
+Ziel dieser Teilaufgabe ist es das Crate *procinfo* kennen zu lernen und sich
108
+noch ein wenig mit dem Return Typ 'Result' und der Fehlerbehandlung zu üben.
109
+
110
+Die folgenden Funktionen werden im Modul `readproc.rs` implementiert. Alle
111
+Funktionen reichen mögliche Fehler über einen Result zurück zur aufrufenden
112
+Funktion. Keine der Funktionen darf im Fehlerfall einen Programmabbruch
113
+erzwingen.
114
+
115
+1. Schreiben Sie die Funktion `fn self_pids() -> Result<(i32, i32), &'static
116
+   str>`, die die eigene PID und die PPID in einem Tupel (PID,PPID) Im
117
+   Erfolgsfall zurückgibt.
118
+
119
+   > Hinweis: Überlegen Sie sich, ob `stat()` und der `stat_self()` die
120
+   > geeignetere Funktion im *procinfo* Crate für diesen Fall ist.
121
+
122
+1. Schreiben Sie die Funktion `fn get_pid_command(pid: i32) -> Result<String,
123
+   &'static str>`, die den Command Namen zu einer PID zurück liefert. Wird die
124
+   PID nicht gefunden im System, so soll die Funktion den String "PID not alive:
125
+   no command name found" zurück geben.
126
+
127
+1. Schreiben Sie die Funktion `fn get_last_created_command() -> Result<String,
128
+   &'static str>`, die den Command Namen des zuletzt erzeugten Prozesses im
129
+   System zurück gibt. Wird die PID nicht gefunden im System, so soll die
130
+   Funktion den String "No last command via PID found" zurück geben.
131
+
132
+   > Tip: `loadavg()` Funktion.
133
+
134
+1. Schreiben Sie die Funktion `fn get_thread_count(pid: i32) -> Result<u32,
135
+   &'static str>`, die die Anzahl der Threads pro PID zurückliefert. Wird die
136
+   PID nicht gefunden im System, so soll die Funktion den String "PID not alive:
137
+   no threads counted" zurück geben.
138
+
139
+1. Benutzen Sie nun Ihre Funktionen geeignet, um in Ihrer `main()` Funktion
140
+   folgende Ausgaben zu produzieren:
141
+
142
+   ```text
143
+    My PID : 31894 - process1 running 4 threads
144
+    My PPID: 27952 - process2 running 2 threads
145
+    Last process created in system was: process3
146
+   ```
147
+
148
+   > Hinweis: Die Nummer und Command Namen sind bei Ihnen verschieden. Wenn Sie
149
+   > Probleme beim Aufruf Ihres Programms über **cargo run** haben lesen Sie
150
+   > unbedingt die nächste Frage!
151
+
152
+1. Sie können Ihr Programm über:
153
+
154
+   - **cargo run** oder
155
+   - **./target/debug/task1** starten.
156
+
157
+   Überlegen Sie sich, wie es zu den unterschiedlichen Ausgaben und
158
+   Programmverhalten kommt.
159
+
160
+1. Schreiben Sie die Funktion `fn get_task_total() -> Result<u32, &'static
161
+   str>`, die die Gesamtmenge aller Tasks im System zurück liefert. Wird die
162
+   Gesamtmenge nicht gefunden, so soll die Funktion den String "No total count
163
+   of tasks in system found" zurück geben.
164
+
165
+   > Warum z.B. zeigt Ihnen das Programm **htop** eine andere Anzahl von
166
+   > Prozessen als Ihre ausgelesene Funktion?
167
+
168
+1. Schreiben Sie die Funktion `fn get_ownprocess_mem() ->
169
+   Result<(usize,usize,usize), &'static str>`, die in einem Tuple die Werte für:
170
+
171
+   - vsize
172
+   - code und
173
+   - data
174
+
175
+   zurück liefert.
176
+
177
+1. Im Modul `main.rs` produzieren Sie die Ausgabe für die Kommandozeile und
178
+   kümmern sich um evtl. Fehler. Bei korrektem Programmablauf ergibt sich
179
+   folgende Ausgabe (genau 5 Zeilen!):
180
+
181
+   ```text
182
+   My PID : 31894 - process1 running 4 threads
183
+   My PPID: 27952 - process2 running 2 threads
184
+   My mem : 3560 (vspace), 208 (code), 588 (data)
185
+   Last process created in system was: process3
186
+   Total number of tasks: 145
187
+   ```
188
+
189
+### `pstree.rs`: Prozesskette ausgeben
190
+
191
+1. Auf Basis des Crate *procinfo* sollen Sie bei Aufruf des Programms mit einer
192
+   PID, ausgehend von dieser PID der Tree bis zum aktuellen Programm ausgegeben
193
+   werden. Wird somit als Argument eine `1` angegeben, wird eine Funktionalität
194
+   des Kommandozeilen Programms **pstree / pstree.x11** nachgebildet. Wenn Sie sich z.B. mit
195
+   dem Kommando **ps** die PID Ihrer aktuellen Shell anzeigen lassen, können Sie
196
+   sich durch Aufruf von **pstree.x11 -p -s <pid>** die Kette aller Prozesse
197
+   anzeigen lassen, in denen die <pid> enthalten ist.
198
+
199
+   ```text
200
+   systemd(1)---sshd(1264)---sshd(7161)---sshd(7198)---zsh(7199)---pstree.x11(47200)
201
+   ```
202
+
203
+   Genau diese Ausgabe soll nun Ihr Programm erzeugen bei der Option '1', wobei
204
+   natürlich das Ende der Kette nicht mehr `pstree.x11` ist sondern Ihr Programm.
205
+
206
+   Geben Sie im obigen Beispiel die PID 7161 als Parameter an, so soll Ihr Programm
207
+   nur den Teil-Tree ausgegeben, startend vom Prozess mit PID 7161.
208
+
209
+   ```text
210
+   sshd(7161)---sshd(7198)---zsh(7199)---task1(47200)
211
+   ```
212
+
213
+   Ausgehend von der eigenen pid sollen alle Elternpid bis zum übergebenen PID
214
+   (z.B. 1 für init Prozess, hier `systemd`) angezeigt werden. Der Init Prozess
215
+   (hier systemd) hat immer die PID 1 in UNIX Systemen, aber unterschiedliche Namen.
216
+
217
+1. Nutzen Sie zum parsen der PID im Argument die `parse()` Funktion. Behandeln
218
+   Sie folgende Fehler:
219
+
220
+   - Parameter ist keine Zahl
221
+   - Parameter ist keine Elternpid
222
+   - Mehr als 2 Parameter werden bei Aufruf mit angegeben.
223
+
224
+   Geben Sie im Fehlerfall eine entsprechende Meldung in einer(1!) Zeile aus und
225
+   beenden Sie das Programm mit dem Exitcode 1. Eine möglich Ausgabe:
226
+
227
+   ```text
228
+   $ ./task1 2 3
229
+   Correct usage: no param or param PID
230
+   ```
231
+
232
+   >Wichtig: Im Fehlerfall beenden Sie das Programm kontrolliert mit exit(1).
233
+   >Den Fehlercode '1' überprüfen die Tests in tests/output.bats!
234
+
235
+1. Erstellen Sie eine eigene Datenstruktur und Methoden um die Aufgabe zu lösen.
236
+   Verwenden Sie nur die externe Crate *procinfo* dazu. Die Ausgabe beim Aufruf
237
+   Ihres Programms muss folgender Beispielformatierung entsprechen:
238
+
239
+    ```text
240
+    systemd(1)---sshd(1264)---sshd(7161)---sshd(7198)---zsh(7199)---task1(47151)
241
+    ```
242
+   > Je nachdem wie Sie Ihr Programm aufrufen wird es natürlich andere Ausgaben
243
+   > produzieren. Durch das Kommandozeilen-Tool **pstree.x11** können Sie Ihre
244
+   > Ausgabe kontrollieren!
245
+
246
+1. Schreiben Sie eine eigene unit Test Datei `unit_test_pstree.rs`, die Ihre
247
+   Funktionen im Modul `pstree.rs` geeignet testet und beim Aufruf von **cargo
248
+   test** die Tests ausführt.
249
+
250
+1. Schreiben Sie ausreichend Kommentare, um Ihre Implementierung in `pstree.rs`
251
+   über die per **cargo doc** erstellten Dokumentation nachvollziehen zu können.
252
+
253
+## Kontrolle Ihres Repositories
254
+
255
+Haben Sie die Aufgaben komplett bearbeitet, so sollten sich folgende Dateien in
256
+Ihrem Projekt-Verzeichnis befinden:
257
+
258
+```text
259
+.
260
+├── Cargo.lock
261
+├── Cargo.toml
262
+├── README.md
263
+├── src
264
+│   ├── main.rs
265
+│   ├── pstree.rs
266
+│   ├── readproc.rs
267
+│   ├── unit_test_pstree.rs
268
+│   └── unit_test_readproc.rs
269
+└── tests
270
+    └── output.bats
271
+
272
+2 directories, 9 files
273
+```
274
+
275
+[Referenz]: http://www.linux-praxis.de/lpic1/lpi101/proc.html
276
+[procinfo]: https://docs.rs/procinfo/
277
+[io::Result]: https://doc.rust-lang.org/std/io/type.Result.html

+ 59
- 0
hw4/task1/src/unit_test_readproc.rs View File

@@ -0,0 +1,59 @@
1
+#[cfg(test)]
2
+mod tests {
3
+    use procinfo::pid::{status, status_self};
4
+    use {self_pids, get_pid_command, get_thread_count, get_ownprocess_mem, get_task_total};
5
+
6
+
7
+    fn sol_self_pids() -> (i32, i32) {
8
+        match status_self() {
9
+            Ok(status) => (status.pid, status.ppid),
10
+            Err(_) => panic!(),
11
+        }
12
+    }
13
+
14
+    #[test]
15
+    fn test0_ppid() {
16
+        assert_eq!(sol_self_pids(), self_pids().unwrap());
17
+    }
18
+
19
+    #[test]
20
+    fn test1_command() {
21
+        assert_eq!(
22
+            Err("PID not alive: no command name found"),
23
+            get_pid_command(0)
24
+        );
25
+    }
26
+
27
+    #[test]
28
+    fn test2_command() {
29
+        assert_eq!(Ok("systemd".to_string()), get_pid_command(1));
30
+    }
31
+
32
+
33
+    #[test]
34
+    fn test3_systemd_command() {
35
+        let status = status(1).unwrap();
36
+        assert_eq!("systemd".to_string(), status.command);
37
+    }
38
+
39
+    #[test]
40
+    fn test4_systemd_threads() {
41
+        let status = status(1).unwrap();
42
+        assert_eq!(get_thread_count(1), Ok(status.threads));
43
+    }
44
+
45
+    // Only check if fn is defined
46
+
47
+    #[test]
48
+    #[should_panic]
49
+    fn test8_mem() {
50
+        assert_eq!(Ok((0, 0, 0)), get_ownprocess_mem());
51
+    }
52
+
53
+    #[test]
54
+    #[should_panic]
55
+    fn test9_get_task_total() {
56
+        assert_eq!(Ok((0)), get_task_total());
57
+    }
58
+
59
+}

+ 45
- 0
hw4/task1/tests/output.bats View File

@@ -0,0 +1,45 @@
1
+#!/usr/bin/env bats
2
+
3
+
4
+@test "task1: Check that we have a debug output" {
5
+    run stat "$BATS_TEST_DIRNAME/../target/debug/task1"
6
+    [ "$status" -eq 0 ]
7
+}
8
+
9
+
10
+# wc output with white spaces is trimmed by xargs
11
+@test "task1: Output with no param must be exact 5 line long" {
12
+    run bash -c "'$BATS_TEST_DIRNAME/../target/debug/task1'  | wc -l | xargs"
13
+    [ "$output" = "5" ]
14
+}
15
+
16
+# wc output with white spaces is trimmed by xargs
17
+@test "task1: Output with to many paras must be exact 1 line long" {
18
+    run bash -c "'$BATS_TEST_DIRNAME/../target/debug/task1' 2 3 | wc -l | xargs"
19
+    [ "$output" = "1" ]
20
+
21
+}
22
+
23
+
24
+# wc output with white spaces is trimmed by xargs
25
+@test "task1: Output with wrong para must be exact 1 line long" {
26
+    run bash -c "'$BATS_TEST_DIRNAME/../target/debug/task1' b | wc -l | xargs"
27
+    [ "$output" = "1" ]
28
+}
29
+
30
+
31
+
32
+@test "task1: Output with wrong PID does not crash" {
33
+    run bash -c "'$BATS_TEST_DIRNAME/../target/debug/task1' 2 "
34
+    [ "$status" = 1 ]
35
+}
36
+
37
+@test "task1: Output with wrong PARAM does not crash" {
38
+    run bash -c "'$BATS_TEST_DIRNAME/../target/debug/task1' a "
39
+    [ "$status" = 1 ]
40
+}
41
+
42
+@test "task1: Output with to many para does not crash" {
43
+    run bash -c "'$BATS_TEST_DIRNAME/../target/debug/task1' 2 3 "
44
+    [ "$status" = 1 ]
45
+}

+ 376
- 0
hw4/task2/README.md View File

@@ -0,0 +1,376 @@
1
+# Homework hw4 task2
2
+
3
+Die folgenden Informationen sollen helfen, sich schneller in die Materie des
4
+'Timings' von Programmen einzuarbeiten:
5
+
6
+- Das externe Crate [time][] bietet mehr und vor allem einfachere Funktionalität
7
+  als die Rust Standard-Bibliothek.
8
+- Alle Betriebssysteme stellen eine Time API zu Verfügung. Es kann hilfreich
9
+  sein zu verstehen, wie diese funktioniert. Daher beschäftigt sich das nächste
10
+  Kapitel ausführlich mit dieser API.
11
+- Das Modul [std::mem] aus der Standardbibliothek ist zur Lösung dieser Aufgabe
12
+  sehr hilfreich.
13
+
14
+[time]: https://docs.rs/time
15
+[std::mem]: https://doc.rust-lang.org/std/mem/
16
+
17
+## Warmup für das Programm
18
+
19
+Um mit hoher Wahrscheinlichkeit Instruktionen und Daten aus dem Cache zu beziehen, sollte Ihr Programm ein kleines 'Cache' Warmup durchlaufen.
20
+
21
+Dies kann über ein simples Inkrementieren erreicht werden. Bei der Berechnung müssen Sie aber darauf achten, dass der Compiler das Ergebnis nicht vorausberechnen kann. Sonst würde bei der Optimierung der Compiler einfach das Ergebnis ausrechnen und das Ergebnis direkt in den Code schreiben. Damit wäre das Warmup (Durchlaufen des Codes) dahin.
22
+
23
+Mit einer Parameterübergabe durch die Kommandozeile steht das Ergebnis zur Compile-Zeit nicht fest und somit kann der Code nicht einfach 'weg'-optimiert werden. Dazu ein Beispiel:
24
+
25
+```Rust
26
+//where page_count and jump are cmd arguments
27
+let mut a = vec![0i32; page_count * jump];
28
+
29
+    // warm up
30
+    for i in a.iter_mut() {
31
+        *i += 1;
32
+    }
33
+```
34
+
35
+## Zeiten lesen in C
36
+
37
+Das folgende Kapitel muss zur Lösung von task2 nicht komplett verstanden werden.
38
+Vielmehr soll es weitere Informationen liefern, wenn Ihnen gewisse
39
+Funktionalitäten der Thematik 'Timing' unklar sind.
40
+
41
+- [Datenstrukturen](#datenstrukturen)
42
+- [Zeit lesen](#zeit-lesen)
43
+- [Zeitvergleich: Differenzzeitmessung](#zeitvergleich-differenzzeitmessung)
44
+
45
+### Datenstrukturen
46
+
47
+Betriebssysteme stellen Anwendungen Zeitgeber mit unterschiedlichen
48
+Eigenschaften zur Verfügung, über die das Zeitverhalten kontrolliert wird. Diese
49
+sind gekennzeichnet durch ihre
50
+
51
+- Genauigkeit,
52
+- die Zuverlässigkeit,
53
+- den Bezugspunkt,
54
+- die Darstellung und
55
+- den maximalen Zeitbereich.
56
+
57
+Das Betriebssystem repräsentiert Zeiten unter anderem mit den folgenden
58
+Datentypen (Darstellung):
59
+
60
+- clock_t: Timerticks.
61
+- struct timeval: Zeit in Mikrosekunden-Auflösung.
62
+- struct timespec: Zeit in Nanosekunden-Auflösung.
63
+- struct tm: absolute Zeitangabe.
64
+
65
+```c
66
+struct timeval {
67
+
68
+    time_t      tv_sec;     /* seconds */
69
+    suseconds_t tv_usec;    /* microseconds */
70
+};
71
+
72
+struct timespec {
73
+    time_t tv_sec; /* seconds */
74
+    long tv_nsec; /* nanoseconds */
75
+};
76
+
77
+struct tm
78
+{
79
+    int tm_sec; /* seconds */
80
+    int tm_min; /* minutes */
81
+    int tm_hour; /* hours */
82
+    int tm_mday; /* day of the month */
83
+    int tm_mon; /* month */
84
+    int tm_year; /* year */
85
+    int tm_wday; /* day of the week */
86
+    int tm_yday; /* day in the year */
87
+    int tm_isdst; /* daylight saving time */
88
+};
89
+```
90
+
91
+Die Strukturen struct timeval und struct timespec bestehen aus jeweils zwei
92
+Variablen, die einmal den Sekundenanteil und einmal den Mikro- beziehungsweise
93
+den Nanosekundenanteil repräsentieren. Die Darstellung erfolgt jeweils normiert.
94
+Das bedeutet, dass der Mikro- oder Nanosekundenanteil immer kleiner als eine
95
+volle Sekunde bleibt. Ein Zeitstempel von beispielsweise 1 Sekunde, 123456
96
+Mikrosekunden ist gültig, 1 Sekunde, 1234567 Mikrosekunden ist ungültig. In
97
+normierter Darstellung ergäbe sich 2 Sekunden, 234567 Mikrosekunden.
98
+
99
+Die Darstellungs- beziehungsweise Repräsentationsform reflektiert auch den
100
+darstellbaren Wertebereich. Da bei den dargestellten Datenstrukturen für den Typ
101
+time\_t ein long eingesetzt wird, lassen sich auf einer 32-Bit Maschine rund 4
102
+Milliarden Sekunden zählen, auf einer 64-Bit Maschine 2\^64 (mehr als 500
103
+Milliarden Jahre).
104
+
105
+Als Bezugspunkte haben sich die folgenden eingebürgert: *Start des Systems,
106
+*Start eines Jobs und \*Start einer Epoche, beispielsweise "Christi Geburt" oder
107
+der 1.1.1970 (Unix-Epoche). Dieser Bezugspunkt weist zudem noch eine örtliche
108
+Komponente auf: Der Zeitpunkt 19:00 Uhr in Europa entspricht beispielsweise
109
+einem anderen Zeitpunkt in den USA (minus sechs Stunden zur Ostküste).
110
+
111
+Die Genauigkeit wird beeinflußt durch, die Taktung des Zeitgebers, deren
112
+Schwankungen und durch Zeitsprünge.
113
+
114
+Das Attribut Zuverlässigkeit eines Zeitgebers beschreibt dessen Verhalten bei
115
+(bewußten) Schwankungen der Taktung und bei Zeitsprüngen: Ein Zeitgeber kann
116
+beispielsweise der Systemuhr folgen (CLOCK\_REALTIME) oder unabhängig von
117
+jeglicher Modifikation an der Systemzeit einfach weiterzählen
118
+(CLOCK\_MONOTONIC). Die Posix-Realzeiterweiterung beziehungsweise Linux-Systeme
119
+definieren hierzu folgende Clocks [Man-Page zu
120
+clock\_gettime()](http://linux.die.net/man/3/clock_gettime):
121
+
122
+- **CLOCK\_REALTIME**: Dieser Zeitgeber repräsentiert die systemweite, aktuelle
123
+  Zeit. Er reagiert auf Zeitsprünge, sowohl vorwärts als auch rückwärts, die
124
+  beispielsweise beim Aufwachen (Resume) nach einem Suspend (Schlafzustand des
125
+  gesamten Systems) ausgelöst werden. Er reagiert ebenfalls auf unterschiedliche
126
+  Taktungen, die beispielsweise durch NTP erfolgen. Dieser Zeitgeber liefert die
127
+  Sekunden und Nanosekunden seit dem 1.1. 1970 UTC (Unixzeit) zurück.
128
+- **CLOCK\_MONOTONIC**: Dieser Zeitgeber läuft entsprechend seiner Auflösung
129
+  stets vorwärts, ohne dabei Zeitsprünge zu vollziehen. Er ist also unabhängig
130
+  von der mit Superuserprivilegien zu verändernden Systemuhr. Allerdings
131
+  reagiert dieser Zeitgeber auf Modifikationen der Taktung, die beispielsweise
132
+  durch NTP erfolgen.
133
+- **CLOCK\_MONOTONIC_RAW**: Dieser Zeitgeber ist linuxspezifisch. Er reagiert
134
+  weder auf Zeitsprünge noch auf in Betrieb geänderte Taktungen (NTP).
135
+- **CLOCK\_PROCESS\_CPUTIME_ID**: Dieser Zeitgeber erfasst die Verarbeitungszeit
136
+  (Execution Time) des zugehörigen Prozesses. Das funktioniert aber nur
137
+  zuverlässig auf Single-Core-Systemen beziehungsweise wenn sichergestellt
138
+  werden kann, dass keine Prozessmigration stattfindet.
139
+- **CLOCK\_THREAD\_CPUTIME\_ID**: Dieser Zeitgeber erfasst die Verarbeitungszeit
140
+  (Execution Time) des zugehörigen Threads. Das funktioniert aber nur
141
+  zuverlässig auf Single-Core-Systemen beziehungsweise wenn sichergestellt
142
+  werden kann, dass keine Prozessmigration stattfindet.
143
+
144
+Der maximale Zeitbereich schließlich ergibt sich durch die Auflösung des
145
+Zeitgebers und die Bitbreite der Variablen:
146
+
147
+```text
148
+zeitbereich = auflösung * 2^bitbreite
149
+```
150
+
151
+### Zeit lesen
152
+
153
+Es gibt unterschiedliche Systemfunktionen, mit denen die aktuelle Zeit gelesen
154
+werden kann. Favorisiert ist die Funktion *int clock\_gettime(clockid\_t
155
+clk\_id, struct timespec * tp)*, die die Zeit seit dem 1.1.1970 (Unixzeit) als
156
+Universal Time (Zeitzone UTC) zurückliefert (struct timespec). Konnte die
157
+aktuelle Zeit gelesen werden, gibt die Funktion Null, ansonsten einen Fehlercode
158
+zurück. Allerdings ist das Auslesen auf 32-Bit Systemen in so fern
159
+problematisch, da der 32-Bit Zähler am 19. Januar 2038 überläuft.
160
+
161
+```c
162
+struct timespec {
163
+        time_t   tv_sec;        /* seconds */
164
+        long     tv_nsec;       /* nanoseconds */
165
+};
166
+
167
+```
168
+
169
+```c
170
+struct timespec timestamp;
171
+    ...
172
+    if (clock_gettime(CLOCK_MONOTONIC,&timestamp))
173
+    {
174
+        perror("timestamp");
175
+        return -1;
176
+    }
177
+    printf("seconds: %ld, nanoseconds: %ld\n",
178
+        timestamp.tv\_sec, timestamp.tv\_nsec);
179
+```
180
+
181
+Durch die Wahl der Clock CLOCK\_PROCESS\_CPUTIME\_ID beziehungsweise
182
+CLOCK\_THREAD\_CPUTIME\_ID kann auch die Verarbeitungszeit ausgemessen werden
183
+(Profiling).
184
+
185
+Die Genauigkeit der zurückgelieferten Zeit kann mit Hilfe der Funktion
186
+*clock\_getres(clockid\_t clk\_id, struct timespec * res)* ausgelesen werden.
187
+
188
+Die Funktion *clock\_gettime()* ist nicht in der Standard-C-Bibliothek zu
189
+finden, sondern in der Realzeit-Bibliothek librt. Daher ist bei der
190
+Programmgenerierung diese Bibliothek hinzuzulinken (Parameter -lrt). Steht nur
191
+die Standard-C-Bibliothek zur Verfügung, kann
192
+
193
+- time\_t time(time\_t *t) oder auch
194
+- int gettimeofday(struct timeval * tv, struct timezone * tz)
195
+
196
+eingesetzt werden.
197
+
198
+*time()* gibt die Sekunden zurück, die seit dem 1.1.1970 (UTC) vergangen sind.
199
+
200
+```c
201
+time_t now;
202
+...
203
+now = time(NULL);
204
+```
205
+
206
+*gettimeofday()* schreibt an die per tv übergebene Speicheradresse die Sekunden
207
+und Mikrosekunden seit dem 1.1.1970. Das Argument tz wird typischerweise mit
208
+NULL angegeben.
209
+
210
+Liegen die Sekunden seit dem 1.1.1970 vor (timestamp.tv\_sec), können diese mit
211
+Hilfe der Funktionen
212
+
213
+- struct tm *localtime\_r(const time\_t *timep, struct tm *result) oder
214
+- struct tm *gmtime\_r(const time\_t * timep, struct tm *result)
215
+
216
+in die Struktur *struct tm* konvertiert werden.
217
+
218
+```c
219
+struct tm absolute_time;
220
+if (localtime_r( timestamp.tv_sec, &absolute_time )==NULL)
221
+{
222
+    perror("localtime_r" );
223
+    return -1;
224
+}
225
+
226
+printf("year: %d\n", absolute_time.tm_year);
227
+```
228
+
229
+Die Funktion *time\_t mktime(struct tm * tm)* konvertiert eine über die Struktur
230
+struct tm gegebene Zeit in Sekunden seit dem 1.1.1970 (time\_t).
231
+
232
+Mit Hilfe der Funktion *clock\_t times(struct tms \ buf)* lässt sich sowohl die
233
+aktuelle Zeit zu einem letztlich nicht genau definierten Bezugspunkt, als auch
234
+die Verarbeitungszeit (Execution-Time) des aufrufenden Prozesses bestimmen. Die
235
+Zeiten werden als Timerticks (clock\_t) zurückgeliefert. Die zurückgelieferte
236
+Verarbeitungszeit ist aufgeschlüsselt in die Anteile, die im Userland und die
237
+Anteile, die im Kernel verbraucht wurden. Außerdem werden diese Anteile auch für
238
+Kindprozesse gelistet.
239
+
240
+```c
241
+#include <stdio.h>
242
+#include <sys/times.h>
243
+#include <unistd.h>
244
+#include <time.h>
245
+
246
+int main( int argc, char **argv, char **envp )
247
+{
248
+    struct tms exec_time;
249
+    clock_t act_time;
250
+    long ticks_per_second;
251
+    long tickduration_in_ms;
252
+
253
+    ticks_per_second = sysconf(_SC_CLK_TCK);
254
+    tickduration_in_ms = 1000/ticks_per_second;
255
+
256
+    act_time = times( &exec_time );
257
+    printf("actual time (in ms): %ld\n", act_time*tickduration_in_ms);
258
+    printf("execution time (in ms): %ld\n",
259
+            (exec_time.tms_utime+exec_time.tms_stime)*tickduration_in_ms);
260
+
261
+    return 0;
262
+
263
+}
264
+```
265
+
266
+Sehr genaue Zeiten lassen sich erfassen, falls der eingesetzte Prozessor einen
267
+Zähler besitzt, der mit der Taktfrequenz des Systems getaktet wird. Bei einer
268
+x86-Architektur (PC) heißt dieser Zähler Time Stamp Counter (TSC). Der TSC kann
269
+auch von einer normalen Applikation ausgelesen werden, allerdings muss
270
+sichergestellt sein, dass sich die Taktfrequenz zwischen zwei Messungen ändert.
271
+Alternativ kann man sich vom Betriebssystem über die Taktänderung informieren
272
+lassen.
273
+
274
+### Zeitvergleich: Differenzzeitmessung
275
+
276
+Zwei Absolutzeiten (struct tm) werden am einfachsten über deren Repräsentation
277
+in Sekunden verglichen. Die Umwandlung erfolgt über die Funktion (time\_t
278
+mktime(struct tm \*tm)). Allerdings ist dabei zu beachten, dass es auf einem
279
+32-Bit System am 19. Januar 2038 zu einem Überlauf kommt. Wird einer der beiden
280
+Zeitstempel vor dem 19. Januar 2038 genommen, der andere danach, kommt es zu
281
+einem falschen Ergebnis, wenn nur die beiden Werte per "\<" beziehungsweise "\>"
282
+verglichen werden.
283
+
284
+Das ist ein generelles Problem und kann dann gelöst werden, wenn sichergestellt
285
+ist, dass die zu vergleichenden Zeiten nicht weiter als die Hälfte des gesamten
286
+Zeitbereiches auseinanderliegen. In diesem Fall lassen sich die Makros
287
+einsetzen, die im Linux-Kernel für den Vergleich zweier Zeiten eingesetzt
288
+werden. Das Makro time\_after(a,b) liefert true zurück, falls es sich bei a um
289
+eine spätere Zeit als b handelt. Das Makro time\_after\_eq(a,b) liefert true
290
+zurück, falls es sich bei a um eine spätere Zeit oder um die gleiche Zeit
291
+handelt, wie b handelt. Die Zeitstempel a und b müssen beide vom Typ unsigned
292
+long sein. Natürlich können die Makros auch auf andere Datentypen angepasst
293
+werden
294
+
295
+[HEADERDATEI linux/jiffies.h].
296
+
297
+```c
298
+#define time_after(a,b)         \
299
+        (typecheck(unsigned long, a) && \
300
+         typecheck(unsigned long, b) && \
301
+         ((long)(b) - (long)(a) < 0))
302
+
303
+#define time_before(a,b)        time_after(b,a)
304
+
305
+#define time_after_eq(a,b)      \
306
+        (typecheck(unsigned long, a) && \
307
+         typecheck(unsigned long, b) && \
308
+         ((long)(a) - (long)(b) \>= 0))
309
+
310
+#define time_before_eq(a,b)     time_after_eq(b,a)
311
+```
312
+
313
+Beim Benchmarking ist es häufig notwendig eine Zeitdauer zu messen, Zeitpunkte
314
+zu erfassen oder eine definierte Zeit verstreichen zu lassen. Dabei sind
315
+folgende Aspekte zu beachten:
316
+
317
+- Die Genauigkeit der eingesetzten Zeitgeber,
318
+- die maximalen Zeitdifferenzen,
319
+- Schwankungen der Zeitbasis, beispielsweise durch Schlafzustände,
320
+- Modifikationen an der Zeitbasis des eingesetzten Rechnersystems (Zeitsprünge)
321
+  und
322
+- die Ortsabhängigkeit absoluter Zeitpunkte.
323
+
324
+Zur Bestimmung einer Zeitdauer verwendet man häufig eine Differenzzeitmessung.
325
+Dabei wird vor und nach der auszumessenden Aktion jeweils ein Zeitstempel
326
+genommen. Die Zeitdauer ergibt sich aus der Differenz dieser beiden Zeitstempel.
327
+
328
+Dies ist allerdings nicht immer ganz einfach. Liegt der Zeitstempel
329
+beispielsweise in Form der Datenstruktur struct timeval (als Ergebnis der
330
+Funktion *gettimeofday()*) vor, müssen die Sekunden zunächst getrennt von den
331
+Mikrosekunden subtrahiert werden. Ist der Mikrosekundenanteil negativ, muss der
332
+Sekundenanteil um eins erniedrigt und der Mikrosekundenanteil korrigiert werden.
333
+Dazu wird auf die Anzahl der Mikrosekunden pro Sekunde (also eine Million) der
334
+negative Mikrosekundenanteil addiert.
335
+
336
+```c
337
+#define MICROSECONDS\_PER\_SECOND 1000000
338
+
339
+struct timespec * diff_time( struct timeval before, struct timeval after,
340
+    struct timeval *result )
341
+{
342
+    if (result==NULL)
343
+        return NULL;
344
+
345
+    result->tv_sec = after.tv_sec - before.tv_sec;
346
+    result->tv_usec= after.tv_usec- before.tv_usec;
347
+
348
+    if (result->tv_usec<0) {
349
+        result->tv_sec--;
350
+        /* result->tv_usec is negative, therefore we use "+" */
351
+        result->tv_usec = MICROSECONDS_PER_SECOND+result->tv_usec;
352
+    }
353
+    return result;
354
+
355
+}
356
+```
357
+
358
+Für Zeitstempel vom Typ struct timeval gilt entsprechendes. Anstelle der
359
+MICROSECONDS\_PER\_SECOND sind NANOSECONDS\_PER\_SECOND einzusetzen. Sind die
360
+Zeitstempel vorzeichenlos, sieht die Rechnung für den Sekundenanteil etwas
361
+komplizierter aus. Das soll hier aber nicht weiter vertieft werden.
362
+
363
+Etwas einfacher ist die Differenzbildung, wenn aus der Datenstruktur eine
364
+einzelne Variable mit der gewünschten Auflösung, beispielsweise Mikrosekunden,
365
+generiert wird. Im Fall von struct timeval wird dazu der Sekundenanteil mit
366
+einer Million multipliziert und der Mikrosekundenanteil aufaddiert. Bei der
367
+Multiplikation können natürlich Informationen verloren gehen, allerdings geht
368
+der gleiche Informationsgehalt auch beim zweiten Zeitstempel verloren. Für die
369
+Differenzbildung ist das damit nicht relevant, solange der zu messende zeitliche
370
+Abstand kleiner als 1000 Sekunden ist und es während der Messung keinen Überlauf
371
+beim Sekundenanteil gibt.
372
+
373
+```c
374
+time_in_usec=((nachher.tv_sec*1000000)+nachher.tv_usec)-
375
+    ((vorher.tv_sec*1000000)+vorher.tv_usec);
376
+```

Loading…
Cancel
Save