CS 111 Winter 2015 Lecture 4 Scribe Notes: OS Organization

Assembled by Evan Krause and Jenny Li

Introduction to Hard Modularity

Programs that exhibit modularity are easier to understand and debug. A common way to introduce modularity into a program is by dividing it up into functions that call one another. However, this approach is classified as soft modularity because implementation errors can cause unwanted interactions that go beyond the specified interfaces. To introduce more enforced boundaries or hard modularity into a program, one can implement client-server virtualization.

The simplest way to implement client-server virtualization is to write a simulator of the x86 architecture. The simulator will include:

The downside to this implementation is that it is too slow.

Hardware Assistance for Fast Simulators

The architecture and simulated architecture should be the same or similar. The instruction set is partitioned into dangerous and safe.

Hardware has 2 modes:

  1. Privileged mode: can run anything
  2. Unprivileged mode: can only run unprivileged instructions

By convention, if an application runs a certain privileged instruction, a system call is executed.

In unprivileged mode, the INT instruction will trap. The kernel code will execute in privileged mode when the trap occurs. This is referred to as a protected transfer of control.

Hardware:       INT x       pushes

ss       stack segment

esp       stack pointer

eflags       (e.g.: privileged?)

cs       code segment

eip       instruction pointer

error code

eip = trap[0x80]

Software:       By convention, in Linux the system call number is stored in %eax.

Arguments are placed in %ebx, ecd, %edx, %esc, %ebp

How does one return from a system call?

CALL --> RET

INT --> RETI       pops everything off the stack. The return value is stored in %eax.

However, this method is slow compared to function calls. Recent versions of Linux use a different calling convention.

SYSENTER       sets       cs, eip, ss, esp to values in machine-specific privileged registers

SYSEXIT does the opposite

Processes

A process is a program in execution on an isolated domain that can be manipulated in Unix by issuing system calls.

pid_t getpid(void);       gets the correct process type

/usr/include/systypes.h       typedef int pid_t

pid_t fork(void);       "clones" the current process

int main(void) {

    for (int i = 0; i < 3 i++)

      fork();

    return 0;

}

The child differs from the parent in terms of the return value of fork(), the pid/ppid, and file descriptors. In addition, the child has no file locks or pending signals.

int execvp(char const *file, char *const *argv);

      returns -1 always, because if it has returned then it hasn't totally changed the program.

void _exit(int)

      exit status 0-255, 0 is success. Doesn't destroy process object.

pid_t waitpid(pid_t pid, int *status, int flags);

A process can be runnable or exited.

$ date -u

  Jan 15 2015 ...

$ which date

/usr/bin/date

bool print_date(void) {

    pid_t fork();

    if (p < 0) return false;

    if (p == 0) {

      execvp("/usr/bin/date", (char *[]{"date", "-u", 0});

      exit(127);

}

    int status;

    if (waitpid(p, &status, 0) < 0 )

      return false;

    return (WIFEXITED(status) && WEEXISTSTATUS(status) == 0);

}

int posix_spawnp(pid_t *restrict pid, char const *restrict file, posix_spawnfile_actions_t *file_acts, posix_spawnattr_t *restruct attrp, argv, envp)