36.5 — Exercises
Chapter 36 Exercises with Full Solution Code | EmbeddedPathashala
3
Exercises
3
Solution Programs
Exercise 36-1
Problem: Write a program that shows that the getrusage() RUSAGE_CHILDREN flag retrieves information about only the children for which a wait() call has been performed. Have the program create a child process that consumes some CPU time, and then have the parent call getrusage() before and after calling wait().
/* Exercise 36-1 Solution
* Demonstrates that RUSAGE_CHILDREN only shows stats AFTER wait()
* Compile: gcc -O0 -o ex36_1 ex36_1.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/resource.h>
static void print_children_rusage(const char *label)
{
struct rusage ru;
if (getrusage(RUSAGE_CHILDREN, &ru) == -1) {
perror("getrusage"); return;
}
printf("%s\n", label);
printf(" User CPU : %ld.%06ld s\n",
(long)ru.ru_utime.tv_sec, (long)ru.ru_utime.tv_usec);
printf(" System CPU : %ld.%06ld s\n",
(long)ru.ru_stime.tv_sec, (long)ru.ru_stime.tv_usec);
printf(" Minor faults: %ld\n", ru.ru_minflt);
}
int main(void)
{
pid_t child;
long i;
print_children_rusage("=== BEFORE fork (should all be zero):");
child = fork();
if (child == -1) { perror("fork"); exit(1); }
if (child == 0) {
/* Child: burn CPU with a busy loop */
for (i = 0; i < 300000000L; i++)
;
printf("[child PID %d] Done burning CPU. Exiting.\n", (int)getpid());
exit(0);
}
/* Parent: give child time to finish */
sleep(3);
print_children_rusage("\n=== AFTER child exits, BEFORE wait() (still zero):");
/* Now reap the child */
waitpid(child, NULL, 0);
print_children_rusage("\n=== AFTER wait() (child stats now visible):");
return 0;
}
/* Expected output:
* === BEFORE fork:
* User CPU : 0.000000 s
* === AFTER child exits, BEFORE wait():
* User CPU : 0.000000 s ← still zero!
* === AFTER wait():
* User CPU : 0.450000 s ← now non-zero
*
* Key lesson: stats are invisible until wait() is called.
*/
Exercise 36-2
Problem: Write a program that executes a command and then displays its resource usage. This is analogous to what the time(1) command does. Use it as:
./rusage command arg.../* Exercise 36-2 Solution: rusage wrapper (like /usr/bin/time)
* Compile: gcc -o rusage rusage.c
* Usage: ./rusage ls -la /tmp
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/resource.h>
int main(int argc, char *argv[])
{
pid_t child;
int status;
struct rusage ru;
if (argc < 2) {
fprintf(stderr, "Usage: %s command [args...]\n", argv[0]);
exit(1);
}
child = fork();
if (child == -1) { perror("fork"); exit(1); }
if (child == 0) {
/* Child: replace itself with the given command */
execvp(argv[1], &argv[1]);
perror("execvp"); /* Only reached on error */
exit(127);
}
/* Parent: wait for child to finish */
if (wait4(child, &status, 0, &ru) == -1) {
perror("wait4"); exit(1);
}
/* Note: wait4() fills ru with child's stats directly,
equivalent to waitpid() + getrusage(RUSAGE_CHILDREN) */
/* Print exit status */
if (WIFEXITED(status))
fprintf(stderr, "\nCommand exited with status %d\n",
WEXITSTATUS(status));
else if (WIFSIGNALED(status))
fprintf(stderr, "\nCommand killed by signal %d\n",
WTERMSIG(status));
/* Print resource usage */
fprintf(stderr, "\n--- Resource Usage ---\n");
fprintf(stderr, "User time : %ld.%03ld s\n",
(long)ru.ru_utime.tv_sec,
(long)ru.ru_utime.tv_usec / 1000);
fprintf(stderr, "Sys time : %ld.%03ld s\n",
(long)ru.ru_stime.tv_sec,
(long)ru.ru_stime.tv_usec / 1000);
fprintf(stderr, "Max RSS : %ld KB\n", ru.ru_maxrss);
fprintf(stderr, "Minor faults: %ld\n", ru.ru_minflt);
fprintf(stderr, "Major faults: %ld\n", ru.ru_majflt);
fprintf(stderr, "Vol ctx sw : %ld\n", ru.ru_nvcsw);
fprintf(stderr, "Invol ctx sw: %ld\n", ru.ru_nivcsw);
return WIFEXITED(status) ? WEXITSTATUS(status) : 1;
}
/* Sample run: ./rusage ls /usr/bin
* --- Resource Usage ---
* User time : 0.002 s
* Sys time : 0.005 s
* Max RSS : 2048 KB
* Minor faults: 487
* ...
* Note: wait4() is a Linux/BSD extension that combines
* waitpid() and getrusage(RUSAGE_CHILDREN) in one call. */
Exercise 36-3
Problem: Write programs to determine what happens if a process’s consumption of various resources already exceeds the soft limit specified in a call to setrlimit(). For example, what happens if a process already has 20 open file descriptors and then sets RLIMIT_NOFILE to 15?
/* Exercise 36-3 Solution
* Tests what happens when you set a limit BELOW current consumption.
* Compile: gcc -o ex36_3 ex36_3.c
*
* Key finding: setting a limit below current usage does NOT
* immediately kill the process or close existing resources.
* The limit only affects FUTURE allocations.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/resource.h>
int main(void)
{
struct rlimit rl;
int fds[30];
int open_count = 0;
int i;
/* ---- Test 1: RLIMIT_NOFILE ---- */
printf("=== Test: RLIMIT_NOFILE ===\n");
/* Open 20 files */
for (i = 0; i < 20; i++) {
fds[i] = open("/dev/null", O_RDONLY);
if (fds[i] != -1) open_count++;
}
printf("Opened %d file descriptors\n", open_count);
/* Now set RLIMIT_NOFILE BELOW current usage */
getrlimit(RLIMIT_NOFILE, &rl);
rl.rlim_cur = 10; /* less than the 20+ already open */
setrlimit(RLIMIT_NOFILE, &rl);
printf("Set RLIMIT_NOFILE soft limit to 10 (below current usage)\n");
/* Existing FDs are NOT closed — they continue to work */
char buf[16];
ssize_t n = read(fds[0], buf, sizeof(buf));
printf("read() on existing fd[0]: %s\n",
n >= 0 ? "still works!" : "failed");
/* New open() will fail because FD number would exceed limit */
int new_fd = open("/dev/null", O_RDONLY);
if (new_fd == -1 && errno == EMFILE)
printf("open() for new fd fails: EMFILE (as expected)\n");
else if (new_fd != -1) {
printf("open() succeeded (FD %d) — limit not enforced yet\n",
new_fd);
close(new_fd);
}
/* Clean up */
for (i = 0; i < open_count; i++) close(fds[i]);
/* ---- Test 2: RLIMIT_DATA / heap ---- */
printf("\n=== Test: RLIMIT_DATA (heap) ===\n");
/* Allocate 10 MB heap first */
char *heap = malloc(10 * 1024 * 1024);
if (heap) {
memset(heap, 1, 10 * 1024 * 1024);
printf("Allocated 10 MB heap\n");
}
/* Set RLIMIT_DATA below current data segment size */
getrlimit(RLIMIT_DATA, &rl);
rl.rlim_cur = 1024 * 1024; /* 1 MB — below current usage */
setrlimit(RLIMIT_DATA, &rl);
printf("Set RLIMIT_DATA to 1 MB (below current usage)\n");
/* Existing allocation still accessible */
heap[0] = 42;
printf("Existing heap memory still accessible: heap[0]=%d\n",
(int)(unsigned char)heap[0]);
/* New malloc will fail */
char *more = malloc(2 * 1024 * 1024);
printf("New malloc(2 MB): %s\n", more ? "succeeded" : "failed (ENOMEM)");
if (more) free(more);
free(heap);
printf("\nConclusion: Setting a limit below current usage does NOT\n");
printf("retroactively revoke existing resources. It only prevents\n");
printf("NEW allocations from exceeding the limit.\n");
return 0;
}
/* Key lesson from all tests:
* Resource limits are PROSPECTIVE, not retrospective.
* Existing open files, allocated memory, etc. are not
* taken away when you lower a limit. Only new allocations
* that would push the usage past the limit are rejected. */
Summary of Findings from Exercise 36-3
- Setting a limit below current consumption does not immediately affect existing resources.
- Existing open file descriptors remain valid and usable even after RLIMIT_NOFILE is lowered below the current count.
- Existing heap memory remains accessible even after RLIMIT_DATA is lowered below current usage.
- Only new resource allocations (new open(), new malloc()) are checked against the new lower limit.
- SUSv3 notes that setting RLIMIT_NOFILE below the highest open FD number may cause “unexpected behaviour” — it is implementation-defined and best avoided in production code.
