Compare commits

..

8 Commits

Author SHA1 Message Date
8b3246ebd9 complement 2025-12-04 11:11:48 +09:00
575d48efa6 complement 9.md 10.md 11.md 2025-11-30 21:19:04 +09:00
1518950242 some complement in 11.27 2025-11-27 14:10:51 +09:00
0a652a3214 update 8.md 9.md 2025-11-26 03:47:12 +09:00
17257c2d9f add L10.pdf L11.pdf L12.pdf 2025-11-26 03:39:07 +09:00
e1da8a7292 fic minor lab03 2025-11-25 17:27:46 +09:00
1c50fbd315 complement lab03 2025-11-24 03:03:35 +09:00
3e87f2c37d add P3.pdf 2025-11-22 19:56:06 +09:00
12 changed files with 1116 additions and 274 deletions

View File

@@ -1,18 +1,18 @@
/* /*
* tsh - A tiny shell program with job control * tsh - A tiny shell program with job control
* *
* Name: <fill in> * Name: Hajin Ju
* Student id: <fill in> * Student id: 2024062806
*/ */
#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h>
#include <string.h> #include <string.h>
#include <ctype.h>
#include <signal.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <errno.h> #include <unistd.h>
/* Misc manifest constants */ /* Misc manifest constants */
#define MAXLINE 1024 /* max line size */ #define MAXLINE 1024 /* max line size */
@@ -52,7 +52,6 @@ struct job_t { /* The job struct */
struct job_t jobs[MAXJOBS]; /* The job list */ struct job_t jobs[MAXJOBS]; /* The job list */
/* End global variables */ /* End global variables */
/* Function prototypes */ /* Function prototypes */
/*---------------------------------------------------------------------------- /*----------------------------------------------------------------------------
@@ -94,8 +93,7 @@ handler_t *Signal(int signum, handler_t *handler);
/* /*
* main - The shell's main routine * main - The shell's main routine
*/ */
int main(int argc, char **argv) int main(int argc, char **argv) {
{
char c; char c;
char cmdline[MAXLINE]; char cmdline[MAXLINE];
int emit_prompt = 1; /* emit prompt (default) */ int emit_prompt = 1; /* emit prompt (default) */
@@ -169,9 +167,41 @@ int main(int argc, char **argv)
* background children don't receive SIGINT (SIGTSTP) from the kernel * background children don't receive SIGINT (SIGTSTP) from the kernel
* when we type ctrl-c (ctrl-z) at the keyboard. * when we type ctrl-c (ctrl-z) at the keyboard.
*/ */
void eval(char *cmdline) void eval(char *cmdline) {
{ pid_t pid;
return;
sigset_t mask, prev_mask;
char *argv[MAXARGS];
int bg = parseline(cmdline, argv);
if (argv[0] && !builtin_cmd(argv)) {
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
sigprocmask(SIG_BLOCK, &mask, &prev_mask);
if ((pid = fork()) == 0) {// Child process
sigprocmask(SIG_SETMASK, &prev_mask, NULL);
setpgid(0, 0);// Set new process group
if (execve(argv[0], argv, environ) < 0) {
printf("%s: Command not found\n", argv[0]);
exit(0);
}
}
if (!bg) {// Foreground job
addjob(jobs, pid, FG, cmdline);
} else {// Background job
addjob(jobs, pid, BG, cmdline);
fprintf(stderr, "[%d] (%d) %s", pid2jid(pid), pid, cmdline);
}
sigprocmask(SIG_SETMASK, &prev_mask, NULL);
if (!bg) waitfg(pid);
}
} }
/* /*
@@ -181,8 +211,7 @@ void eval(char *cmdline)
* argument. Return true if the user has requested a BG job, false if * argument. Return true if the user has requested a BG job, false if
* the user has requested a FG job. * the user has requested a FG job.
*/ */
int parseline(const char *cmdline, char **argv) int parseline(const char *cmdline, char **argv) {
{
static char array[MAXLINE]; /* holds local copy of command line */ static char array[MAXLINE]; /* holds local copy of command line */
char *buf = array; /* ptr that traverses command line */ char *buf = array; /* ptr that traverses command line */
char *delim; /* points to first space delimiter */ char *delim; /* points to first space delimiter */
@@ -199,8 +228,7 @@ int parseline(const char *cmdline, char **argv)
if (*buf == '\'') { if (*buf == '\'') {
buf++; buf++;
delim = strchr(buf, '\''); delim = strchr(buf, '\'');
} } else {
else {
delim = strchr(buf, ' '); delim = strchr(buf, ' ');
} }
@@ -214,8 +242,7 @@ int parseline(const char *cmdline, char **argv)
if (*buf == '\'') { if (*buf == '\'') {
buf++; buf++;
delim = strchr(buf, '\''); delim = strchr(buf, '\'');
} } else {
else {
delim = strchr(buf, ' '); delim = strchr(buf, ' ');
} }
} }
@@ -235,24 +262,81 @@ int parseline(const char *cmdline, char **argv)
* builtin_cmd - If the user has typed a built-in command then execute * builtin_cmd - If the user has typed a built-in command then execute
* it immediately. * it immediately.
*/ */
int builtin_cmd(char **argv) int builtin_cmd(char **argv) {
{ // Built-in-commands: jobs, bg, fg, quit
return 0; /* not a builtin command */
if (!strcmp(argv[0], "quit")) {
exit(0);
} else if (!strcmp(argv[0], "jobs")) {
listjobs(jobs);
return 1;
} else if (!strcmp(argv[0], "bg") || !strcmp(argv[0], "fg")) {
do_bgfg(argv);
return 1;
}
return 0;
} }
/* /*
* do_bgfg - Execute the builtin bg and fg commands * do_bgfg - Execute the builtin bg and fg commands
*/ */
void do_bgfg(char **argv) void do_bgfg(char **argv) {
{ struct job_t *job = NULL;
pid_t pid = 0;
int jid = 0;
if (argv[1] == NULL) {
printf("%s command requires PID or %%jobid argument\n", argv[0]);
return; return;
} }
if (argv[1][0] == '%') {
if (sscanf(&argv[1][1], "%d", &jid) != 1) {
printf("%s: argument must be a PID or %%jobid\n", argv[0]);
return;
}
job = getjobjid(jobs, jid);
if (job == NULL) {
printf("%s: No such job\n", argv[1]);
return;
}
} else if (isdigit((unsigned char) argv[1][0])) {
if (sscanf(argv[1], "%d", &pid) != 1) {
printf("%s: argument must be a PID or %%jobid\n", argv[0]);
return;
}
job = getjobpid(jobs, pid);
if (job == NULL) {
printf("(%s): No such process\n", argv[1]);
return;
}
} else {
printf("%s: argument must be a PID or %%jobid\n", argv[0]);
return;
}
pid = job->pid;
if (!strcmp(argv[0], "bg")) {
job->state = BG;
if (kill(-pid, SIGCONT) < 0)
unix_error("kill (SIGCONT) error");
printf("[%d] (%d) %s", job->jid, job->pid, job->cmdline);
} else { /* fg */
job->state = FG;
if (kill(-pid, SIGCONT) < 0)
unix_error("kill (SIGCONT) error");
waitfg(pid);
}
}
/* /*
* waitfg - Block until process pid is no longer the foreground process * waitfg - Block until process pid is no longer the foreground process
*/ */
void waitfg(pid_t pid) void waitfg(pid_t pid) {
{ while (pid == fgpid(jobs)) {
sleep(1);
}
return; return;
} }
@@ -267,9 +351,27 @@ void waitfg(pid_t pid)
* available zombie children, but doesn't wait for any other * available zombie children, but doesn't wait for any other
* currently running children to terminate. * currently running children to terminate.
*/ */
void sigchld_handler(int sig) void sigchld_handler(int sig) {
{ int olderrno = errno;
return; pid_t pid;
int status;
while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
if (WIFEXITED(status)) {
deletejob(jobs, pid);
} else if (WIFSIGNALED(status)) {
fprintf(stderr, "Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid, WTERMSIG(status));
deletejob(jobs, pid);
} else if (WIFSTOPPED(status)) {
struct job_t *job = getjobpid(jobs, pid);
if (job != NULL) {
job->state = ST;
fprintf(stderr, "Job [%d] (%d) stopped by signal %d\n", job->jid, pid, WSTOPSIG(status));
}
}
}
errno = olderrno;
} }
/* /*
@@ -277,9 +379,11 @@ void sigchld_handler(int sig)
* user types ctrl-c at the keyboard. Catch it and send it along * user types ctrl-c at the keyboard. Catch it and send it along
* to the foreground job. * to the foreground job.
*/ */
void sigint_handler(int sig) void sigint_handler(int sig) {
{ pid_t pid;
return; if ((pid = fgpid(jobs)) > 0) {
kill(-pid, SIGINT);
}
} }
/* /*
@@ -287,9 +391,11 @@ void sigint_handler(int sig)
* the user types ctrl-z at the keyboard. Catch it and suspend the * the user types ctrl-z at the keyboard. Catch it and suspend the
* foreground job by sending it a SIGTSTP. * foreground job by sending it a SIGTSTP.
*/ */
void sigtstp_handler(int sig) void sigtstp_handler(int sig) {
{ pid_t pid;
return; if ((pid = fgpid(jobs)) > 0) {
kill(-pid, SIGTSTP);
}
} }
/********************* /*********************
@@ -301,8 +407,7 @@ void sigtstp_handler(int sig)
**********************************************/ **********************************************/
/* clearjob - Clear the entries in a job struct */ /* clearjob - Clear the entries in a job struct */
void clearjob(struct job_t *job) void clearjob(struct job_t *job) {
{
job->pid = 0; job->pid = 0;
job->jid = 0; job->jid = 0;
job->state = UNDEF; job->state = UNDEF;
@@ -310,8 +415,7 @@ void clearjob(struct job_t *job)
} }
/* initjobs - Initialize the job list */ /* initjobs - Initialize the job list */
void initjobs(struct job_t *jobs) void initjobs(struct job_t *jobs) {
{
int i; int i;
for (i = 0; i < MAXJOBS; i++) for (i = 0; i < MAXJOBS; i++)
@@ -319,8 +423,7 @@ void initjobs(struct job_t *jobs)
} }
/* maxjid - Returns largest allocated job ID */ /* maxjid - Returns largest allocated job ID */
int maxjid(struct job_t *jobs) int maxjid(struct job_t *jobs) {
{
int i, max = 0; int i, max = 0;
for (i = 0; i < MAXJOBS; i++) for (i = 0; i < MAXJOBS; i++)
@@ -330,8 +433,7 @@ int maxjid(struct job_t *jobs)
} }
/* addjob - Add a job to the job list */ /* addjob - Add a job to the job list */
int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline) int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline) {
{
int i; int i;
if (pid < 1) if (pid < 1)
@@ -356,8 +458,7 @@ int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline)
} }
/* deletejob - Delete a job whose PID=pid from the job list */ /* deletejob - Delete a job whose PID=pid from the job list */
int deletejob(struct job_t *jobs, pid_t pid) int deletejob(struct job_t *jobs, pid_t pid) {
{
int i; int i;
if (pid < 1) if (pid < 1)
@@ -374,8 +475,7 @@ int deletejob(struct job_t *jobs, pid_t pid)
} }
/* fgpid - Return PID of current foreground job, 0 if no such job */ /* fgpid - Return PID of current foreground job, 0 if no such job */
pid_t fgpid(struct job_t *jobs) pid_t fgpid(struct job_t *jobs) {
{
int i; int i;
for (i = 0; i < MAXJOBS; i++) for (i = 0; i < MAXJOBS; i++)
@@ -385,8 +485,7 @@ pid_t fgpid(struct job_t *jobs)
} }
/* getjobpid - Find a job (by PID) on the job list */ /* getjobpid - Find a job (by PID) on the job list */
struct job_t *getjobpid(struct job_t *jobs, pid_t pid) struct job_t *getjobpid(struct job_t *jobs, pid_t pid) {
{
int i; int i;
if (pid < 1) if (pid < 1)
@@ -398,8 +497,7 @@ struct job_t *getjobpid(struct job_t *jobs, pid_t pid)
} }
/* getjobjid - Find a job (by JID) on the job list */ /* getjobjid - Find a job (by JID) on the job list */
struct job_t *getjobjid(struct job_t *jobs, int jid) struct job_t *getjobjid(struct job_t *jobs, int jid) {
{
int i; int i;
if (jid < 1) if (jid < 1)
@@ -411,8 +509,7 @@ struct job_t *getjobjid(struct job_t *jobs, int jid)
} }
/* pid2jid - Map process ID to job ID */ /* pid2jid - Map process ID to job ID */
int pid2jid(pid_t pid) int pid2jid(pid_t pid) {
{
int i; int i;
if (pid < 1) if (pid < 1)
@@ -425,8 +522,7 @@ int pid2jid(pid_t pid)
} }
/* listjobs - Print the job list */ /* listjobs - Print the job list */
void listjobs(struct job_t *jobs) void listjobs(struct job_t *jobs) {
{
int i; int i;
for (i = 0; i < MAXJOBS; i++) { for (i = 0; i < MAXJOBS; i++) {
@@ -454,7 +550,6 @@ void listjobs(struct job_t *jobs)
* end job list helper routines * end job list helper routines
******************************/ ******************************/
/*********************** /***********************
* Other helper routines * Other helper routines
***********************/ ***********************/
@@ -462,8 +557,7 @@ void listjobs(struct job_t *jobs)
/* /*
* usage - print a help message * usage - print a help message
*/ */
void usage(void) void usage(void) {
{
printf("Usage: shell [-hvp]\n"); printf("Usage: shell [-hvp]\n");
printf(" -h print this message\n"); printf(" -h print this message\n");
printf(" -v print additional diagnostic information\n"); printf(" -v print additional diagnostic information\n");
@@ -474,8 +568,7 @@ void usage(void)
/* /*
* unix_error - unix-style error routine * unix_error - unix-style error routine
*/ */
void unix_error(char *msg) void unix_error(char *msg) {
{
fprintf(stdout, "%s: %s\n", msg, strerror(errno)); fprintf(stdout, "%s: %s\n", msg, strerror(errno));
exit(1); exit(1);
} }
@@ -483,8 +576,7 @@ void unix_error(char *msg)
/* /*
* app_error - application-style error routine * app_error - application-style error routine
*/ */
void app_error(char *msg) void app_error(char *msg) {
{
fprintf(stdout, "%s\n", msg); fprintf(stdout, "%s\n", msg);
exit(1); exit(1);
} }
@@ -492,8 +584,7 @@ void app_error(char *msg)
/* /*
* Signal - wrapper for the sigaction function * Signal - wrapper for the sigaction function
*/ */
handler_t *Signal(int signum, handler_t *handler) handler_t *Signal(int signum, handler_t *handler) {
{
struct sigaction action, old_action; struct sigaction action, old_action;
action.sa_handler = handler; action.sa_handler = handler;
@@ -509,8 +600,7 @@ handler_t *Signal(int signum, handler_t *handler)
* sigquit_handler - The driver program can gracefully terminate the * sigquit_handler - The driver program can gracefully terminate the
* child shell by sending it a SIGQUIT signal. * child shell by sending it a SIGQUIT signal.
*/ */
void sigquit_handler(int sig) void sigquit_handler(int sig) {
{
printf("Terminating after receipt of SIGQUIT signal\n"); printf("Terminating after receipt of SIGQUIT signal\n");
exit(1); exit(1);
} }

218
notes/10.md Normal file
View File

@@ -0,0 +1,218 @@
# Signals and Nonlocal Jumps
## Shell
Linux Process Hierachy: all is rooted from `init` or `systemd` (pid = 1).
A shell is an application that runs programs on behalf of the user.
* `sh` Original Unix shell
* `csh` BSD Unix C Shell
* `bash` GNU Bourne-Again Shell (default Linux shell)
## Signals
All signal is a small message that notifies a process that an event has occured in the system.
Signal is sent by kernel.
Signal is identified by small integer ID (1-30).
Only information in a signal is **its ID** and **the fact that it arrived**.
| ID | name | Default Action | Corresponding Event |
| --- | ------- | -------------- | ----------------------------------- |
| 2 | SIGINT | terminate | user types `Ctrl+C` |
| 9 | SIGKILL | terminate | kill program |
| 11 | SIGSEGV | terminate | segmentation violation |
| 14 | SIGALRM | terminate | timer expired |
| 17 | SIGCHLD | ignore | child process stopped or terminated |
Sending a signal is essentially **updating some state** in the context of the destination process.
Kernel sends a signal for one of the following reasons:
A destination process receives a signal when it is forced by the kernel and reacts some way to the signal.
* Ignore
* Terminate (with optional core-dump)
* Catch
* executing a user-level handler function called **signal handler**.
### Pending & Blocking
A signal is **pending** if sent but not yet received. There can be at most one pending signal of any particular type because signals are **not queued**. Signal is managed by bitmask. So subsequent signals of same type is discarded.
* sets `k` bit when delivered
* clears `k` bit when received
Blocking signals: a process can block the receipt of certain signals. It is not received until unblocked. It is also managed by bitmask.
* can be set and cleared by using `sigprocmask`
### Receiving
Suppose kernel is returning from an exception handler and is ready to pass control to process `p`
Kernel computes `pnb = pending(p) & ~blocked(p)`.
* if `pnb == 0`, no unblocked pending signals for `p`, so just return to `p` normally.
* otherwise, choose least nonzero bit `k` in `pnb` and force process `p` to receive signal `k`
* The receipt of the signal triggers action by `p`
* Repeat for all nonzero `k`
### Default Action & Signal Handler
Each signal type has a predefined default action:
* terminates
* stops until restarted by a SIGCONT signal
* ignores
```c
handler_t * signal(int signum, handler_t *handler);
```
* `SIG_IGN` ignore
* `SIG_DFL` revert to default action
* handler function pointer
### Blocking and Unblocking
* Implicit blocking
Kernel blocks any pending signals of type currently being handled.
* Explicit blocking `sigprocmask()`
Supporting functions:
* `sigemptyset()` create empty set
* `sigfillset()` add every signal number to set
* `sigaddset()` add signal number to set
* `sigdelset()` delete signal number from set
```c
sigset_t mask, prev_mask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigprocmaksk(SIG_BLOCK, &mask, &prev_mask);
// critical section
sigprocmask(SIG_SETMASK, &prev_mask, NULL);
```
### Safe Signal Handling
Handlers are concurrent with main program and share global data structures. This can lead to troubles.
Some guidlines help to avoid troubles:
0. Keep you handlers as simple as possible
1. Call only **async-signal-safe** functions in your handlers
2. Save and restore `errno` on entry and exit
3. Protect accesses to shared data structure by temporarily blocking all signals
4. Declare global variables as `volatile`
5. Declare global flags as `volatile sig_atomic_t`
#### Async-Signal-Safety
A function satisfying either two conditions(below) is **Async-Signal-Safety function**:
* **reentrant**: not modifying global data (or static data), only use local data(stack frame).
* **non-interruptible**: blocking singnals during modifying global data.
POSIX guarantees that the 117 functions to be async-signal-safe, including:
* `_exit`, `write`, `wait`, `waitpid`, `sleep`, `kill`
**IMPORTANT**: `printf`, `malloc`, `sprintf`, `eixt` are **NOT** async-signal-safe.
### Correct Signal Handling Example for reaping multiple childs
```c
void child_handler(int sig) {
int olderrno = errno; pid_t pid;
while ((pid = wait(NULL)) > 0) {
ccount --;
sio_puts("Handler reaped child ");
sio_putl((long) pid);
sio_puts(" \n");
}
if (errno != ECHILD) {
sio_error("wait error");
}
errno = olderrno;
}
```
### Portable Signal Handling
Different UNIX systems have different signal handling sematnics.
So `sigaction` is introduced to provide a portable interface for signal handling.
```c
handler_t * Signal(int signum, handler_t *handler) {
struct sigaction action, old_action;
action.sa_handler = handler;
sigemptyset(&action.sa_mask); // block sigs of type being handled
action.sa_flags = SA_RESTART; // restart syscalls if possible
if (sigaction(signum, &action, &old_action) < 0)
unix_error("Signal error");
return (old_action.sa_handler);
}
```
### Concurrent Flows to lead Races
```c
void handler(int sig) {
int olderrno = errno;
pid_t pid;
while ((pid = waitpid(-1, NULL, 0)) > 0) {
deletejob(pid);
}
if (errno != ECHILD) sio_error("waitpid error");
errno = olderrno;
}
int main() {
while (1) {
if ((pid = Fork()) == 0) {
execve("/bin/date", argv, NULL);
}
}
addjob(pid);
}
```
In above example, the ecf flow can be executed before the deletejob; thus it cannot be deleted.
Therefore, we need to synchronize the two concurrent flows.
```c
void handler(int sig) {
int olderrno = errno;
sigset_t mask_all, prev_all;
pid_t pid;
sigfillset(&mask_all);
while ((pid = waitpid(-1, NULL, 0)) > 0) {
sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
deletejob(pid);
sigprocmask(SIG_SETMASK, &prev_all, NULL);
}
if (errno != ECHILD) sio_error("waitpid error");
errno = olderrno;
}
int main() {
pid_t pid;
sigset_t mask_all, prev_one;
sigfillset(&mask_all);
signal(SIGCHLD, handler);
while (1) {
sigprocmask(SIG_BLOCK, &mask_all, &prev_one); // Block SIGCHLD
if ((pid = Fork()) == 0) {
sigprocmask(SIG_SETMASK, &prev_one, NULL); // Unblock SIGCHLD
execve("/bin/date", argv, NULL);
}
sigprocmask(SIG_BLOCK, &mask_one, NULL);
addjob(pid);
sigprocmask(SIG_SETMASK, &prev_one, NULL);
}
}
```

223
notes/11.md Normal file
View File

@@ -0,0 +1,223 @@
# System-Level I/O
IO is the process of coping data between the main memory and external devices.
In a Linux, **file** is a sequence of $m$ bytes.
All I/O devices are represented as files. Even the kernel is represented as a file.
## Unix IO
* `open` and `close`
* `read` and `write`
* `lseek` changing **current file position**
### File Types
* Regular files
* Directory
* Socket
* ...
#### Regular Files
A regular file contains arbitary data.
For example **text file** is a sequence of text lines. EOL is different in different OS: (`\n` in Unix, `\r\n` in Windows & Internet).
#### Directories
Directory contains an array of links. Least two links are `.`(itself) and `..`(parent dir).
* `ls`
* `mkdir`
* `rmdir`
All files are orgnaized as a hierarchy anchored by root dir named `/`.
Kernel maintains curr working dir (cwd) for each process that modified using the `cd` command.
Path names
* Absolute `/home/yenru0/workspace`
* Relative `../workspace`
### Open & Close & Read & Write
```c
int fd;
if ((fd = open("file.txt", O_RDONLY)) < 0) {
perror("open");
exit(1);
}
```
* `open` returns a non-negative integer called **file descriptor** (fd).
* `fd == -1` indicates an error.
* `0`: stdin, `1`: stdout, `2`: stderr
```c
int fd; int ret;
if ((ret = close(fd)) < 0) {
perror("close");
exit(1);
}
```
Closing an already closed can lead to a disastrous situation in threaded programs. So always check the return code.
```c
char buf[512];
nbytes = read(fd, buf, sizeof(buf));
```
```c
ssize_t read(int fd, void *usrbuf, size_t n);
```
read returns the number of bytes read from the `fd` into `buf`.
`ssize_t` is signed version of `size_t`.
If `read` returns negative value, an error occurred.
```c
ssize_t write(int fd, const void *usrbuf, size_t n);
```
If `write` returns negative value, an error occurred.
### Short Counts
It means that `read` or `write` transfers fewer bytes than requested. It can occur in these situations:
* `EOF` on reads
* Reading text lines from an terminal
* Reading from a network socket
Never occurs:
* Reading from disk files (except for `EOF`)
* Writing to disk files
## RIO pakcage
RIO is a set of wrappers efficient and robust I/O functions subject to **short couunts**.
* unbuffered RIO functions `rio_readn`, `rio_writen`
* buffered RIO functions `rio_readnb`, `rio_readlineb`
* buffered RIO functions are thread-safe and can be interleaved arbitrarily on the same descriptor.
### Buffered RIO
To read efficiently from a file, RIO uses partially cached in an interal memory buffer. (`rio_t` structure)
For reading from file, Buffer has buffered portion of already read and unread data. It is refilled automatically by `rio_readnb` and `rio_readlineb` as needed. This is **partially cached**.
```c
typedef struct {
int rio_fd; // Descriptor for this internal buf
int rio_cnt; // Unread bytes in internal buf
char *rio_bufptr; // Next unread byte in internal buf
char rio_buf[RIO_BUFSIZE]; // Internal buffer
} rio_t;
```
example:
```c
int main(int argc, char **argv) {
int n; rio_t rio; char buf[MAXLINE];
rio_readinitb(&rio, STDIN_FILENO);
while ((n = rio_readlineb(&rio, buf, MAXLINE)) != 0) {
rio_writen(STDOUT_FILENO, buf, n);
}
exit(0);
}
```
## Metadata
Metadata is data about data. (file access, file size, file type)
* Per-process metadata
* when a process opens a file, the kernel creates an entry in a per-process table called the **file descriptor table**
* Per-file metadata
* can be accessed using `stat` system call
```c
struct stat {
dev_t st_dev; // ID of device containing file
ino_t st_ino; // inode number
mode_t st_mode; // protection
nlink_t st_nlink; // number of hard links
uid_t st_uid; // user ID of owner
gid_t st_gid; // group ID of owner
dev_t st_rdev; // device ID (if special file)
off_t st_size; // total size, in bytes
blksize_t st_blksize; // blocksize for filesystem I/O
blkcnt_t st_blocks; // number of 512B blocks allocated
time_t st_atime; // time of last access
time_t st_mtime; // time of last modification
time_t st_ctime; // time of last status change
};
```
### How to Kernel represents Open Files
* Descriptor table(per-process)
* Open file table(shared by all processes)
* v-node table(shared by all processes)
When a process opens a file, the kernel creates an entry in the per-process file descriptor table. Each entry contains a pointer to an entry in the open file table. Each entry in the open file table contains a pointer to an entry in the v-node table.
When a `fork` calls: the child process inherits copies of the parent's file descriptors. And the entry points to open file table's entry increasing `refcnt`.
### IO redirection
for example: `ls > foo.txt`
Answer: `dup2(oldfd, newfd)` it means copies descriptor table entry `oldfd` to `newfd`
so `dup2(4, 1)` makes `stdout` point to the same open file as descriptor 4.
## stdio
The C standard library (`libc.so`) provides a collection of higher-level standard I/O functions.
* `fopen`, `fclose`, `fread`, `fwrite`, `fgets`, `fputs`, `fscanf`, `fprintf`
`stdio` models open files as **streams**, which are abstraction for a file descriptor and a buffer in memory.
```c
extern FILE * stdin;
extern FILE * stdout;
extern FILE * stderr;
```
### Buffered I/O
Application often read and write one char at a time. However, UNIX System calls `read` and `write` calls expensive. So we need buffered read & write; use unix `read` & `write` to **get a block of data into a buffer**. And then user application reads/writes **one char at a time from/to the buffer**; it is efficient because it is simple memory access.
`stdio` uses buffer. `printf` is not write immediately to the `stdout` file; it is stored in a buffer. And then when `fflush(stdout)`, `exit`, or return from `main`, the buffer is flushed to the file using `write` syscall.
## Remark
* UNIX IO
* RIO package
* stdio
When to use
* stdio: disk or terminal files
* unix io: signal handlers, or when you need absolute high performance
* RIO: networking
### Binary
DO NOT USE:
* text oriented I/O: `fgets`, `scanf`, `rio_readlineb`
* string functions: `strlen`, `strcpy`, `strcat`, `strcmp`

14
notes/12.md Normal file
View File

@@ -0,0 +1,14 @@
# Memory
## Memory Hierachies
Storage media typically trades off speed with capacity
## Locality
* **Temporal Locality**: recently referenced items are likely to be referenced again soon
* **Spaitial Locality**: items with nearby addresses are tend to be referenced close together in time
## Cache Concepts

25
notes/8.md Normal file
View File

@@ -0,0 +1,25 @@
# Linking
When Calling Functions in Other Files, We Need to Link Them Together. Because Caller do know how to pass data by calling convention, but do know where the callee is located in memory.
## Why Linker Needed?
* Modularity
* Efficiency
* Time: Seperate Compiliation
* Space: Libraries
## What do Linker do?
1. Symbol Resolution
2. Reloacation
## 3 Types of Object
1. Relocatable Object File (`*.o`)
2. Executable File (`a.out`)
3. Shared Object File (`*.so`)
## ELF* Executable and Linkable Format

254
notes/9.md Normal file
View File

@@ -0,0 +1,254 @@
# Exception
An Exception is a **transfer of control** to the OS kernel in response to some event(like div by 0, overflow, ctrl+C). that is change in processor state.
**Exception Tables**
Each type of event has an unique exc number `k`: `k` is index into **exception table(interrupt vector table)**
Handler `k` is called each time exception `k` occurs.
## Asyncronous exceptions
It is caused by external events. Handler returns to next instruction.
for example: Timer interrupt, I/O interrupt.
## Syncronous exceptions
It is caused by events that occur as a result of exxecuting an current instruction.
* Traps
* Intentional like procedure calls (e.g. system calls)
* Returns to next instruction
* Faults
* Unintentional but possibly recoverable (e.g. page fault(recoverable), protection fault(not recoverable), floating point exception)
* re-executes by kernel or aborts
* Aborts
* Unintentional and not recoverable (e.g. illegal instruction, parity error, machine check)
* Aborts the program
## System Calls
Each x86-64 system call has a unique syscall number.
| Num | Name | Desc |
| --- | ------ | --------------- |
| 0 | read | read file |
| 1 | write | write file |
| 2 | open | open file |
| 3 | close | close file |
| 4 | stat | get file status |
| 57 | fork | create process |
| 59 | execve | execute program |
| 62 | kill | send signal |
## Fault Example
### Page Fault
```c
int a[1000];
main() {
a[500] = 13;
}
```
In this situation, a page containing `a[500]` is currently on disk, so page fault occurs, CPU cannot find the data in physical RAM. So kernel copy page from disk to memory, and return and re-executes the instruction `movl`
### Invalid Memory Ref
```c
int a[1000];
main() {
a[5000] = 13;
}
```
In this situation, address `a[5000]` is invalid, so protection fault occurs, kernel terminates the program by sending `SIGSEGV` signal to the user process. Then user process exits with error code `Segmentation Fault`.
## Process
An instance of a running program.
Process provides each program with two key abstractions:
* Logical control flow
* Each program seems to have **exclusive use of the CPU** provided by **context switching** of the kernel
* Private address space
* Each program seems to have **exclusive use of main memory** provided by **virtual memory** of the kernel
But in reality, computer runs multiple processes simultaneously by time-sharing CPU and multiplexing memory.
### Multiprocessing
Single processor executes multiple processes concurrently. process execution interleaved by time-slicing. Address spaces managed by virtual memory system. And register values for non-executing processes saved in memory.
Multicore processor share main memory each can execute a separate process. scheduling of processors onto cores done by kernel.
#### Concurrent Processes
Concurrency is **not at the exact same time**.
Two processes are **concurrent** if their flows **overlap in time**. Otherwise, they are **sequential**.
Control flows for concurrent processes are pysically disjoint in time. But user think that they are logically running in parallel.
* Execution time of instruction may vary because of the Nondeterminism of the System: OS scheduling, Interrupts, Cache miss or Page fault, I/O device delays.
#### Context Switching
Prcess are managed by a shared chunk of memory-resident OS code called the **kernel**.
What is important is that the kernel is **not a seprate process**. It is invoked by processes when they need OS services, or when exceptions occur. That is Part of the processor.
Control flow passes via a context switching.
## Syscall Error Handling
On error, Linux sys level function typically returns `-1` and sets the global variable `errno` to indicate the specific error.
Hard and fast rule:
* You must check the return status of every system-level function
* Only exception is the handful of functions that return void
Error reporting functions:
```c
void unix_error(char *msg) {
fprintf(stderr, "%s: %s\n", msg, strerror(errno));
exit(0);
}
```
Error handling wrappers:
```c
pid_t Fork(void) {
pid_t pid;
if ((pid = fork()) < 0) unix_error("Fork error");
return pid;
}
```
## Creating and Terminating Processes
* `pid_t getpid(void)` returnes pid of current process
* `pid_t getppid(void)` returns pid of parent process
We can think of a process as being in one of three states:
* Running
* Executing or waiting to be executed
* Stopped
* Process execution is suspended and will not be scheduled until futher notice
* Terminated
* Stopped permanently
### Terminating Processes
1. `return` from `main`
2. call `exit(status)` function
3. Receive a termination signal
```c
void exit(int status);
```
### Creating Process
Parent process can creates a new running child process by calling `fork()` system call.
```c
int fork(void);
```
it returns `0` to the newly created child, and returns **child's pid** to the parent.
Child is almost identical to parent: child get an identical copy of the parent's virtual address space, file descriptors, and process state.
But child has its own unique pid.
```c {cmd=gcc, args=[-O2 -x c $input_file -o 9_1.out]}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
pid_t pid;
int x = 1;
pid = fork();
if (pid == 0) { /* Child */
printf("child: x=%d\n", ++x);
exit(0);
}
printf("parent: x=%d\n", --x);
exit(0);
}
```
```sh {cmd hide}
while ! [ -r 9_1.out ]; do sleep .1; done; ./9_1.out
```
Concurrent execution of parent and child processes. `fork` duplicates but separates address space.
File descriptors are shared between parent and child like `stdout`, `stderr`.
Modeling fork with Process Graphs
* Each vertex represents a process state
* Directive Edges represent is ordering of execution.
* Edge can be labeled with current value of variables
Any topological sort of the graph corresponds to a feasible total ordering.
### Reaping Child Processes
When process terminates, it still consumes system resources. It is called a "zombie".
**"Reaping"** is performed by the parent by using `wait` or `waitpid`. And then parent is given exit status information. Finally kernel then deletes zombie child process.
If any parent terminates without reaping a child, then the orphaned child will be reaped by the `init` process (pid 1).
However, long-running processes should reap their children to avoid accumulating zombies.
```c {cmd=gcc, args=[-O2 -x c $input_file -o 9_2.out]}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
if(fork() == 0) {
printf("Terminating child process\n");
exit(0);
} else {
printf("Running Parent\n");
sleep(1);
printf("Parent exiting\n");
}
}
```
```sh {cmd hide}
while ! [ -r 9_2.out ]; do sleep .1; done; ./9_2.out & ps -ef | grep "9_2.out" | grep -v grep;
```
```c
pid_t wait(int * child_status);
```
`wait` suspends current process until **one of its children** terminates.
* **return value**: pid of terminated child
* **child_status**: if not `NULL`, the integer points will be set to the termination status(reason and exit status) of the child.
* It can be checked by using macros defined in `wait.h`
## execve
```c
int execve(const char *filename, char *const argv[], char *envp[]);
```
Load and runs in the current process.
* `filename`: path of the executable file
* `argv`: argument list
* `envp`: environment variables "name=value"
* `getenv`, `putenv`, `printenv`
It does **overwrite code, data, stack**. retain only **pid, file descriptors, signal context**.
Called **once and never returns on success**.

BIN
pdf/L10.pdf (Stored with Git LFS) Normal file

Binary file not shown.

BIN
pdf/L11.pdf (Stored with Git LFS) Normal file

Binary file not shown.

BIN
pdf/L12.pdf (Stored with Git LFS) Normal file

Binary file not shown.

BIN
pdf/L13.pdf (Stored with Git LFS) Normal file

Binary file not shown.

BIN
pdf/L14.pdf (Stored with Git LFS) Normal file

Binary file not shown.

BIN
pdf/P3.pdf (Stored with Git LFS) Normal file

Binary file not shown.