Operating Systems 2020W Lecture 7: Difference between revisions
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]. =..." |
(No difference)
|
Latest revision as of 02:33, 20 March 2020
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;
}