cd2c939e808aaac822de41c7779530f64045d027
[pintos-anon] / src / tests / userprog / no-vm / multi-oom.c
1 /* Recursively executes itself until the child fails to execute.
2    We expect that at least 30 copies can run.
3
4    We count how many children your kernel was able to execute
5    before it fails to start a new process.  We require that,
6    if a process doesn't actually get to start, exec() must
7    return -1, not a valid PID.
8
9    We repeat this process 10 times, checking that your kernel
10    allows for the same level of depth every time.
11
12    In addition, some processes will spawn children that terminate
13    abnormally after allocating some resources.
14
15    Written by Godmar Back <godmar@gmail.com>
16  */
17
18 #include <debug.h>
19 #include <stdio.h>
20 #include <string.h>
21 #include <stdlib.h>
22 #include <stdbool.h>
23 #include <syscall.h>
24 #include <random.h>
25 #include "tests/lib.h"
26
27 static const int EXPECTED_DEPTH_TO_PASS = 30;
28 static const int EXPECTED_REPETITIONS = 10;
29
30 const char *test_name = "multi-oom";
31
32 enum child_termination_mode { RECURSE, CRASH };
33
34 /* Spawn a recursive copy of ourselves, passing along instructions
35    for the child. */
36 static pid_t
37 spawn_child (int c, enum child_termination_mode mode)
38 {
39   char child_cmd[128];
40   snprintf (child_cmd, sizeof child_cmd,
41             "%s %d %s", test_name, c, mode == CRASH ? "-k" : "");
42   return exec (child_cmd);
43 }
44
45 /* Open a number of files (and fail to close them).
46    The kernel must free any kernel resources associated
47    with these file descriptors. */
48 static void
49 consume_some_resources (void)
50 {
51   int fd, fdmax = 126;
52
53   /* Open as many files as we can, up to fdmax.
54      Depending on how file descriptors are allocated inside
55      the kernel, open() may fail if the kernel is low on memory.
56      A low-memory condition in open() should not lead to the
57      termination of the process.  */
58   for (fd = 0; fd < fdmax; fd++)
59     if (open (test_name) == -1)
60       break;
61 }
62
63 /* Consume some resources, then terminate this process
64    in some abnormal way.  */
65 static int NO_INLINE
66 consume_some_resources_and_die (int seed)
67 {
68   consume_some_resources ();
69   random_init (seed);
70   volatile int *PHYS_BASE = (volatile int *)0xC0000000;
71
72   switch (random_ulong () % 5)
73     {
74       case 0:
75         *(volatile int *) NULL = 42;
76
77       case 1:
78         return *(volatile int *) NULL;
79
80       case 2:
81         return *PHYS_BASE;
82
83       case 3:
84         *PHYS_BASE = 42;
85
86       case 4:
87         open ((char *)PHYS_BASE);
88         exit (-1);
89
90       default:
91         NOT_REACHED ();
92     }
93   return 0;
94 }
95
96 /* The first copy is invoked without command line arguments.
97    Subsequent copies are invoked with a parameter 'depth'
98    that describes how many parent processes preceded them.
99    Each process spawns one or multiple recursive copies of
100    itself, passing 'depth+1' as depth.
101
102    Some children are started with the '-k' flag, which will
103    result in abnormal termination.
104  */
105 int
106 main (int argc, char *argv[])
107 {
108   int n;
109
110   n = argc > 1 ? atoi (argv[1]) : 0;
111   bool is_at_root = (n == 0);
112   if (is_at_root)
113     msg ("begin");
114
115   /* If -k is passed, crash this process. */
116   if (argc > 2 && !strcmp(argv[2], "-k"))
117     {
118       consume_some_resources_and_die (n);
119       NOT_REACHED ();
120     }
121
122   int howmany = is_at_root ? EXPECTED_REPETITIONS : 1;
123   int i, expected_depth = -1;
124
125   for (i = 0; i < howmany; i++)
126     {
127       pid_t child_pid;
128
129       /* Spawn a child that will be abnormally terminated.
130          To speed the test up, do this only for processes
131          spawned at a certain depth. */
132       if (n > EXPECTED_DEPTH_TO_PASS/2)
133         {
134           child_pid = spawn_child (n + 1, CRASH);
135           if (child_pid != -1)
136             {
137               if (wait (child_pid) != -1)
138                 fail ("crashed child should return -1.");
139             }
140           /* If spawning this child failed, so should
141              the next spawn_child below. */
142         }
143
144       /* Now spawn the child that will recurse. */
145       child_pid = spawn_child (n + 1, RECURSE);
146
147       /* If maximum depth is reached, return result. */
148       if (child_pid == -1)
149         return n;
150
151       /* Else wait for child to report how deeply it was able to recurse. */
152       int reached_depth = wait (child_pid);
153       if (reached_depth == -1)
154         fail ("wait returned -1.");
155
156       /* Record the depth reached during the first run; on subsequent
157          runs, fail if those runs do not match the depth achieved on the
158          first run. */
159       if (i == 0)
160         expected_depth = reached_depth;
161       else if (expected_depth != reached_depth)
162         fail ("after run %d/%d, expected depth %d, actual depth %d.",
163               i, howmany, expected_depth, reached_depth);
164       ASSERT (expected_depth == reached_depth);
165     }
166
167   consume_some_resources ();
168
169   if (n == 0)
170     {
171       if (expected_depth < EXPECTED_DEPTH_TO_PASS)
172         fail ("should have forked at least %d times.", EXPECTED_DEPTH_TO_PASS);
173       msg ("success. program forked %d times.", howmany);
174       msg ("end");
175     }
176
177   return expected_depth;
178 }
179 // vim: sw=2