Lectures‎ > ‎

Week04


Process Definitions

  • A program in execution
  • An instance of a program running on a computer
  • A process has
    • User-level state: memory + register values
      • Memory: stack, heap, code
    • Kernel-level state: stack, process info, memory maps, file descriptors, network connections, etc.

Process Execution

  • A kernel provides a mechanism to create new processes
    • fork() in UNIX
  • On creation, memory is allocated for the process
  • Code is loaded into memory, the stack is initialized
  • The process begins at a pre-determined location
    • main()

The Stack

  • The stack is a special region of memory used to support program execution
  • Primarily used for supporting function calls
    • Passing parameters, local variables, return address
    • Used by compilers to "spill" registers to memory
  • Most programming languages use a stack to support program execution
  • Process execution needs
    • Stack pointer (sp)
    • Instruction pointer (ip or pc)
    • General registers (r1, r2, eax, ebx, etc.)

UNIX Concepts

  • Processes
  • System calls
  • File descriptors
  • File and directories
  • Pipes

Overview of System Calls

  • The OS kernel is an extended machine
  • The kernel provides convenient abstractions for system resources:
    • Processes
    • Memory
    • Files
    • Network
  • User mode processes access kernel abstractions using system calls
  • In C, a system call looks like a function call:
    • pid = getpid()
  • However, unlike normal functions, a system call transfers control to the kernel
  • Draw picture of getpid() and sys_getpid()

System Call Template


int syscall(type1 arg1, type2 arg2, ...) {

put arguments into registers

trap into kernel using special instruction

(e.g., int 0x80)

extract return value and return

}


System Calls vs Library Functions

  • A system call is executed in the kernel
    • p = getpid();
  • A library function is executed in user space
    • n = strlen(s);
  • Some library calls are implemented with system calls
    • printf() really calls the write() system call
  • Programs use both system calls and library functions

UNIX System Calls

  • Files and I/O
  • Processes
  • Pipes
  • Signals
  • Time
  • Network I/O with sockets

UNIX Man Pages

  • UNIX man pages (manual pages, documentation) are divided into sections:
    • Section 1: commands (issued from command line)
    • Section 2: system calls
    • Section 3: library functions
  • Examples:
    • man 2 getpid
    • man 3 strlen

System Call Basics

  • Since system calls are functions, we need to include the proper header files
    • E.g., for getpid() we need
      • #include <sys/types.h>
      • #include <unistd.h>
    • See man pages for details.
  • Most system calls have a meaningful return value
    • Usually, -1 or a negative value indicates an error
    • A specific error code is place in a global variable called
      • errno
    • To access errno you must declare it:
      • extern int errno;

Using errno

  • Consider the read() system call
    • ssize_t read(int fd, void *buf, size_t nbytes);
  • On success it returns the number of bytes read
  • Otherwise it returns -1
  • Actual error code is errno, some errors include
    • [EBADF] fd is invalid
    • [EFAULT] buf is an invalid address
    • [EIO] I/O error
    • [EISDIR] fs is a directory, not a file, cannot read
  • Use char *strerror(int code) to get a text version of errno

UNIX Files and I/O

  • Steps for accessing a file (and devices):
    • open a file/device (existing or new)
    • open returns a file descriptor (fd)
    • Use the fd to access the file/device (read/write/etc.)
    • close(fd) when finished
  • It is possible to open more than one file at a time
  • You should alway close a file when you are finished

File Descriptors

  • A small non-negative integer
  • Returned by open()
  • Used as first argument in read(), write(), close(), etc.
  • The fd is an index into the process's file table
    • The kernel uses the file table to keep track of open files
    • By default, the table consists of:
      • 0 : stdin
      • 1 : stdout
      • 2 : stderr

Default File Descriptors

  • You do not need to open the default file descriptors
    • stdin, stdout, stderr
  • The are used to obtain console input and give console output
  • Examples
write(1, "Hello World!\n", 13);

char buf[128];
read(0, buf, 127);


The open() System Call

#include <fcntl.h>
int open(char *path, int flags [, mode_t mode]);
  • Create a new file or open an existing file
  • Flags
    • O_RDONLY
    • O_WRONLY
    • O_RDWR
    • O_CREAT
    • use bitwise or ("|") (e.g., O_WRONLY | O_CREAT)
  • Mode: octal value for rwx bits (e.g. 0400 read only by owner)


More File I/O System Calls

int close(int fd);

    Close a previously opened file

ssize_t read(int fd, void *buf, ssize_t nbytes);

    Extract date from a file or device

ssize_t write(int fd, void *buf, ssize_t nbytes):

    Write to a file or device

off_t lseek(int fd, off_t offset, int whence);

    Move to a specified byte offset in file



File I/O Examples

count.c

/* count.c - count the number of bytes in a file */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>


#define BUFSIZE 512  /* number of bytes to read at a time */

