Exercises with Full Solution Code

 

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.

Leave a Reply

Your email address will not be published. Required fields are marked *