Operating Systems 2020W Lecture 7

From Soma-notes
Revision as of 22:33, 19 March 2020 by Soma (talk | contribs) (Created page with "==Video== Video for the lecture given on January 29, 2020 [https://homeostasis.scs.carleton.ca/~soma/os-2020w/lectures/comp3000-2020w-lec07-20200129.m4v is now available]. =...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Video

Video for the lecture given on January 29, 2020 is now available.

Notes

Topics

  • Assignment 1
  • Signals
  • setuid
  • login process

(others moved to Friday)

In Class

Lecture 7
---------

UNIX signals
 - messages received by a process, sent by another process or the kernel
 - used to pause or terminate process, and to report error conditions
   (memory access errors, divide by zero, etc)

 - you can send signals using the kill command (which makes a kill system call)
   - by default, sends SIGTERM

 - sorry for no subtitles, will try to set up soon.  Also will see if zoom
   helps with audio

 - can send any signal with kill, that's what it is for
   - but what they do depends on who you are, what privileges you have
   - is the way you send a signal

 - for example, regular users can't terminate processes running as other
   users (can't send SIGTERM or SIGKILL)
   - the root user can terminate any process (send signals to all processes)

 - signal handlers are a bit weird
   - regular functions, but...
   - they can run at any time
   - they are called directly by the kernel
   - they pause regular execution (which resumes after the signal handler
     finishes)

 - the C library registers default signal handlers
   - they mostly just terminate the process when a signal is received
 - you can register your own for *most* signals
   - except SIGKILL and SIGSTOP  ("force quit" and pause/stop)
     so you can't change what happens with them

   - but the rest, you can control by registering a signal handler for
     that signal.  Afterwards, your code will be called when your process
     receives that signal
      - your signal handler can ignore the signal
      - thus, for SIGSEGV, you can just ignore and have your program continue

   - if you want to send a signal for an application-specific purpose,
     use SIGUSR1 and SIGUSR2, that is what they are there for
   - you cannot define your own signals, they are defined by the kernel

 - signals introduce a weird form of concurrency in single-threaded UNIX
   processes
     - signal handlers can be called at any time, pausing whatever is currently
       going on
     - so if a signal handler modifies shared data structures or state
       it can corrupt ongoing work
     - basic rule: make sure signal handlers do as little as possible
       and don't interfere
     - even printing things out isn't recommended from a signal handler
       (that's I/O)

 - if signals can happen at anytime, that means they can also happen
   while a process is doing a system call
 - so, what happens to the system call?
     - option 1: system call is cancelled, returns an error
     - option 2: system call is paused then resumed
     - option 3: system call completes *then* signal handler is run
 - in reality, 1 or 3 is what happens, but sometimes we simulate 2
     - simulated by setting SA_RESTART option in sigaction
     - (this is the default behavior on BSD systems)
     - means a read or sleep will continue even if interrupted by a signal
        - signal handler runs
        - system call returns with error saying it was interrupted by a signal
        - library code calls system call again
 - for example, consider sleep and read (say for terminal input)
     - interrupted by signals, cause signal handler to be run
       then system call returns immediately (before sleep has finished
       or data has been read)
 - running a signal handler is an old fashioned way of handling an exception
     - before there were exceptions, there were signals
     - low-level exceptions are based on signals (e.g., divide by zero)

 - (and yes they kind of look like hardware interrupts, they are pure software
    but design is like interrupt handlers)

 - signals are relatively lightweight as far as IPC goes
     - but don't use them for performance-critical tasks, other
       mechanisms are much safer and more reliable

 - signals are specialized, not a general mechanism

 
 - when you type some special characters into a terminal they generate
   (by default) signals
     Ctrl-C generates SIGINT (I think)
     Ctrl-Z generates SIGSTOP

 - sigchld is sent to a process when a child process terminates
   - so parent doesn't have to call wait() and wait around, it
     can do its own thing and call wait() in a signal handler
     for SIGCHLD

 - all processes are listening to all signals
   - most just have default handlers defined by the C library
   - not quite sure what happens if you don't define a handler
     (e.g., if you bypass the C library)

 - when any process terminates, its parent must call wait to
   get its return value (otherwise it turns into a zombie)
 - if the parent goes away, there is always the parent of last resort:
   init (PID 1)
 - init always calls wait in response to SIGCHLD
 - if init terminates, the system shuts down

 - on modern Linux systems, init is part of systemd

 - a zombie process is a process that has terminated but who hasn't
   been reaped (waited on by its parent)
     - it still shows up in the process table with status "Z"
     - it can't be killed
     - can only be eliminated by killing the irresponsible parent
       - then init will take over and run wait
 

3000login
 - Q: what happens when you "log in" to a UNIX system?

 - first, have to authenticate (type in your password, do public key based
   auth, show your face, whatever)

 - after authentication, have to start the user's session.  How?

 - to a first approximation, it is just like running a command in a shell
   - except that first "command" has to run as a specific user

Code

download 3000login.c

/* 3000login.c */
/* version 0.1 */

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

int main(int argc, char *argv[], char *envp[])
{
        uid_t uid;
        int result;
        char *bash_argv[3];
        
        if (argc < 2) {
                fprintf(stderr, "Usage: %s <user ID>\n", argv[0]);
                exit(-1);
        }

        uid = atoi(argv[1]);

        result = setuid(uid);

        if (result == 0) {
                fprintf(stderr, "Successfully changed to uid %d\n", uid);
                bash_argv[0] = "bash";
                bash_argv[1] = "--login";
                bash_argv[2] = NULL;
                execve("/bin/bash", bash_argv, envp);
        } else {
                fprintf(stderr, "Failed to change to uid %d\n", uid);
                exit(-2);
        }

        fprintf(stderr, "Failed to exec bash\n");
        return -3;
}