int main(int argc, char *argv[])
{
    char buffer[BUFSIZE];
    int fd;
    int count;
    long total = 0;

    if ( (fd = open(argv[1], O_RDONLY)) < 0) {
        printf("Cannot open %s\n", argv[1]);
        exit(1);
    }

    printf("fd = %d\n", fd);

    while ( (count = read(fd, buffer, BUFSIZE)) > 0 ) {
        total += count;
    }
        
    printf("Total bytes in %s is %ld\n", argv[1], total);

    close(fd);
    return(0);
}

caps.c

/* caps.c - convert user input to caps and write to a file */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <ctype.h>

int main(int argc, char *argv[])
{
    char buffer[2];
    int fd;
    int count;
    long total = 0;

    if (argc != 2) {
        printf("usage: caps filename\n");
        exit(1);
    }
        
    if ( (fd = open(argv[1], O_CREAT | O_WRONLY, 0600)) < 0) {
        printf("Cannot open %s\n", argv[1]);
        exit(1);
    }

    while ( (count = read(0, buffer, 1)) > 0 ) {
        buffer[0] = toupper(buffer[0]);
        write(fd, buffer, 1);
    }

    close(fd);
    return(0);
}

Process Details

  • In user space
    • Code (C functions)
    • Data (static and dynamic)
    • Stack
    • Registers
  • In kernel space
    • Process Control Block
    • Priority
    • File descriptors
    • Memory map
    • Others


UNIX Processes

  • Recall a process is a program in execution
  • Processes create other processes with the fork() system call
  • fork() creates an identical copy of the parent process
  • We say the parent has cloned itself to create a child
  • We can tell the two process apart use the return value of fork()
    • In parent: fork() returns the PID of the new child
    • In child: fork() returns 0
  • fork() may seem strange at first, that's because it is a bit strange!
  • Draw picture
main() {
    int id;

    id = fork();
    if (id == 0) {
        /* in child */
    } else {
        /* in parent */
    }
}





Starting New Programs

  • fork() only allows us to create a new process that is a duplicate of the parent
  • The exec() system call is used to start a new program
  • exec() replaces the memory image of the calling processes with the image of the new program
  • We use fork() and exec() together to start a new program
  • Note: be sure to check the return value of exec()
    • If exec() fails, then the memory image of the calling process remains the same
    • exec() does not return success (why?)
  • Draw picture:

main() {
    int id;
    
    id = fork();
    if (id == 0) {
        /* in child */
        exec("/bin/ls");
    } else {
        /* in parent */
        wait();
    }
}




Syscalls for Processes

  • pid_t fork(void)
    • Create a new child process, which is a copy of the current process
    • Parent return value is the PID of the child proces
    • Child return value is 0
  • int execl(char *name, char *arg0, ..., (char *) 0)
    • Change program image of current process
    • Reset stack and free memory
    • Start at main()
    • Also see other versions: execlp(), execv(), etc.
  • pid_t wait(int *status)
    • Wait for a child process (any child) to complete
    • Also see waitpid() to wait for a specific process
  • void exit(int status)
    • Terminate the calling process
    • Can also achieve with a return from main()
  • int kill(pid_t pid, int sig)
    • Send a signal to a process
    • Send SIGKILL to force termination

Process Creation Details

  • fork() creates a copy of the current process
    • It is the only way to create a new process
  • What is copied on fork():
    • Memory of process (code, data, stack)
    • Registers (stack pointer, program counter, etc.)
    • File descriptors
    • Command-line arguments
    • Environment
  • Only the return value is different
    • Can fork() fail?


Process Examples

fork1.c

/* fork1.c - example of using the fork system call */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    pid_t id;
    int x = 3;

    printf("Parent: x = %d\n", x);

    id = fork();

    if (id == 0) {
        /* we are in the child */
        printf("Child: id = %d\n", id);
        printf("Child PID = %d\n", getpid());
        printf("Child: x = %d\n", x);
        x = 99;
        printf("Child: x = %d\n", x);
        printf("Child: &x = 0x%8X\n", &x);
        printf("Child: &x = 0x%8X\n", (void *) 0x1);
        exit(0);
    } else {
        /* we are in the parent */
        printf("Parent PID = %d\n", getpid());
        printf("Parent: id = %d\n", id);
        printf("Parent: wait for child\n");
        id = wait(NULL);
        printf("Parent: child terminated pid = %d\n", id);
        printf("Parent: x = %d\n", x);
        printf("Parent: &x = 0x%8X\n", &x);
    }

    return 0;
}

fork2.c

/* fork2.c - example of using the fork and execl system calls */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    pid_t id;

    id = fork();

    if (id == 0) {
        /* we are in the child */
        execl("/bin/date", "date", NULL);
        printf("Child: WE DON'T SEE THIS\n");
        exit(0);
    } else {
        /* we are in the parent */
        id = wait(NULL);
        printf("Parent: child terminated\n");
    }

    return 0;
}

Comments