Operating Systems 2020W Lecture 7
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
/* 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;
}