Operating Systems 2019W Lecture 6: Difference between revisions
Created page with "==Video== ==Notes== Topics for today * environment variables * signal handlers * I/O redirection" |
|||
(12 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
==Video== | ==Video== | ||
Video from the lecture given on January 23, 2019 [https://homeostasis.scs.carleton.ca/~soma/os-2019w/lectures/comp3000-2019w-lec06-20190123.m4v is now available]. | |||
==Notes== | ==Notes== | ||
===Signals=== | |||
* environment variables | * processes can register signal handler functions for specific signals | ||
* signal | * When the kernel delivers a signal to a process, it runs the specified handler | ||
* I/O redirection | * The C library defines default handlers for all signals (except STOP and KILL) | ||
* When a process gets a signal, current execution is interrupted and the handler is invoked. When the handler terminates, the process continues where it was | |||
* Interrupted system calls return with an error EINTR. | |||
* If the process was blocked on a system call, the system call is interrupted and the handler is run. | |||
* The standard library can do different things with interrupted system calls. The SA_RESTART flag says to run the system call again. It only applies to a few system calls, primarily read-like ones. (So, when a read is interrupted, it is tried again.) | |||
'''reactive.c''' | |||
* reactive.c waits forever, but says ouch when it receives SIGUSR1 | |||
* Note the sleep calls (which use the nanosleep system call) get interrupted when signals arrive. | |||
* The behavior of sleep does not change by setting SA_RESTART because sleep system calls are never restarted. | |||
* When nanosleep is interrupted, it returns the time left in the sleep. | |||
'''3000shell-errno.c''' | |||
* This is a corrected version of the attempt in lecture to prevent backgrounded processes from causing the shell to exit when SA_RESTART is removed. | |||
* Note how it does not exit if <tt>errno==EINTR</tt> (see highlighted lines) | |||
===I/O redirection=== | |||
To redirect standard in, out, or error, a shell does the following when running a command: | |||
* The shell does a fork | |||
* The parent (original process) calls wait on the child when it exits in order to get its return value. This wait can be synchronous (i.e., the wait system call blocks until the child terminates) or it can be asynchronous (the parent only calls wait when it receives a SIGCHLD signal). | |||
* '''The child process initializes standard in, out, and error by opening files and assigning them to file descriptors 0, 1, and 2''' | |||
* The child calls execve | |||
To implement this, the hardest part is parsing the command line in order to figure out what files, if any, the child should open for file descriptors 0, 1, and 2. | |||
===Command-line arguments and environment variables=== | |||
* They are both stored in the memory of the process. | |||
* They are put there by execve, as part of loading a new executable into a process (and erasing everything that was in the process's address space). | |||
* execve doesn't necessarily change other aspects of process's state, thus they should be initialized by something else (normally, the C library) | |||
* In particular, the data structures associated with file descriptors (i.e., open files) are preserved across execve. | |||
==Code== | |||
===reactive.c=== | |||
<source lang="c" line> | |||
#include <stdio.h> | |||
#include <signal.h> | |||
#include <unistd.h> | |||
#include <string.h> | |||
void signal_handler(int the_signal) | |||
{ | |||
if (the_signal == SIGUSR1) { | |||
fprintf(stderr, "Ouch!\n"); | |||
return; | |||
} | |||
} | |||
int main(int argc, char *argv[], char *envp[]) | |||
{ | |||
int i = 1; | |||
struct sigaction signal_handler_struct; | |||
memset(&signal_handler_struct, 0, sizeof(signal_handler_struct)); | |||
signal_handler_struct.sa_handler = signal_handler; | |||
signal_handler_struct.sa_flags = SA_RESTART; | |||
if (sigaction(SIGUSR1, &signal_handler_struct, NULL)) { | |||
fprintf(stderr, "Couldn't register SIGUSR1 handler.\n"); | |||
} | |||
printf("Hello!\n"); | |||
printf("Environment variables at %lx\n", (unsigned long) envp); | |||
printf("Argument variables at %lx\n", (unsigned long) argv); | |||
printf("Sitting around doing nothing...\n"); | |||
while (1) { | |||
sleep(i); | |||
i++; | |||
} | |||
return 0; | |||
} | |||
</source> | |||
===3000shell-errno.c=== | |||
<source lang="c" highlight="243,263-267" line> | |||
/* 3000shell.c */ | |||
/* v1 Sept. 24, 2017 */ | |||
/* based off of csimpleshell.c, Enrico Franchi © 2005 | |||
https://web.archive.org/web/20170223203852/ | |||
http://rik0.altervista.org/snippets/csimpleshell.html */ | |||
/* Original under "BSD" license */ | |||
/* This version is under GPLv3, copyright Anil Somayaji */ | |||
/* You really shouldn't be incorporating parts of this in any other code, | |||
it is meant for teaching, not production */ | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <unistd.h> | |||
#include <string.h> | |||
#include <errno.h> | |||
#include <sys/types.h> | |||
#include <sys/stat.h> | |||
#include <sys/wait.h> | |||
#include <dirent.h> | |||
#include <ctype.h> | |||
#include <fcntl.h> | |||
#include <signal.h> | |||
#define BUFFER_SIZE 1<<16 | |||
#define ARR_SIZE 1<<16 | |||
#define COMM_SIZE 32 | |||
const char *proc_prefix = "/proc"; | |||
void parse_args(char *buffer, char** args, | |||
size_t args_size, size_t *nargs) | |||
{ | |||
char *buf_args[args_size]; /* You need C99 */ | |||
char **cp; | |||
char *wbuf; | |||
size_t i, j; | |||
wbuf=buffer; | |||
buf_args[0]=buffer; | |||
args[0] =buffer; | |||
for(cp=buf_args; (*cp=strsep(&wbuf, " \n\t")) != NULL ;){ | |||
if ((*cp != '\0') && (++cp >= &buf_args[args_size])) | |||
break; | |||
} | |||
for (j=i=0; buf_args[i]!=NULL; i++){ | |||
if(strlen(buf_args[i])>0) | |||
args[j++]=buf_args[i]; | |||
} | |||
*nargs=j; | |||
args[j]=NULL; | |||
} | |||
/* this is kind of like getenv() */ | |||
char *find_env(char *envvar, char *notfound, char *envp[]) | |||
{ | |||
const int MAXPATTERN = 128; | |||
int i, p; | |||
char c; | |||
char pattern[MAXPATTERN]; | |||
char *value = NULL; | |||
p = 0; | |||
while ((c = envvar[p])) { | |||
pattern[p] = c; | |||
p++; | |||
if (p == (MAXPATTERN - 2)) { | |||
break; | |||
} | |||
} | |||
pattern[p] = '='; | |||
p++; | |||
pattern[p] = '\0'; | |||
i = 0; | |||
while (envp[i] != NULL) { | |||
if (strncmp(pattern, envp[i], p) == 0) { | |||
value = envp[i] + p; | |||
} | |||
i++; | |||
} | |||
if (value == NULL) { | |||
return notfound; | |||
} else { | |||
return value; | |||
} | |||
} | |||
void find_binary(char *name, char *path, char *fn, int fn_size) { | |||
char *n, *p; | |||
int r, stat_return; | |||
struct stat file_status; | |||
if (name[0] == '.' || name[0] == '/') { | |||
strncpy(fn, name, fn_size); | |||
return; | |||
} | |||
p = path; | |||
while (*p != '\0') { | |||
r = 0; | |||
while (*p != '\0' && *p != ':' && r < fn_size - 1) { | |||
fn[r] = *p; | |||
r++; | |||
p++; | |||
} | |||
fn[r] = '/'; | |||
r++; | |||
n = name; | |||
while (*n != '\0' && r < fn_size) { | |||
fn[r] = *n; | |||
n++; | |||
r++; | |||
} | |||
fn[r] = '\0'; | |||
stat_return = stat(fn, &file_status); | |||
if (stat_return == 0) { | |||
return; | |||
} | |||
if (*p != '\0') { | |||
p++; | |||
} | |||
} | |||
} | |||
void setup_comm_fn(char *pidstr, char *comm_fn) | |||
{ | |||
char *c; | |||
strcpy(comm_fn, proc_prefix); | |||
c = comm_fn + strlen(comm_fn); | |||
*c = '/'; | |||
c++; | |||
strcpy(c, pidstr); | |||
c = c + strlen(pidstr); | |||
strcpy(c, "/comm"); | |||
} | |||
void plist() | |||
{ | |||
DIR *proc; | |||
struct dirent *e; | |||
int result; | |||
char comm[COMM_SIZE]; /* seems to just need 16 */ | |||
char comm_fn[512]; | |||
int fd, i, n; | |||
proc = opendir(proc_prefix); | |||
if (proc == NULL) { | |||
fprintf(stderr, "ERROR: Couldn't open /proc.\n"); | |||
} | |||
for (e = readdir(proc); e != NULL; e = readdir(proc)) { | |||
if (isdigit(e->d_name[0])) { | |||
setup_comm_fn(e->d_name, comm_fn); | |||
fd = open(comm_fn, O_RDONLY); | |||
if (fd > -1) { | |||
n = read(fd, comm, COMM_SIZE); | |||
close(fd); | |||
for (i=0; i < n; i++) { | |||
if (comm[i] == '\n') { | |||
comm[i] = '\0'; | |||
break; | |||
} | |||
} | |||
printf("%s: %s\n", e->d_name, comm); | |||
} else { | |||
printf("%s\n", e->d_name); | |||
} | |||
} | |||
} | |||
result = closedir(proc); | |||
if (result) { | |||
fprintf(stderr, "ERROR: Couldn't close /proc.\n"); | |||
} | |||
} | |||
void signal_handler(int the_signal) | |||
{ | |||
int pid, status; | |||
if (the_signal == SIGHUP) { | |||
fprintf(stderr, "Received SIGHUP.\n"); | |||
return; | |||
} | |||
if (the_signal != SIGCHLD) { | |||
fprintf(stderr, "Child handler called for signal %d?!\n", | |||
the_signal); | |||
return; | |||
} | |||
pid = wait(&status); | |||
if (pid == -1) { | |||
/* nothing to wait for */ | |||
return; | |||
} | |||
if (WIFEXITED(status)) { | |||
fprintf(stderr, "\nProcess %d exited with status %d.\n", | |||
pid, WEXITSTATUS(status)); | |||
} else { | |||
fprintf(stderr, "\nProcess %d aborted.\n", pid); | |||
} | |||
} | |||
int main(int argc, char *argv[], char *envp[]) | |||
{ | |||
char buffer[BUFFER_SIZE]; | |||
char *args[ARR_SIZE]; | |||
char bin_fn[BUFFER_SIZE]; | |||
struct sigaction signal_handler_struct; | |||
char *s; | |||
int *ret_status = NULL; | |||
size_t nargs; | |||
pid_t pid; | |||
int background; | |||
char *username; | |||
char *default_username = "UNKNOWN"; | |||
char *path; | |||
char *default_path = "/usr/bin:/bin"; | |||
memset (&signal_handler_struct, 0, sizeof(signal_handler_struct)); | |||
signal_handler_struct.sa_handler = signal_handler; | |||
/* signal_handler_struct.sa_flags = SA_RESTART; */ | |||
if (sigaction(SIGCHLD, &signal_handler_struct, NULL)) { | |||
fprintf(stderr, "Couldn't register SIGCHLD handler.\n"); | |||
} | |||
if (sigaction(SIGHUP, &signal_handler_struct, NULL)) { | |||
fprintf(stderr, "Couldn't register SIGHUP handler.\n"); | |||
} | |||
username = find_env("USER", default_username, envp); | |||
path = find_env("PATH", default_path, envp); | |||
while(1){ | |||
printf("%s $ ", username); | |||
s = fgets(buffer, BUFFER_SIZE, stdin); | |||
if (s == NULL) { | |||
/* we reached EOF */ | |||
printf("\n"); | |||
if (errno == EINTR) { | |||
fprintf(stderr, "Read system call was interrupted.\n"); | |||
} else { | |||
exit(0); | |||
} | |||
} | |||
parse_args(buffer, args, ARR_SIZE, &nargs); | |||
if (nargs==0) continue; | |||
if (!strcmp(args[0], "exit")) { | |||
exit(0); | |||
} | |||
if (!strcmp(args[0], "plist")) { | |||
plist(); | |||
continue; | |||
} | |||
background = 0; | |||
if (strcmp(args[nargs-1], "&") == 0) { | |||
background = 1; | |||
args[nargs-1] = NULL; | |||
nargs--; | |||
} | |||
pid = fork(); | |||
if (pid) { | |||
if (background) { | |||
fprintf(stderr, | |||
"Process %d running in the background.\n", | |||
pid); | |||
} else { | |||
pid = wait(ret_status); | |||
} | |||
} else { | |||
find_binary(args[0], path, bin_fn, BUFFER_SIZE); | |||
/* open files for I/O redirection */ | |||
/* assign it to 0, 1, 2, or other file descriptor */ | |||
if (execve(bin_fn, args, envp)) { | |||
puts(strerror(errno)); | |||
exit(127); | |||
} | |||
} | |||
} | |||
return 0; | |||
} | |||
</source> |
Latest revision as of 03:51, 24 January 2019
Video
Video from the lecture given on January 23, 2019 is now available.
Notes
Signals
- processes can register signal handler functions for specific signals
- When the kernel delivers a signal to a process, it runs the specified handler
- The C library defines default handlers for all signals (except STOP and KILL)
- When a process gets a signal, current execution is interrupted and the handler is invoked. When the handler terminates, the process continues where it was
- Interrupted system calls return with an error EINTR.
- If the process was blocked on a system call, the system call is interrupted and the handler is run.
- The standard library can do different things with interrupted system calls. The SA_RESTART flag says to run the system call again. It only applies to a few system calls, primarily read-like ones. (So, when a read is interrupted, it is tried again.)
reactive.c
- reactive.c waits forever, but says ouch when it receives SIGUSR1
- Note the sleep calls (which use the nanosleep system call) get interrupted when signals arrive.
- The behavior of sleep does not change by setting SA_RESTART because sleep system calls are never restarted.
- When nanosleep is interrupted, it returns the time left in the sleep.
3000shell-errno.c
- This is a corrected version of the attempt in lecture to prevent backgrounded processes from causing the shell to exit when SA_RESTART is removed.
- Note how it does not exit if errno==EINTR (see highlighted lines)
I/O redirection
To redirect standard in, out, or error, a shell does the following when running a command:
- The shell does a fork
- The parent (original process) calls wait on the child when it exits in order to get its return value. This wait can be synchronous (i.e., the wait system call blocks until the child terminates) or it can be asynchronous (the parent only calls wait when it receives a SIGCHLD signal).
- The child process initializes standard in, out, and error by opening files and assigning them to file descriptors 0, 1, and 2
- The child calls execve
To implement this, the hardest part is parsing the command line in order to figure out what files, if any, the child should open for file descriptors 0, 1, and 2.
Command-line arguments and environment variables
- They are both stored in the memory of the process.
- They are put there by execve, as part of loading a new executable into a process (and erasing everything that was in the process's address space).
- execve doesn't necessarily change other aspects of process's state, thus they should be initialized by something else (normally, the C library)
- In particular, the data structures associated with file descriptors (i.e., open files) are preserved across execve.
Code
reactive.c
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
void signal_handler(int the_signal)
{
if (the_signal == SIGUSR1) {
fprintf(stderr, "Ouch!\n");
return;
}
}
int main(int argc, char *argv[], char *envp[])
{
int i = 1;
struct sigaction signal_handler_struct;
memset(&signal_handler_struct, 0, sizeof(signal_handler_struct));
signal_handler_struct.sa_handler = signal_handler;
signal_handler_struct.sa_flags = SA_RESTART;
if (sigaction(SIGUSR1, &signal_handler_struct, NULL)) {
fprintf(stderr, "Couldn't register SIGUSR1 handler.\n");
}
printf("Hello!\n");
printf("Environment variables at %lx\n", (unsigned long) envp);
printf("Argument variables at %lx\n", (unsigned long) argv);
printf("Sitting around doing nothing...\n");
while (1) {
sleep(i);
i++;
}
return 0;
}
3000shell-errno.c
/* 3000shell.c */
/* v1 Sept. 24, 2017 */
/* based off of csimpleshell.c, Enrico Franchi © 2005
https://web.archive.org/web/20170223203852/
http://rik0.altervista.org/snippets/csimpleshell.html */
/* Original under "BSD" license */
/* This version is under GPLv3, copyright Anil Somayaji */
/* You really shouldn't be incorporating parts of this in any other code,
it is meant for teaching, not production */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <dirent.h>
#include <ctype.h>
#include <fcntl.h>
#include <signal.h>
#define BUFFER_SIZE 1<<16
#define ARR_SIZE 1<<16
#define COMM_SIZE 32
const char *proc_prefix = "/proc";
void parse_args(char *buffer, char** args,
size_t args_size, size_t *nargs)
{
char *buf_args[args_size]; /* You need C99 */
char **cp;
char *wbuf;
size_t i, j;
wbuf=buffer;
buf_args[0]=buffer;
args[0] =buffer;
for(cp=buf_args; (*cp=strsep(&wbuf, " \n\t")) != NULL ;){
if ((*cp != '\0') && (++cp >= &buf_args[args_size]))
break;
}
for (j=i=0; buf_args[i]!=NULL; i++){
if(strlen(buf_args[i])>0)
args[j++]=buf_args[i];
}
*nargs=j;
args[j]=NULL;
}
/* this is kind of like getenv() */
char *find_env(char *envvar, char *notfound, char *envp[])
{
const int MAXPATTERN = 128;
int i, p;
char c;
char pattern[MAXPATTERN];
char *value = NULL;
p = 0;
while ((c = envvar[p])) {
pattern[p] = c;
p++;
if (p == (MAXPATTERN - 2)) {
break;
}
}
pattern[p] = '=';
p++;
pattern[p] = '\0';
i = 0;
while (envp[i] != NULL) {
if (strncmp(pattern, envp[i], p) == 0) {
value = envp[i] + p;
}
i++;
}
if (value == NULL) {
return notfound;
} else {
return value;
}
}
void find_binary(char *name, char *path, char *fn, int fn_size) {
char *n, *p;
int r, stat_return;
struct stat file_status;
if (name[0] == '.' || name[0] == '/') {
strncpy(fn, name, fn_size);
return;
}
p = path;
while (*p != '\0') {
r = 0;
while (*p != '\0' && *p != ':' && r < fn_size - 1) {
fn[r] = *p;
r++;
p++;
}
fn[r] = '/';
r++;
n = name;
while (*n != '\0' && r < fn_size) {
fn[r] = *n;
n++;
r++;
}
fn[r] = '\0';
stat_return = stat(fn, &file_status);
if (stat_return == 0) {
return;
}
if (*p != '\0') {
p++;
}
}
}
void setup_comm_fn(char *pidstr, char *comm_fn)
{
char *c;
strcpy(comm_fn, proc_prefix);
c = comm_fn + strlen(comm_fn);
*c = '/';
c++;
strcpy(c, pidstr);
c = c + strlen(pidstr);
strcpy(c, "/comm");
}
void plist()
{
DIR *proc;
struct dirent *e;
int result;
char comm[COMM_SIZE]; /* seems to just need 16 */
char comm_fn[512];
int fd, i, n;
proc = opendir(proc_prefix);
if (proc == NULL) {
fprintf(stderr, "ERROR: Couldn't open /proc.\n");
}
for (e = readdir(proc); e != NULL; e = readdir(proc)) {
if (isdigit(e->d_name[0])) {
setup_comm_fn(e->d_name, comm_fn);
fd = open(comm_fn, O_RDONLY);
if (fd > -1) {
n = read(fd, comm, COMM_SIZE);
close(fd);
for (i=0; i < n; i++) {
if (comm[i] == '\n') {
comm[i] = '\0';
break;
}
}
printf("%s: %s\n", e->d_name, comm);
} else {
printf("%s\n", e->d_name);
}
}
}
result = closedir(proc);
if (result) {
fprintf(stderr, "ERROR: Couldn't close /proc.\n");
}
}
void signal_handler(int the_signal)
{
int pid, status;
if (the_signal == SIGHUP) {
fprintf(stderr, "Received SIGHUP.\n");
return;
}
if (the_signal != SIGCHLD) {
fprintf(stderr, "Child handler called for signal %d?!\n",
the_signal);
return;
}
pid = wait(&status);
if (pid == -1) {
/* nothing to wait for */
return;
}
if (WIFEXITED(status)) {
fprintf(stderr, "\nProcess %d exited with status %d.\n",
pid, WEXITSTATUS(status));
} else {
fprintf(stderr, "\nProcess %d aborted.\n", pid);
}
}
int main(int argc, char *argv[], char *envp[])
{
char buffer[BUFFER_SIZE];
char *args[ARR_SIZE];
char bin_fn[BUFFER_SIZE];
struct sigaction signal_handler_struct;
char *s;
int *ret_status = NULL;
size_t nargs;
pid_t pid;
int background;
char *username;
char *default_username = "UNKNOWN";
char *path;
char *default_path = "/usr/bin:/bin";
memset (&signal_handler_struct, 0, sizeof(signal_handler_struct));
signal_handler_struct.sa_handler = signal_handler;
/* signal_handler_struct.sa_flags = SA_RESTART; */
if (sigaction(SIGCHLD, &signal_handler_struct, NULL)) {
fprintf(stderr, "Couldn't register SIGCHLD handler.\n");
}
if (sigaction(SIGHUP, &signal_handler_struct, NULL)) {
fprintf(stderr, "Couldn't register SIGHUP handler.\n");
}
username = find_env("USER", default_username, envp);
path = find_env("PATH", default_path, envp);
while(1){
printf("%s $ ", username);
s = fgets(buffer, BUFFER_SIZE, stdin);
if (s == NULL) {
/* we reached EOF */
printf("\n");
if (errno == EINTR) {
fprintf(stderr, "Read system call was interrupted.\n");
} else {
exit(0);
}
}
parse_args(buffer, args, ARR_SIZE, &nargs);
if (nargs==0) continue;
if (!strcmp(args[0], "exit")) {
exit(0);
}
if (!strcmp(args[0], "plist")) {
plist();
continue;
}
background = 0;
if (strcmp(args[nargs-1], "&") == 0) {
background = 1;
args[nargs-1] = NULL;
nargs--;
}
pid = fork();
if (pid) {
if (background) {
fprintf(stderr,
"Process %d running in the background.\n",
pid);
} else {
pid = wait(ret_status);
}
} else {
find_binary(args[0], path, bin_fn, BUFFER_SIZE);
/* open files for I/O redirection */
/* assign it to 0, 1, 2, or other file descriptor */
if (execve(bin_fn, args, envp)) {
puts(strerror(errno));
exit(127);
}
}
}
return 0;
}