Compare commits

..

33 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
4f21b2e69f Merge commit 'e2a5e6750a362e0fd8c89f3d1d40a623ba6d1fd6' 2025-11-21 16:23:27 +09:00
Woong Sul
e2a5e6750a Lab03 assigned 2025-11-12 02:58:26 +00:00
646ecc0931 fix pdf ordering and add L8.pdf L9.pdf and a missing one 2025-11-06 14:36:06 +09:00
99381bb361 build notes 2025-10-22 03:19:36 +09:00
d5c9dcf735 update notes style 2025-10-22 03:19:07 +09:00
bf60eeff7e construct notes build system 2025-10-22 03:18:51 +09:00
6f41ae9730 update 3.md and 4.md 2025-10-22 03:04:38 +09:00
a86855a11c add L6.pdf 2025-10-22 01:50:32 +09:00
ab6ccd8cf6 lab02/defuse the bomb 2025-10-21 21:36:20 +09:00
375aac730e lab02/defuse the bomb 2025-10-21 21:36:20 +09:00
1122a9c541 initializing Makefile 2025-10-20 22:19:26 +09:00
5b96f9961d initializing Makefile 2025-10-20 22:19:26 +09:00
42efa4341b fix 3.md 2025-10-20 21:58:00 +09:00
99628ab532 fix 3.md and update Makefile 2025-10-20 21:54:07 +09:00
b04b68a339 delete illegal files 2025-10-15 20:22:23 +09:00
05332b0146 Merge commit 'c50fc23a19060f984c8219b98e196a662df888ee' 2025-10-15 20:20:33 +09:00
Woong Sul
2dfb064afb Lab02 assigned 2025-10-15 20:19:19 +09:00
Woong Sul
c50fc23a19 Lab02 assigned 2025-10-14 03:48:15 +00:00
4c2c0363b3 complement many 2025-10-11 08:39:17 +09:00
3d651c4a8a add pdf L4 L5 2025-10-11 08:38:50 +09:00
1c72fe6940 some minor changes for datalab01/bits.c 2025-10-01 13:31:09 +09:00
85db177ba3 some minor changes for datalab01/bits.c 2025-10-01 13:31:09 +09:00
8813f46c6c update datalab01/bits.c 2025-09-29 15:45:56 +09:00
b189afd37a update datalab01/bits.c 2025-09-29 15:45:56 +09:00
29734eb1e2 some change in lab1 2025-09-26 01:18:42 +09:00
66 changed files with 10883 additions and 15 deletions

15
.crossnote/config.js Normal file
View File

@@ -0,0 +1,15 @@
({
katexConfig: {
"macros": {}
},
mathjaxConfig: {
"tex": {},
"options": {},
"loader": {}
},
mermaidConfig: {
"startOnLoad": false
},
})

6
.crossnote/head.html Normal file
View File

@@ -0,0 +1,6 @@
<!-- The content below will be included at the end of the <head> element. -->
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function () {
// your code here
});
</script>

12
.crossnote/parser.js Normal file
View File

@@ -0,0 +1,12 @@
({
// Please visit the URL below for more information:
// https://shd101wyy.github.io/markdown-preview-enhanced/#/extend-parser
onWillParseMarkdown: async function(markdown) {
return markdown;
},
onDidParseMarkdown: async function(html) {
return html;
},
})

25
.crossnote/style.less Normal file
View File

@@ -0,0 +1,25 @@
/* Please visit the URL below for more information: */
/* https://shd101wyy.github.io/markdown-preview-enhanced/#/customize-css */
.markdown-preview.markdown-preview {
// modify your style here
// eg: background-color: blue;
font-size: 10pt;
.mermaid {
background-color: white;
}
p {
margin-bottom: 0.0pt;
}
h1, h2, h3, h4, h5, h6 {
margin-bottom: 1pt;
margin-top: 2pt;
}
div[data-cmd="sh"] {
font-size: 8pt;
}
font-family: NanumGothic;
}

2
.gitattributes vendored
View File

@@ -1,3 +1,5 @@
* text=auto eol=lf
[attr]lfs-file filter=lfs diff=lfs merge=lfs -text
"*.pdf" lfs-file

8
.gitignore vendored
View File

@@ -1,2 +1,8 @@
.vscode
.venv
.venv
*.out
notes/*.o
notes/*.s
node_modules/

9
Makefile Normal file
View File

@@ -0,0 +1,9 @@
all: clean build
clean:
rm -f ./notes/*.out
rm -f ./notes/*.o
rm -f ./notes/*.s
build:
node build.js

BIN
assets/3_1stackframe.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

43
build.js Normal file
View File

@@ -0,0 +1,43 @@
const { Notebook } = require("crossnote")
const path = require('path')
const fs = require('fs')
async function main() {
const notebook = await Notebook.init(
{
notebookPath: path.resolve(''),
config: {
previewTheme: 'github-light.css',
mathRenderingOption: 'KaTeX',
codeBlockTheme: 'github.css',
printBackground: true,
enableScriptExecution: true,
chromePath: '/usr/bin/google-chrome-stable',
},
}
);
const files = fs.readdirSync(path.resolve('notes')).filter(file => {
return path.extname(file).toLowerCase() == '.md';
});
files.forEach(async (file) => {
const fileBase = "notes/" +path.basename(file);
const fileName = path.basename(file, ".md")
console.log("found " + fileBase);
const engine = notebook.getNoteMarkdownEngine(fileBase);
await engine.chromeExport({ runAllCodeChunks: true });
const old = path.resolve('notes', fileName + ".pdf");
const dest = path.resolve('out', 'notes', fileName + ".pdf");
fs.rename(old, dest, (err) => {
if (err) throw err;
console.log(fileName + ".pdf" + " moved to out completed");
});;
});
}
main();

View File

@@ -187,7 +187,7 @@ int bitAnd(int x, int y) {
* Rating: 1
*/
int bitXor(int x, int y) {
return x & ~y | ~x & y;
return ~((~(x & ~y)) & (~(~x & y)));
}
/*
* isTmin - returns 1 if x is the minimum, two's complement number,
@@ -196,8 +196,8 @@ int bitXor(int x, int y) {
* Max ops: 10
* Rating: 1
*/
int isTmin(int x) {
return !!x & !(x ^ (~x + 1));
volatile int isTmin(int x) {
return !!x & (!(((~(x) + 1) ^ x)));
}
//2
/*
@@ -208,7 +208,7 @@ int isTmin(int x) {
* Rating: 2
*/
int isEqual(int x, int y) {
return !(x ^ y);
}
/*
* negate - return -x
@@ -229,7 +229,7 @@ int negate(int x) {
* Rating: 2
*/
int getByte(int x, int n) {
return 2;
return (x >> (n << 3)) & 0xFF;
}
//3
/*
@@ -240,7 +240,7 @@ int getByte(int x, int n) {
* Rating: 3
*/
int isLess(int x, int y) {
return 2;
return ((x >> 31) & 1 & ~((y >> 31) & 1)) | (~(((x >> 31) & 1) ^ ((y >> 31) & 1)) & (((x + (~y + 1)) >> 31) & 1));
}
/*
* conditional - same as x ? y : z
@@ -250,7 +250,7 @@ int isLess(int x, int y) {
* Rating: 3
*/
int conditional(int x, int y, int z) {
return 2;
return (((x | (~x + 1)) >> 31) & y) | (~((x | (~x + 1)) >> 31) & z);
}
//4
/*
@@ -265,7 +265,7 @@ int conditional(int x, int y, int z) {
* Rating: 4
*/
unsigned floatScale2(unsigned uf) {
return 2;
return (((uf >> 23) & 0xFF) == 0xFF) ? uf : ((((uf >> 23) & 0xFF) == 0) ? ((uf << 1) | (uf & (1 << 31))) : (uf + (1 << 23)));
}
/*
* floatFloat2Int - Return bit-level equivalent of expression (int) f
@@ -280,5 +280,31 @@ unsigned floatScale2(unsigned uf) {
* Rating: 4
*/
int floatFloat2Int(unsigned uf) {
return 2;
unsigned exp, sign, frac, mantissa;
int value, shift;
exp = (uf >> 23) & ((1 << 8) - 1);
sign = (uf >> 31) & 1;
frac = uf & ((1 << 23) - 1);
if (exp >= 158) {
return 1 << 31;
}
if (exp < 127) {
return 0;
}
shift = exp - 150;
mantissa = ((1 << 23) | frac);
if (shift > 0) {
value = mantissa << shift;
} else {
value = mantissa >> -shift;
}
if (sign) {
return -value;
} else {
return value;
}
}

View File

@@ -0,0 +1,7 @@
I can see Russia from my house!
1 2 4 8 16 32
0 262
6 6 DrEvil
eabhcc
2 1 5 3 4 6
22

BIN
labs/02_bomblab/bomb Executable file

Binary file not shown.

115
labs/02_bomblab/bomb.c Normal file
View File

@@ -0,0 +1,115 @@
/***************************************************************************
* Dr. Evil's Insidious Bomb, Version 1.1
* Copyright 2011, Dr. Evil Incorporated. All rights reserved.
*
* LICENSE:
*
* Dr. Evil Incorporated (the PERPETRATOR) hereby grants you (the
* VICTIM) explicit permission to use this bomb (the BOMB). This is a
* time limited license, which expires on the death of the VICTIM.
* The PERPETRATOR takes no responsibility for damage, frustration,
* insanity, bug-eyes, carpal-tunnel syndrome, loss of sleep, or other
* harm to the VICTIM. Unless the PERPETRATOR wants to take credit,
* that is. The VICTIM may not distribute this bomb source code to
* any enemies of the PERPETRATOR. No VICTIM may debug,
* reverse-engineer, run "strings" on, decompile, decrypt, or use any
* other technique to gain knowledge of and defuse the BOMB. BOMB
* proof clothing may not be worn when handling this program. The
* PERPETRATOR will not apologize for the PERPETRATOR's poor sense of
* humor. This license is null and void where the BOMB is prohibited
* by law.
***************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include "support.h"
#include "phases.h"
/*
* Note to self: Remember to erase this file so my victims will have no
* idea what is going on, and so they will all blow up in a
* spectaculary fiendish explosion. -- Dr. Evil
*/
FILE *infile;
int main(int argc, char *argv[])
{
char *input;
/* Note to self: remember to port this bomb to Windows and put a
* fantastic GUI on it. */
/* When run with no arguments, the bomb reads its input lines
* from standard input. */
if (argc == 1) {
infile = stdin;
}
/* When run with one argument <file>, the bomb reads from <file>
* until EOF, and then switches to standard input. Thus, as you
* defuse each phase, you can add its defusing string to <file> and
* avoid having to retype it. */
else if (argc == 2) {
if (!(infile = fopen(argv[1], "r"))) {
printf("%s: Error: Couldn't open %s\n", argv[0], argv[1]);
exit(8);
}
}
/* You can't call the bomb with more than 1 command line argument. */
else {
printf("Usage: %s [<input_file>]\n", argv[0]);
exit(8);
}
/* Do all sorts of secret stuff that makes the bomb harder to defuse. */
initialize_bomb();
printf("Welcome to my fiendish little bomb. You have 6 phases with\n");
printf("which to blow yourself up. Have a nice day!\n");
/* Hmm... Six phases must be more secure than one phase! */
input = read_line(); /* Get input */
phase_1(input); /* Run the phase */
phase_defused(); /* Drat! They figured it out!
* Let me know how they did it. */
printf("Phase 1 defused. How about the next one?\n");
/* The second phase is harder. No one will ever figure out
* how to defuse this... */
input = read_line();
phase_2(input);
phase_defused();
printf("That's number 2. Keep going!\n");
/* I guess this is too easy so far. Some more complex code will
* confuse people. */
input = read_line();
phase_3(input);
phase_defused();
printf("Halfway there!\n");
/* Oh yeah? Well, how good is your math? Try on this saucy problem! */
input = read_line();
phase_4(input);
phase_defused();
printf("So you got that one. Try this one.\n");
/* Round and 'round in memory we go, where we stop, the bomb blows! */
input = read_line();
phase_5(input);
phase_defused();
printf("Good work! On to the next...\n");
/* This phase will never be used, since no one will get past the
* earlier ones. But just in case, make this one extra hard. */
input = read_line();
phase_6(input);
phase_defused();
/* Wow, they got it! But isn't something... missing? Perhaps
* something they overlooked? Mua ha ha ha ha! */
return 0;
}

93
labs/03_shlab/Makefile Normal file
View File

@@ -0,0 +1,93 @@
# Makefile for the Shell Lab
TEAM = INDIVIDUAL
VERSION = 1
DRIVER = ./sdriver.pl
TSH = ./tsh
TSHREF = ./tshref
TSHARGS = "-p"
CC = gcc
CFLAGS = -Wall -O2
FILES = $(TSH) ./myspin ./mysplit ./mystop ./myint
all: $(FILES)
##################
# Regression tests
##################
tests: test01 test02 test03 test04 test05 test06 test07 test08 test09 test10 test11 test12 test13 test14 test15 test16
# Run tests using the student's shell program
test01:
$(DRIVER) -t trace01.txt -s $(TSH) -a $(TSHARGS)
test02:
$(DRIVER) -t trace02.txt -s $(TSH) -a $(TSHARGS)
test03:
$(DRIVER) -t trace03.txt -s $(TSH) -a $(TSHARGS)
test04:
$(DRIVER) -t trace04.txt -s $(TSH) -a $(TSHARGS)
test05:
$(DRIVER) -t trace05.txt -s $(TSH) -a $(TSHARGS)
test06:
$(DRIVER) -t trace06.txt -s $(TSH) -a $(TSHARGS)
test07:
$(DRIVER) -t trace07.txt -s $(TSH) -a $(TSHARGS)
test08:
$(DRIVER) -t trace08.txt -s $(TSH) -a $(TSHARGS)
test09:
$(DRIVER) -t trace09.txt -s $(TSH) -a $(TSHARGS)
test10:
$(DRIVER) -t trace10.txt -s $(TSH) -a $(TSHARGS)
test11:
$(DRIVER) -t trace11.txt -s $(TSH) -a $(TSHARGS)
test12:
$(DRIVER) -t trace12.txt -s $(TSH) -a $(TSHARGS)
test13:
$(DRIVER) -t trace13.txt -s $(TSH) -a $(TSHARGS)
test14:
$(DRIVER) -t trace14.txt -s $(TSH) -a $(TSHARGS)
test15:
$(DRIVER) -t trace15.txt -s $(TSH) -a $(TSHARGS)
test16:
$(DRIVER) -t trace16.txt -s $(TSH) -a $(TSHARGS)
rtests: rtest01 rtest02 rtest03 rtest04 rtest05 rtest06 rtest07 rtest08 rtest09 rtest10 rtest11 rtest12 rtest13 rtest14 rtest15 rtest16
# Run the tests using the reference shell program
rtest01:
$(DRIVER) -t trace01.txt -s $(TSHREF) -a $(TSHARGS)
rtest02:
$(DRIVER) -t trace02.txt -s $(TSHREF) -a $(TSHARGS)
rtest03:
$(DRIVER) -t trace03.txt -s $(TSHREF) -a $(TSHARGS)
rtest04:
$(DRIVER) -t trace04.txt -s $(TSHREF) -a $(TSHARGS)
rtest05:
$(DRIVER) -t trace05.txt -s $(TSHREF) -a $(TSHARGS)
rtest06:
$(DRIVER) -t trace06.txt -s $(TSHREF) -a $(TSHARGS)
rtest07:
$(DRIVER) -t trace07.txt -s $(TSHREF) -a $(TSHARGS)
rtest08:
$(DRIVER) -t trace08.txt -s $(TSHREF) -a $(TSHARGS)
rtest09:
$(DRIVER) -t trace09.txt -s $(TSHREF) -a $(TSHARGS)
rtest10:
$(DRIVER) -t trace10.txt -s $(TSHREF) -a $(TSHARGS)
rtest11:
$(DRIVER) -t trace11.txt -s $(TSHREF) -a $(TSHARGS)
rtest12:
$(DRIVER) -t trace12.txt -s $(TSHREF) -a $(TSHARGS)
rtest13:
$(DRIVER) -t trace13.txt -s $(TSHREF) -a $(TSHARGS)
rtest14:
$(DRIVER) -t trace14.txt -s $(TSHREF) -a $(TSHARGS)
rtest15:
$(DRIVER) -t trace15.txt -s $(TSHREF) -a $(TSHARGS)
rtest16:
$(DRIVER) -t trace16.txt -s $(TSHREF) -a $(TSHARGS)
# clean up
clean:
rm -f $(FILES) *.o *~

22
labs/03_shlab/README Normal file
View File

@@ -0,0 +1,22 @@
************************************
* CSE4009 Assignment 3 - Shell Lab *
************************************
Files:
Makefile # Compiles your shell program and runs the tests
README # This file
tsh.c # The shell program that you will write and hand in
tshref # The reference shell binary.
# The remaining files are used to test your shell
sdriver.pl # The trace-driven shell driver
trace*.txt # The 15 trace files that control the shell driver
tshref.out # Example output of the reference shell on all 16 traces
# Little C programs that are called by the trace files
myspin.c # Takes argument <n> and spins for <n> seconds
mysplit.c # Forks a child that spins for <n> seconds
mystop.c # Spins for <n> seconds and sends SIGTSTP to itself
myint.c # Spins for <n> seconds and sends SIGINT to itself

36
labs/03_shlab/myint.c Normal file
View File

@@ -0,0 +1,36 @@
/*
* myint.c - Another handy routine for testing your tiny shell
*
* usage: myint <n>
* Sleeps for <n> seconds and sends SIGINT to itself.
*
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
int main(int argc, char **argv)
{
int i, secs;
pid_t pid;
if (argc != 2) {
fprintf(stderr, "Usage: %s <n>\n", argv[0]);
exit(0);
}
secs = atoi(argv[1]);
for (i=0; i < secs; i++)
sleep(1);
pid = getpid();
if (kill(pid, SIGINT) < 0)
fprintf(stderr, "kill (int) error");
exit(0);
}

24
labs/03_shlab/myspin.c Normal file
View File

@@ -0,0 +1,24 @@
/*
* myspin.c - A handy program for testing your tiny shell
*
* usage: myspin <n>
* Sleeps for <n> seconds in 1-second chunks.
*
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
int i, secs;
if (argc != 2) {
fprintf(stderr, "Usage: %s <n>\n", argv[0]);
exit(0);
}
secs = atoi(argv[1]);
for (i=0; i < secs; i++)
sleep(1);
exit(0);
}

35
labs/03_shlab/mysplit.c Normal file
View File

@@ -0,0 +1,35 @@
/*
* mysplit.c - Another handy routine for testing your tiny shell
*
* usage: mysplit <n>
* Fork a child that spins for <n> seconds in 1-second chunks.
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
int main(int argc, char **argv)
{
int i, secs;
if (argc != 2) {
fprintf(stderr, "Usage: %s <n>\n", argv[0]);
exit(0);
}
secs = atoi(argv[1]);
if (fork() == 0) { /* child */
for (i=0; i < secs; i++)
sleep(1);
exit(0);
}
/* parent waits for child to terminate */
wait(NULL);
exit(0);
}

36
labs/03_shlab/mystop.c Normal file
View File

@@ -0,0 +1,36 @@
/*
* mystop.c - Another handy routine for testing your tiny shell
*
* usage: mystop <n>
* Sleeps for <n> seconds and sends SIGTSTP to itself.
*
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
int main(int argc, char **argv)
{
int i, secs;
pid_t pid;
if (argc != 2) {
fprintf(stderr, "Usage: %s <n>\n", argv[0]);
exit(0);
}
secs = atoi(argv[1]);
for (i=0; i < secs; i++)
sleep(1);
pid = getpid();
if (kill(-pid, SIGTSTP) < 0)
fprintf(stderr, "kill (tstp) error");
exit(0);
}

210
labs/03_shlab/sdriver.pl Executable file
View File

@@ -0,0 +1,210 @@
#!/usr/bin/perl
#!/usr/local/bin/perl
use Getopt::Std;
use FileHandle;
use IPC::Open2;
#######################################################################
# sdriver.pl - Shell driver
#
# Copyright (c) 2002, R. Bryant and D. O'Hallaron, All rights reserved.
# May not be used, modified, or copied without permission.
#
# The driver runs a student's shell program as a child, sends
# commands and signals to the child as directed by a trace file,
# and captures and displays the output produced by the child.
#
# Tracefile format:
#
# The tracefile consists of text lines that are either blank lines,
# comment lines, driver commands, or shell commands. Blank lines are
# ignored. Comment lines begin with "#" and are echo'd without change
# to stdout. Driver commands are intepreted by the driver and are not
# passed to the child shell. All other lines are shell commands and
# are passed without modification to the shell, which reads them on
# stdin. Output produced by the child on stdout/stderr is read by
# the parent and printed on its stdout.
#
# Driver commands:
# TSTP Send a SIGTSTP signal to the child
# INT Send a SIGINT signal to the child
# QUIT Send a SIGQUIT signal to the child
# KILL Send a SIGKILL signal to the child
# CLOSE Close Writer (sends EOF signal to child)
# WAIT Wait() for child to terminate
# SLEEP <n> Sleep for <n> seconds
#
######################################################################
#
# usage - print help message and terminate
#
sub usage
{
printf STDERR "$_[0]\n";
printf STDERR "Usage: $0 [-hv] -t <trace> -s <shellprog> -a <args>\n";
printf STDERR "Options:\n";
printf STDERR " -h Print this message\n";
printf STDERR " -v Be more verbose\n";
printf STDERR " -t <trace> Trace file\n";
printf STDERR " -s <shell> Shell program to test\n";
printf STDERR " -a <args> Shell arguments\n";
printf STDERR " -g Generate output for autograder\n";
die "\n" ;
}
# Parse the command line arguments
getopts('hgvt:s:a:');
if ($opt_h) {
usage();
}
if (!$opt_t) {
usage("Missing required -t argument");
}
if (!$opt_s) {
usage("Missing required -s argument");
}
$verbose = $opt_v;
$infile = $opt_t;
$shellprog = $opt_s;
$shellargs = $opt_a;
$grade = $opt_g;
# Make sure the input script exists and is readable
-e $infile
or die "$0: ERROR: $infile not found\n";
-r $infile
or die "$0: ERROR: $infile is not readable\n";
# Make sure the shell program exists and is executable
-e $shellprog
or die "$0: ERROR: $shellprog not found\n";
-x $shellprog
or die "$0: ERROR: $shellprog is not executable\n";
# Open the input script
open INFILE, $infile
or die "$0: ERROR: Couldn't open input file $infile: $!\n";
#
# Fork a child, run the shell in it, and connect the parent
# and child with a pair of unidirectional pipes:
# parent:Writer -> child:stdin
# child:stdout -> parent:Reader
#
$pid = open2(\*Reader, \*Writer, "$shellprog $shellargs");
Writer->autoflush();
# The autograder will want to know the child shell's pid
if ($grade) {
print ("pid=$pid\n");
}
#
# Parent reads a trace file, sends commands to the child shell.
#
while (<INFILE>) {
$line = $_;
chomp($line);
# Comment line
if ($line =~ /^#/) {
print "$line\n";
}
# Blank line
elsif ($line =~ /^\s*$/) {
if ($verbose) {
print "$0: Ignoring blank line\n";
}
}
# Send SIGTSTP (ctrl-z)
elsif ($line =~ /TSTP/) {
if ($verbose) {
print "$0: Sending SIGTSTP signal to process $pid\n";
}
kill 'TSTP', $pid;
}
# Send SIGINT (ctrl-c)
elsif ($line =~ /INT/) {
if ($verbose) {
print "$0: Sending SIGINT signal to process $pid\n";
}
kill 'INT', $pid;
}
# Send SIGQUIT (whenever we need graceful termination)
elsif ($line =~ /QUIT/) {
if ($verbose) {
print "$0: Sending SIGQUIT signal to process $pid\n";
}
kill 'QUIT', $pid;
}
# Send SIGKILL
elsif ($line =~ /KILL/) {
if ($verbose) {
print "$0: Sending SIGKILL signal to process $pid\n";
}
kill 'KILL', $pid;
}
# Close pipe (sends EOF notification to child)
elsif ($line =~ /CLOSE/) {
if ($verbose) {
print "$0: Closing output end of pipe to child $pid\n";
}
close Writer;
}
# Wait for child to terminate
elsif ($line =~ /WAIT/) {
if ($verbose) {
print "$0: Waiting for child $pid\n";
}
wait;
if ($verbose) {
print "$0: Child $pid reaped\n";
}
}
# Sleep
elsif ($line =~ /SLEEP (\d+)/) {
if ($verbose) {
print "$0: Sleeping $1 secs\n";
}
sleep $1;
}
# Unknown input
else {
if ($verbose) {
print "$0: Sending :$line: to child $pid\n";
}
print Writer "$line\n";
}
}
#
# Parent echoes the output produced by the child.
#
close Writer;
if ($verbose) {
print "$0: Reading data from child $pid\n";
}
while ($line = <Reader>) {
print $line;
}
close Reader;
# Finally, parent reaps child
wait;
if ($verbose) {
print "$0: Shell terminated\n";
}
exit;

View File

@@ -0,0 +1,5 @@
#
# trace01.txt - Properly terminate on EOF.
#
CLOSE
WAIT

View File

@@ -0,0 +1,5 @@
#
# trace02.txt - Process builtin quit command.
#
quit
WAIT

View File

@@ -0,0 +1,5 @@
#
# trace03.txt - Run a foreground job.
#
/bin/echo tsh> quit
quit

View File

@@ -0,0 +1,5 @@
#
# trace04.txt - Run a background job.
#
/bin/echo -e tsh> ./myspin 1 \046
./myspin 1 &

11
labs/03_shlab/trace05.txt Normal file
View File

@@ -0,0 +1,11 @@
#
# trace05.txt - Process jobs builtin command.
#
/bin/echo -e tsh> ./myspin 2 \046
./myspin 2 &
/bin/echo -e tsh> ./myspin 3 \046
./myspin 3 &
/bin/echo tsh> jobs
jobs

View File

@@ -0,0 +1,8 @@
#
# trace06.txt - Forward SIGINT to foreground job.
#
/bin/echo -e tsh> ./myspin 4
./myspin 4
SLEEP 2
INT

14
labs/03_shlab/trace07.txt Normal file
View File

@@ -0,0 +1,14 @@
#
# trace07.txt - Forward SIGINT only to foreground job.
#
/bin/echo -e tsh> ./myspin 4 \046
./myspin 4 &
/bin/echo -e tsh> ./myspin 5
./myspin 5
SLEEP 2
INT
/bin/echo tsh> jobs
jobs

14
labs/03_shlab/trace08.txt Normal file
View File

@@ -0,0 +1,14 @@
#
# trace08.txt - Forward SIGTSTP only to foreground job.
#
/bin/echo -e tsh> ./myspin 4 \046
./myspin 4 &
/bin/echo -e tsh> ./myspin 5
./myspin 5
SLEEP 2
TSTP
/bin/echo tsh> jobs
jobs

20
labs/03_shlab/trace09.txt Normal file
View File

@@ -0,0 +1,20 @@
#
# trace09.txt - Process bg builtin command
#
/bin/echo -e tsh> ./myspin 4 \046
./myspin 4 &
/bin/echo -e tsh> ./myspin 5
./myspin 5
SLEEP 2
TSTP
/bin/echo tsh> jobs
jobs
/bin/echo tsh> bg %2
bg %2
/bin/echo tsh> jobs
jobs

22
labs/03_shlab/trace10.txt Normal file
View File

@@ -0,0 +1,22 @@
#
# trace10.txt - Process fg builtin command.
#
/bin/echo -e tsh> ./myspin 4 \046
./myspin 4 &
SLEEP 1
/bin/echo tsh> fg %1
fg %1
SLEEP 1
TSTP
/bin/echo tsh> jobs
jobs
/bin/echo tsh> fg %1
fg %1
/bin/echo tsh> jobs
jobs

12
labs/03_shlab/trace11.txt Normal file
View File

@@ -0,0 +1,12 @@
#
# trace11.txt - Forward SIGINT to every process in foreground process group
#
/bin/echo -e tsh> ./mysplit 4
./mysplit 4
SLEEP 2
INT
/bin/echo tsh> /bin/ps a
/bin/ps a

17
labs/03_shlab/trace12.txt Normal file
View File

@@ -0,0 +1,17 @@
#
# trace12.txt - Forward SIGTSTP to every process in foreground process group
#
/bin/echo -e tsh> ./mysplit 4
./mysplit 4
SLEEP 2
TSTP
/bin/echo tsh> jobs
jobs
/bin/echo tsh> /bin/ps a
/bin/ps a

23
labs/03_shlab/trace13.txt Normal file
View File

@@ -0,0 +1,23 @@
#
# trace13.txt - Restart every stopped process in process group
#
/bin/echo -e tsh> ./mysplit 4
./mysplit 4
SLEEP 2
TSTP
/bin/echo tsh> jobs
jobs
/bin/echo tsh> /bin/ps a
/bin/ps a
/bin/echo tsh> fg %1
fg %1
/bin/echo tsh> /bin/ps a
/bin/ps a

47
labs/03_shlab/trace14.txt Normal file
View File

@@ -0,0 +1,47 @@
#
# trace14.txt - Simple error handling
#
/bin/echo tsh> ./bogus
./bogus
/bin/echo -e tsh> ./myspin 4 \046
./myspin 4 &
/bin/echo tsh> fg
fg
/bin/echo tsh> bg
bg
/bin/echo tsh> fg a
fg a
/bin/echo tsh> bg a
bg a
/bin/echo tsh> fg 9999999
fg 9999999
/bin/echo tsh> bg 9999999
bg 9999999
/bin/echo tsh> fg %2
fg %2
/bin/echo tsh> fg %1
fg %1
SLEEP 2
TSTP
/bin/echo tsh> bg %2
bg %2
/bin/echo tsh> bg %1
bg %1
/bin/echo tsh> jobs
jobs

46
labs/03_shlab/trace15.txt Normal file
View File

@@ -0,0 +1,46 @@
#
# trace15.txt - Putting it all together
#
/bin/echo tsh> ./bogus
./bogus
/bin/echo tsh> ./myspin 10
./myspin 10
SLEEP 2
INT
/bin/echo -e tsh> ./myspin 3 \046
./myspin 3 &
/bin/echo -e tsh> ./myspin 4 \046
./myspin 4 &
/bin/echo tsh> jobs
jobs
/bin/echo tsh> fg %1
fg %1
SLEEP 2
TSTP
/bin/echo tsh> jobs
jobs
/bin/echo tsh> bg %3
bg %3
/bin/echo tsh> bg %1
bg %1
/bin/echo tsh> jobs
jobs
/bin/echo tsh> fg %1
fg %1
/bin/echo tsh> quit
quit

16
labs/03_shlab/trace16.txt Normal file
View File

@@ -0,0 +1,16 @@
#
# trace16.txt - Tests whether the shell can handle SIGTSTP and SIGINT
# signals that come from other processes instead of the terminal.
#
/bin/echo tsh> ./mystop 2
./mystop 2
SLEEP 3
/bin/echo tsh> jobs
jobs
/bin/echo tsh> ./myint 2
./myint 2

606
labs/03_shlab/tsh.c Normal file
View File

@@ -0,0 +1,606 @@
/*
* tsh - A tiny shell program with job control
*
* Name: Hajin Ju
* Student id: 2024062806
*/
#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
/* Misc manifest constants */
#define MAXLINE 1024 /* max line size */
#define MAXARGS 128 /* max args on a command line */
#define MAXJOBS 16 /* max jobs at any point in time */
#define MAXJID 1 << 16 /* max job ID */
/* Job states */
#define UNDEF 0 /* undefined */
#define FG 1 /* running in foreground */
#define BG 2 /* running in background */
#define ST 3 /* stopped */
/*
* Jobs states: FG (foreground), BG (background), ST (stopped)
* Job state transitions and enabling actions:
* FG -> ST : ctrl-z
* ST -> FG : fg command
* ST -> BG : bg command
* BG -> FG : fg command
* At most 1 job can be in the FG state.
*/
/* Global variables */
extern char **environ; /* defined in libc */
char prompt[] = "tsh> "; /* command line prompt (DO NOT CHANGE) */
int verbose = 0; /* if true, print additional output */
int nextjid = 1; /* next job ID to allocate */
char sbuf[MAXLINE]; /* for composing sprintf messages */
struct job_t { /* The job struct */
pid_t pid; /* job PID */
int jid; /* job ID [1, 2, ...] */
int state; /* UNDEF, BG, FG, or ST */
char cmdline[MAXLINE]; /* command line */
};
struct job_t jobs[MAXJOBS]; /* The job list */
/* End global variables */
/* Function prototypes */
/*----------------------------------------------------------------------------
* Functions that you will implement
*/
void eval(char *cmdline);
int builtin_cmd(char **argv);
void do_bgfg(char **argv);
void waitfg(pid_t pid);
void sigchld_handler(int sig);
void sigint_handler(int sig);
void sigtstp_handler(int sig);
/*----------------------------------------------------------------------------*/
/* These functions are already implemented for your convenience */
int parseline(const char *cmdline, char **argv);
void sigquit_handler(int sig);
void clearjob(struct job_t *job);
void initjobs(struct job_t *jobs);
int maxjid(struct job_t *jobs);
int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline);
int deletejob(struct job_t *jobs, pid_t pid);
pid_t fgpid(struct job_t *jobs);
struct job_t *getjobpid(struct job_t *jobs, pid_t pid);
struct job_t *getjobjid(struct job_t *jobs, int jid);
int pid2jid(pid_t pid);
void listjobs(struct job_t *jobs);
void usage(void);
void unix_error(char *msg);
void app_error(char *msg);
typedef void handler_t(int);
handler_t *Signal(int signum, handler_t *handler);
/*
* main - The shell's main routine
*/
int main(int argc, char **argv) {
char c;
char cmdline[MAXLINE];
int emit_prompt = 1; /* emit prompt (default) */
/* Redirect stderr to stdout (so that driver will get all output
* on the pipe connected to stdout) */
dup2(1, 2);
/* Parse the command line */
while ((c = getopt(argc, argv, "hvp")) != EOF) {
switch (c) {
case 'h': /* print help message */
usage();
break;
case 'v': /* emit additional diagnostic info */
verbose = 1;
break;
case 'p': /* don't print a prompt */
emit_prompt = 0; /* handy for automatic testing */
break;
default:
usage();
}
}
/* Install the signal handlers */
/* These are the ones you will need to implement */
Signal(SIGINT, sigint_handler); /* ctrl-c */
Signal(SIGTSTP, sigtstp_handler); /* ctrl-z */
Signal(SIGCHLD, sigchld_handler); /* Terminated or stopped child */
/* This one provides a clean way to kill the shell */
Signal(SIGQUIT, sigquit_handler);
/* Initialize the job list */
initjobs(jobs);
/* Execute the shell's read/eval loop */
while (1) {
/* Read command line */
if (emit_prompt) {
printf("%s", prompt);
fflush(stdout);
}
if ((fgets(cmdline, MAXLINE, stdin) == NULL) && ferror(stdin))
app_error("fgets error");
if (feof(stdin)) { /* End of file (ctrl-d) */
fflush(stdout);
exit(0);
}
/* Evaluate the command line */
eval(cmdline);
fflush(stdout);
fflush(stdout);
}
exit(0); /* control never reaches here */
}
/*
* eval - Evaluate the command line that the user has just typed in
*
* If the user has requested a built-in command (quit, jobs, bg or fg)
* then execute it immediately. Otherwise, fork a child process and
* run the job in the context of the child. If the job is running in
* the foreground, wait for it to terminate and then return. Note:
* each child process must have a unique process group ID so that our
* background children don't receive SIGINT (SIGTSTP) from the kernel
* when we type ctrl-c (ctrl-z) at the keyboard.
*/
void eval(char *cmdline) {
pid_t pid;
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);
}
}
/*
* parseline - Parse the command line and build the argv array.
*
* Characters enclosed in single quotes are treated as a single
* argument. Return true if the user has requested a BG job, false if
* the user has requested a FG job.
*/
int parseline(const char *cmdline, char **argv) {
static char array[MAXLINE]; /* holds local copy of command line */
char *buf = array; /* ptr that traverses command line */
char *delim; /* points to first space delimiter */
int argc; /* number of args */
int bg; /* background job? */
strcpy(buf, cmdline);
buf[strlen(buf) - 1] = ' '; /* replace trailing '\n' with space */
while (*buf && (*buf == ' ')) /* ignore leading spaces */
buf++;
/* Build the argv list */
argc = 0;
if (*buf == '\'') {
buf++;
delim = strchr(buf, '\'');
} else {
delim = strchr(buf, ' ');
}
while (delim) {
argv[argc++] = buf;
*delim = '\0';
buf = delim + 1;
while (*buf && (*buf == ' ')) /* ignore spaces */
buf++;
if (*buf == '\'') {
buf++;
delim = strchr(buf, '\'');
} else {
delim = strchr(buf, ' ');
}
}
argv[argc] = NULL;
if (argc == 0) /* ignore blank line */
return 1;
/* should the job run in the background? */
if ((bg = (*argv[argc - 1] == '&')) != 0) {
argv[--argc] = NULL;
}
return bg;
}
/*
* builtin_cmd - If the user has typed a built-in command then execute
* it immediately.
*/
int builtin_cmd(char **argv) {
// Built-in-commands: jobs, bg, fg, quit
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
*/
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;
}
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
*/
void waitfg(pid_t pid) {
while (pid == fgpid(jobs)) {
sleep(1);
}
return;
}
/*****************
* Signal handlers
*****************/
/*
* sigchld_handler - The kernel sends a SIGCHLD to the shell whenever
* a child job terminates (becomes a zombie), or stops because it
* received a SIGSTOP or SIGTSTP signal. The handler reaps all
* available zombie children, but doesn't wait for any other
* currently running children to terminate.
*/
void sigchld_handler(int sig) {
int olderrno = errno;
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;
}
/*
* sigint_handler - The kernel sends a SIGINT to the shell whenver the
* user types ctrl-c at the keyboard. Catch it and send it along
* to the foreground job.
*/
void sigint_handler(int sig) {
pid_t pid;
if ((pid = fgpid(jobs)) > 0) {
kill(-pid, SIGINT);
}
}
/*
* sigtstp_handler - The kernel sends a SIGTSTP to the shell whenever
* the user types ctrl-z at the keyboard. Catch it and suspend the
* foreground job by sending it a SIGTSTP.
*/
void sigtstp_handler(int sig) {
pid_t pid;
if ((pid = fgpid(jobs)) > 0) {
kill(-pid, SIGTSTP);
}
}
/*********************
* End signal handlers
*********************/
/***********************************************
* Helper routines that manipulate the job list
**********************************************/
/* clearjob - Clear the entries in a job struct */
void clearjob(struct job_t *job) {
job->pid = 0;
job->jid = 0;
job->state = UNDEF;
job->cmdline[0] = '\0';
}
/* initjobs - Initialize the job list */
void initjobs(struct job_t *jobs) {
int i;
for (i = 0; i < MAXJOBS; i++)
clearjob(&jobs[i]);
}
/* maxjid - Returns largest allocated job ID */
int maxjid(struct job_t *jobs) {
int i, max = 0;
for (i = 0; i < MAXJOBS; i++)
if (jobs[i].jid > max)
max = jobs[i].jid;
return max;
}
/* addjob - Add a job to the job list */
int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline) {
int i;
if (pid < 1)
return 0;
for (i = 0; i < MAXJOBS; i++) {
if (jobs[i].pid == 0) {
jobs[i].pid = pid;
jobs[i].state = state;
jobs[i].jid = nextjid++;
if (nextjid > MAXJOBS)
nextjid = 1;
strcpy(jobs[i].cmdline, cmdline);
if (verbose) {
printf("Added job [%d] %d %s\n", jobs[i].jid, jobs[i].pid, jobs[i].cmdline);
}
return 1;
}
}
printf("Tried to create too many jobs\n");
return 0;
}
/* deletejob - Delete a job whose PID=pid from the job list */
int deletejob(struct job_t *jobs, pid_t pid) {
int i;
if (pid < 1)
return 0;
for (i = 0; i < MAXJOBS; i++) {
if (jobs[i].pid == pid) {
clearjob(&jobs[i]);
nextjid = maxjid(jobs) + 1;
return 1;
}
}
return 0;
}
/* fgpid - Return PID of current foreground job, 0 if no such job */
pid_t fgpid(struct job_t *jobs) {
int i;
for (i = 0; i < MAXJOBS; i++)
if (jobs[i].state == FG)
return jobs[i].pid;
return 0;
}
/* getjobpid - Find a job (by PID) on the job list */
struct job_t *getjobpid(struct job_t *jobs, pid_t pid) {
int i;
if (pid < 1)
return NULL;
for (i = 0; i < MAXJOBS; i++)
if (jobs[i].pid == pid)
return &jobs[i];
return NULL;
}
/* getjobjid - Find a job (by JID) on the job list */
struct job_t *getjobjid(struct job_t *jobs, int jid) {
int i;
if (jid < 1)
return NULL;
for (i = 0; i < MAXJOBS; i++)
if (jobs[i].jid == jid)
return &jobs[i];
return NULL;
}
/* pid2jid - Map process ID to job ID */
int pid2jid(pid_t pid) {
int i;
if (pid < 1)
return 0;
for (i = 0; i < MAXJOBS; i++)
if (jobs[i].pid == pid) {
return jobs[i].jid;
}
return 0;
}
/* listjobs - Print the job list */
void listjobs(struct job_t *jobs) {
int i;
for (i = 0; i < MAXJOBS; i++) {
if (jobs[i].pid != 0) {
printf("[%d] (%d) ", jobs[i].jid, jobs[i].pid);
switch (jobs[i].state) {
case BG:
printf("Running ");
break;
case FG:
printf("Foreground ");
break;
case ST:
printf("Stopped ");
break;
default:
printf("listjobs: Internal error: job[%d].state=%d ",
i, jobs[i].state);
}
printf("%s", jobs[i].cmdline);
}
}
}
/******************************
* end job list helper routines
******************************/
/***********************
* Other helper routines
***********************/
/*
* usage - print a help message
*/
void usage(void) {
printf("Usage: shell [-hvp]\n");
printf(" -h print this message\n");
printf(" -v print additional diagnostic information\n");
printf(" -p do not emit a command prompt\n");
exit(1);
}
/*
* unix_error - unix-style error routine
*/
void unix_error(char *msg) {
fprintf(stdout, "%s: %s\n", msg, strerror(errno));
exit(1);
}
/*
* app_error - application-style error routine
*/
void app_error(char *msg) {
fprintf(stdout, "%s\n", msg);
exit(1);
}
/*
* Signal - wrapper for the sigaction function
*/
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);
}
/*
* sigquit_handler - The driver program can gracefully terminate the
* child shell by sending it a SIGQUIT signal.
*/
void sigquit_handler(int sig) {
printf("Terminating after receipt of SIGQUIT signal\n");
exit(1);
}

BIN
labs/03_shlab/tshref Executable file

Binary file not shown.

218
labs/03_shlab/tshref.out Normal file
View File

@@ -0,0 +1,218 @@
./sdriver.pl -t trace01.txt -s ./tsh -a "-p"
#
# trace01.txt - Properly terminate on EOF.
#
./sdriver.pl -t trace02.txt -s ./tsh -a "-p"
#
# trace02.txt - Process builtin quit command.
#
./sdriver.pl -t trace03.txt -s ./tsh -a "-p"
#
# trace03.txt - Run a foreground job.
#
tsh> quit
./sdriver.pl -t trace04.txt -s ./tsh -a "-p"
#
# trace04.txt - Run a background job.
#
tsh> ./myspin 1 &
[1] (26252) ./myspin 1 &
./sdriver.pl -t trace05.txt -s ./tsh -a "-p"
#
# trace05.txt - Process jobs builtin command.
#
tsh> ./myspin 2 &
[1] (26256) ./myspin 2 &
tsh> ./myspin 3 &
[2] (26258) ./myspin 3 &
tsh> jobs
[1] (26256) Running ./myspin 2 &
[2] (26258) Running ./myspin 3 &
./sdriver.pl -t trace06.txt -s ./tsh -a "-p"
#
# trace06.txt - Forward SIGINT to foreground job.
#
tsh> ./myspin 4
Job [1] (26263) terminated by signal 2
./sdriver.pl -t trace07.txt -s ./tsh -a "-p"
#
# trace07.txt - Forward SIGINT only to foreground job.
#
tsh> ./myspin 4 &
[1] (26267) ./myspin 4 &
tsh> ./myspin 5
Job [2] (26269) terminated by signal 2
tsh> jobs
[1] (26267) Running ./myspin 4 &
./sdriver.pl -t trace08.txt -s ./tsh -a "-p"
#
# trace08.txt - Forward SIGTSTP only to foreground job.
#
tsh> ./myspin 4 &
[1] (26274) ./myspin 4 &
tsh> ./myspin 5
Job [2] (26276) stopped by signal 20
tsh> jobs
[1] (26274) Running ./myspin 4 &
[2] (26276) Stopped ./myspin 5
./sdriver.pl -t trace09.txt -s ./tsh -a "-p"
#
# trace09.txt - Process bg builtin command
#
tsh> ./myspin 4 &
[1] (26281) ./myspin 4 &
tsh> ./myspin 5
Job [2] (26283) stopped by signal 20
tsh> jobs
[1] (26281) Running ./myspin 4 &
[2] (26283) Stopped ./myspin 5
tsh> bg %2
[2] (26283) ./myspin 5
tsh> jobs
[1] (26281) Running ./myspin 4 &
[2] (26283) Running ./myspin 5
./sdriver.pl -t trace10.txt -s ./tsh -a "-p"
#
# trace10.txt - Process fg builtin command.
#
tsh> ./myspin 4 &
[1] (26290) ./myspin 4 &
tsh> fg %1
Job [1] (26290) stopped by signal 20
tsh> jobs
[1] (26290) Stopped ./myspin 4 &
tsh> fg %1
tsh> jobs
./sdriver.pl -t trace11.txt -s ./tsh -a "-p"
#
# trace11.txt - Forward SIGINT to every process in foreground process group
#
tsh> ./mysplit 4
Job [1] (26298) terminated by signal 2
tsh> /bin/ps a
PID TTY STAT TIME COMMAND
25181 pts/3 S 0:00 -usr/local/bin/tcsh -i
26239 pts/3 S 0:00 make tshrefout
26240 pts/3 S 0:00 /bin/sh -c make tests > tshref.out 2>&1
26241 pts/3 S 0:00 make tests
26295 pts/3 S 0:00 perl ./sdriver.pl -t trace11.txt -s ./tsh -a -p
26296 pts/3 S 0:00 ./tsh -p
26301 pts/3 R 0:00 /bin/ps a
./sdriver.pl -t trace12.txt -s ./tsh -a "-p"
#
# trace12.txt - Forward SIGTSTP to every process in foreground process group
#
tsh> ./mysplit 4
Job [1] (26305) stopped by signal 20
tsh> jobs
[1] (26305) Stopped ./mysplit 4
tsh> /bin/ps a
PID TTY STAT TIME COMMAND
25181 pts/3 S 0:00 -usr/local/bin/tcsh -i
26239 pts/3 S 0:00 make tshrefout
26240 pts/3 S 0:00 /bin/sh -c make tests > tshref.out 2>&1
26241 pts/3 S 0:00 make tests
26302 pts/3 S 0:00 perl ./sdriver.pl -t trace12.txt -s ./tsh -a -p
26303 pts/3 S 0:00 ./tsh -p
26305 pts/3 T 0:00 ./mysplit 4
26306 pts/3 T 0:00 ./mysplit 4
26309 pts/3 R 0:00 /bin/ps a
./sdriver.pl -t trace13.txt -s ./tsh -a "-p"
#
# trace13.txt - Restart every stopped process in process group
#
tsh> ./mysplit 4
Job [1] (26313) stopped by signal 20
tsh> jobs
[1] (26313) Stopped ./mysplit 4
tsh> /bin/ps a
PID TTY STAT TIME COMMAND
25181 pts/3 S 0:00 -usr/local/bin/tcsh -i
26239 pts/3 S 0:00 make tshrefout
26240 pts/3 S 0:00 /bin/sh -c make tests > tshref.out 2>&1
26241 pts/3 S 0:00 make tests
26310 pts/3 S 0:00 perl ./sdriver.pl -t trace13.txt -s ./tsh -a -p
26311 pts/3 S 0:00 ./tsh -p
26313 pts/3 T 0:00 ./mysplit 4
26314 pts/3 T 0:00 ./mysplit 4
26317 pts/3 R 0:00 /bin/ps a
tsh> fg %1
tsh> /bin/ps a
PID TTY STAT TIME COMMAND
25181 pts/3 S 0:00 -usr/local/bin/tcsh -i
26239 pts/3 S 0:00 make tshrefout
26240 pts/3 S 0:00 /bin/sh -c make tests > tshref.out 2>&1
26241 pts/3 S 0:00 make tests
26310 pts/3 S 0:00 perl ./sdriver.pl -t trace13.txt -s ./tsh -a -p
26311 pts/3 S 0:00 ./tsh -p
26320 pts/3 R 0:00 /bin/ps a
./sdriver.pl -t trace14.txt -s ./tsh -a "-p"
#
# trace14.txt - Simple error handling
#
tsh> ./bogus
./bogus: Command not found
tsh> ./myspin 4 &
[1] (26326) ./myspin 4 &
tsh> fg
fg command requires PID or %jobid argument
tsh> bg
bg command requires PID or %jobid argument
tsh> fg a
fg: argument must be a PID or %jobid
tsh> bg a
bg: argument must be a PID or %jobid
tsh> fg 9999999
(9999999): No such process
tsh> bg 9999999
(9999999): No such process
tsh> fg %2
%2: No such job
tsh> fg %1
Job [1] (26326) stopped by signal 20
tsh> bg %2
%2: No such job
tsh> bg %1
[1] (26326) ./myspin 4 &
tsh> jobs
[1] (26326) Running ./myspin 4 &
./sdriver.pl -t trace15.txt -s ./tsh -a "-p"
#
# trace15.txt - Putting it all together
#
tsh> ./bogus
./bogus: Command not found
tsh> ./myspin 10
Job [1] (26343) terminated by signal 2
tsh> ./myspin 3 &
[1] (26345) ./myspin 3 &
tsh> ./myspin 4 &
[2] (26347) ./myspin 4 &
tsh> jobs
[1] (26345) Running ./myspin 3 &
[2] (26347) Running ./myspin 4 &
tsh> fg %1
Job [1] (26345) stopped by signal 20
tsh> jobs
[1] (26345) Stopped ./myspin 3 &
[2] (26347) Running ./myspin 4 &
tsh> bg %3
%3: No such job
tsh> bg %1
[1] (26345) ./myspin 3 &
tsh> jobs
[1] (26345) Running ./myspin 3 &
[2] (26347) Running ./myspin 4 &
tsh> fg %1
tsh> quit
./sdriver.pl -t trace16.txt -s ./tsh -a "-p"
#
# trace16.txt - Tests whether the shell can handle SIGTSTP and SIGINT
# signals that come from other processes instead of the terminal.
#
tsh> ./mystop 2
Job [1] (26359) stopped by signal 20
tsh> jobs
[1] (26359) Stopped ./mystop 2
tsh> ./myint 2
Job [2] (26362) terminated by signal 2

View File

@@ -1,9 +1,55 @@
# Bits, Bytes, and Integers
In computers, everything consists of bits.
By encoding sets of bits in various ways, they made meanings:
* instructions
* and data(numbers, sets, strings, etc..)
## Boolean Algebra
* and `A & B`
* or `A | B`
* not `~A`
* xor `A ^ B`
### in C
* Shift (`<<`, `>>`)
* Left Shift(`<<`)
Zero fill on right
* Right Shift(`>>`)
Logical Shift: zero fill with 0's on left
Arithmetic shift Relicate most significant bit on left
```c {cmd="gcc" args=[-x c $input_file -O0 -m32 -o 1_1.out]}
#include <stdio.h>
int main() {
int a = 0x7fffffff;
int as = a << 1;
printf("shl of %d: %d(%08x)\n", a, as, as);
unsigned b = 0x7fffffff;
unsigned bs = b << 1;
printf("shl of %u: %u(%08x)\n", b, bs, bs);
}
```
```sh {cmd hide}
while ! [ -f 1_1.out ]; do sleep .1; done; ./1_1.out
```
## Integers
### Representation
### Representation & Encoding
* for $w$ bits data $x$
$$B2U(X)=\sum_{i=0}^{w-1} x_i 2^{i}\quad B2T(X)=-x_{w-1}*2^{w-1} + \sum_{i=0}^{w-2}{x_i 2^i}$$
### Conversion

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

View File

@@ -1,6 +1,207 @@
# Machine Level Programming
# Floating Point
아키텍쳐(ISA)
* intel(x86): CISC
* ARM(aarch64, aarch32): RISC
## Fractional Binary Number
representation:
* for $w = i + j + 1$ bits data $b$
$$\sum_{k = -j}^{i}b_k\times 2^k$$
for example:
* $5+3/4 = 23/4 = 101.11_2$
* $1 7/16 = 23/16 = 1.0111_2$
**Limitations**
* Can only exactly represent numbers of the form of $x/2^k$
* Just one setting of binary point within the $w$ bits, which means that very small value or very large value cannot be represented
## IEEE Floating Point Definition
**IEEE Standard 754**
Driven by numerical concerns:
* Nice standards for rounding, overflow, underflow
* But Hard to make fast in hardware
* Numberical Analysts predominated over hw designers in defining standard
### Representation
**Form**
$$(-1)^s M 2^E$$
* $s$: sign bit
* $M$: mantissa fractional value in $[1.0,2.0)$
* $E$: exponent
**Encoding**
```mermaid
---
title: "Single Precision"
config:
packet:
bitsPerRow: 32
rowHeight: 32
---
packet
+1: "s"
+8: "exp"
+23: "frac"
```
```mermaid
---
title: "Double Precision"
config:
packet:
bitsPerRow: 32
rowHeight: 32
---
packet
+1: "s"
+11: "exp"
+52: "frac"
```
There is three kinds of `float`: **normalized**, **denormalized**, **special**
**normalized**
$E = \exp - B$
$B = 2^{k-1}-1$ where $k$ is number of exp bits
* single: 127
* double: 1023
$M = 1.xxxxx$
minumum when $\text {frac} = 0000...\quad (M = 1.0)$
maximum when $\text{frac }= 1111... \quad (M = 2.0 - \epsilon)$
**denormalized**
when `exp=000...0`
$\exp = 1 - Bias$
$M = 0.xxxxx$
**special**
when `exp = 111...1`
* case `exp = 111...1, frac = 000...0`
* repr $\infty$
* operation that overflows
* case `exp = 111...1, frac = 111...1`
* repr `NaN`
* repr case when no numeric value can be determined
* e.g., `sqrt(-1)`, `inf - inf`, `inf * 0`
```c {cmd="gcc-14" args=[-x c $input_file --std=c23 -O0 -m32 -o 2_1.out]}
#include <stdio.h>
int main() {
unsigned x_a = 0b0'11111111'00000000000000000000000;
unsigned x_b = 0b0'11111111'00000000000000000000001;
unsigned x_c = 0b0'01111111'00000000000000000000000;
float a = *(float*)&x_a;
float b = *(float*)&x_b;
float c = *(float*)&x_c;
double cx = c;
printf("%08x: %f\n", x_a, a);
printf("%08x: %f\n", x_b, b);
printf("%08x: %f\n", x_c, c);
printf("%016llx: %f\n", *(unsigned long long *)&cx, cx);
return 0;
}
```
```sh {cmd hide}
while ! [ -f 2_1.out ]; do sleep .1; done; ./2_1.out
```
### Properties
* FP0 is Same as Int0
* Can (almost) use unsigned int comparison
### Arithmetic
$x + y = \text{Round}(x+y)$
$x \times y = \text{Round}(x\times y)$
Idea:
1. compute exact result
2. Make it fit into desired precision
* overflow if too large
* **round** to fit into frac
#### Rounding
* Twowards zero
* Round down
* Round up
* **Nearest Even***(default)
**Nearest Even** is default rounding mode
Any other kind rounding mode is hard to get without dropping into assembly, but C99 has support for rounding mode management.
This rounding mode is used because **reduced statistically bias**.
For binary fractional numbers:
* "Even" when least significant bit is $0$
* "Half way" when bits to right of rounding position is $100..._2$
so for example of rounding to neareast $1/4$:
| Binary Value | Rounded | Action |
| ------------ | ------- | ---------- |
| `10.00011` | `10.00` | (<1/2)Down |
| `10.00110` | `10.01` | (>1/2)Up |
| `10.11100` | `11.00` | (=1/2)Up |
| `10.10100` | `10.10` | (=1/2)Down |
`BBGRXXX`
* `G`: **G**uard bit: LSB of result
* `R`: **R**ound bit: first bit of removed
* `X`: **S**ticky bits: OR of remaining bits(001 = 1, 000 = 0)
Round up conditions
1. R = 1, S = 1 -> `>.5`
2. G = 1, R = 1, S = 0 -> Round to even
```c {cmd="gcc-14" args=[-x c $input_file --std=c23 -O0 -m32 -o 2_2.out]}
#include <stdio.h>
int main() {
unsigned long long
tb = 0b0'10000010000'0000000000000000000001010000000000000000000000000000;
unsigned xb = 0b0'10000001'01000000000000000000011;
double t = *(double*)&tb;
float x = t;
for(int i=31; i>=0;i--) {
if(i == 31 - 1) {
printf("/");
} else if (i == 31 - 1 - 8){
printf("/");
}
printf("%d", !!((*(unsigned *)&x) & (1<<i)));
}
printf("\n");
printf("%f", x);
}
```
```sh {cmd hide}
while ! [ -f 2_2.out ]; do sleep .1; done; ./2_2.out
```
## Float Quiz

793
notes/3.md Normal file
View File

@@ -0,0 +1,793 @@
# Machine Level Programming
## History of Intel Processors
* Eveolutionary design: **Backwards compatible** up until `8086` in 1978
* **C**omplex **I**nstruction **S**et **C**omputer (CISC)
**RISC vs CISC**
* CISC has variable length instructions
* RISC has constant length instructions
1. Intel x86(8086)
1. IA32 to IA64
2. (after x86-64) EM64T(almost same as AMD x86-64)
2. AMD x86-64
## C, Assembly, machine code
* ***Architecture***: The parts of a processor design that one needs to understand or write assembly/machine code
* **ISA(Instruction Set Architecture)**
* e.g., x86, IA32, Itanium, x86-64, ARM
* ***Microarchitecture***: Implementation of the architecture
form of code:
* Machine Code: the byte-level programs that a processor executes
* Assembly Code: A text representation of machine code
### Assembly/Machine Code View
Programmer-Visible State (shown by ISAs)
* PC(Program Counter)
* Address of next instruction
* RIP in (x86-64)
* Register file
* Heavily used program data
* Condition codes
* store status information about most recent arithmetic or logical op
* Used for conditional branching
* Memory(external)
Compiling Into Assembly
```c {cmd=gcc, args=[-Og -x c -c $input_file -o 3_1.o]}
long plus(long x, long y);
void sumstore(long x, long y, long *dest) {
long t = plus(x, y);
*dest = t;
}
```
```sh {cmd hide}
while ! [ -r 3_1.o ]; do sleep .1; done; objdump -d 3_1.o
```
### Integer Registers
* In x86-64
`ax` `bx` `cx` `dx` `si` `di` `sp` `bp` (in 8bytes `r` 4bytes `e`)
`r8` `r9` `r10` `r11` `r12` `r13` `r14` `r15` (in 4bytes: add `d`)
* In IA32
`eax`(32bit): 16bit `ax`(`ah`, `al`); origin: accumulate
`ecx`(32bit): 16bit `cx`(`ch`, `cl`); origin: counter
`edx`(32bit): 16bit `bx`(`bh`, `bl`); origin: data
`ebx`(32bit): 16bit `dx`(`dh`, `dl`); origin: base
`esi`(32bit): 16bit `si`(`sih`, `sil`); origin: source index
`edi`(32bit): 16bit `di`(`dil`, `dil`); origin: destination index
`esp`(32bit): 16bit `sp`(`spl`, `spl`); origin: stack pointer
`ebp`(32bit): 16bit `bp`(`bpl`, `bpl`); origin: base pointer
#### understanding `movq`
In x86asm, There are three operand types: **immediate, register, memory**
* immediate: constant integer data like `$0x400` `$-533`
* register: one of 16 integer regs
* e.g., `%rax`, `%r13`
* but `%rsp` is reserved for special use
* memory: 8 bytes of memory at address given by register
* e.g., `(%rax)`
**movq usage**
`movq $src, $dest`
* limit: Cannot do memory-memory transfer with a single instruction(because memory is external device to cpu)
**memory addressing modes**
`(R)` means `Mem[Reg[R]]`
`D(R)` means `Mem[Reg[R]+D]`, constant displacement `D` specifies offset
`movq 8(%rbp), %rdx`
<table>
<tr>
<td>
```c
int swap (long *xp, long *yp) {
long t0 = *xp;
long t1 = *yp;
*xp = t1;
*yp = t0;
}
```
</td>
<td>
```nasm
swap:
movq (%rdi), %rax
movq (%rsi), %rdx
movq %rdx, (%rdi)
movq %rax, (%rsi)
ret
```
</td>
</tr>
</table>
Complete form of memory addressing modes:
`D(Rb, Ri, S)` means `Mem[Reg[Rb] + S*Reg[Ri] + D]`
* `D`: Constant "displacement"
* `Rb`: Base Register
* `Ri`: Index Register
* `S`: Scale Factor(1, 2, 4, or 8)
for example:
| `%rdx` | `%rcx` |
| -------- | -------- |
| `0xf000` | `0x0100` |
* `0x8(%rdx)` = `0xf008`
* `(%rdx, %rcx)` = `0xf100`
* `(%rdx, %rcx, 4)` = `0xf400`
* `0x80(,%rdx, 2)` = `0x1e080`
#### Arithmetic & Logical Operations
* `leaq $src, $dst`
* computing address without memory reference like `p = &x[i]`
* computing arithmetic expression `x + k * y`
* `addq $src, $dst`
* `subq $src, $dst`
* `imulq $src, $dst`
* `salq $src, $dst`
* `sarq $src, $dst`
* `shrq $src, $dst`
* `xorq $src, $dst`
* `andq $src, $dst`
* `orq $src, $dst`
all the above operator operates like `dest = dest # src`
* `incq $dest`
* `decq $dest`
* `negq $dest`
* `notq $dest`
## Control
**Processor State(x86-64, Partial)**
* Temporary data(`%rax`, ...)
* Location of runtime stack(`%rsp`)
* Location of current code control point(`%rip`, instruction point)
* Status of recent tests(`CF`, `ZF`, `SF`, `OF`)
### Condition Codes
* Single bit registers
* `CF` Carry flag (for unsigned)
* `SF` Sign flag (for signed)
* `ZF` Zero flag
* `OF` Overflow flag (for signed)
**Conditional Codes(Implicit Setting)**
Implicit setting is codes are set by arithmetic operations(`addq`, `subq`, `mulq`)
for example: `addq`: `t = a + b`
* `CF` set if carry out from most significant bit or unsigned overflow
* `ZF` set if `t == 0`
* `SF` set if `t < 0` (as signed)
* `OF` set if two's-complement overflow or signed overflow
`(a > 0 && b > 0 && (a + b) < 0) || (a < 0 && b < 0 && (a + b) >= 0)`
The codes are not implictly set by `leaq`, because it is not designed to be used as arithmetic but used as **address calculation**. so it cannot affect to conditional codes.
**Conditional Codes(Explicit Setting)**
The codes are set explictly by compare instruction.
`cmpq b, a` is computing `a - b` without setting destination.
* `CF` set if carry out from most significant bit or unsigned overflow
* `ZF` set if `a == b` or `a - b == 0`
* `SF` set if `(a - b) < 0` (as signed)
* `OF` set if two's-complement overflow or signed overflow
`(a > 0 && b > 0 && (a - b) < 0) || (a < 0 && b < 0 && (a - b) >= 0)`
And explictly set by test instruction
`testq b, a` is computing `a & b` without setting destination.
Sets condition codes based on value of `a & b` it is useful to have one of the operands be a mask.
* `ZF` set when `a & b == 0`
* `SF` set when `a & b < 0`
**Reading Condition Codes**
`setX`: set single byte based on combination of condition codes
| setX | effect | desc |
| ------- | ---------------- | ------------------------- |
| `sete` | `ZF` | Equal / Zero |
| `setne` | `~ZF` | Not Equal / Not Zero |
| `sets` | `SF` | Negative |
| `setns` | `~SF` | Nonnegative |
| `setg` | `~(SF^OF) & ~ZF` | Greater (signed) |
| `setge` | `~(SF^OF)` | Greater or Equal (signed) |
| `setl` | `SF^OF` | Less (signed) |
| `setle` | `(SF^OF) \| ZF` | Less or Equal (signed) |
| `seta` | `~CF & ~ZF` | Above (unsigned) |
| `setb` | `CF` | Below (unsigned) |
it deos not alter remaining bytes of registers. only use 1 byte register(`%al`, `%bl`)
```nasm
cmpq %rsi(y), %rdi(x) # compare x and y
setg %al # set when >(greater)
movzbl %al, %eax # move zero extend byte to long
ret
```
### Conditional Branches
#### Jumping
`jX` jump to different part of code depending on condition codes.
| jX | condition | desc |
| ----- | ---------------- | ------------------------- |
| `jmp` | 1 | Unconditional |
| `je` | `ZF` | Equal / Zero |
| `jne` | `~ZF` | Not Equal / Not Zero |
| `js` | `SF` | Negative |
| `jns` | `~SF` | Nonnegative |
| `jg` | `~(SF^OF) & ~ZF` | Greater (signed) |
| `jge` | `~(SF^OF)` | Greater or Equal (signed) |
| `jl` | `SF^OF` | Less (signed) |
| `jle` | `(SF^OF) \| ZF` | Less or Equal (signed) |
| `ja` | `~CF & ~ZF` | Above (unsigned) |
| `jb` | `CF` | Below (unsigned) |
Old Style Conditional Branch
```c {cmd=gcc args=[-Og -x c -fno-if-conversion -c $input_file -o 3_3.o]}
long absdiff(long x, long y) {
long result;
if (x > y) result = x - y;
else result = y - x;
return result;
}
```
```sh { cmd hide }
while ! [ -r 3_3.o ]; do sleep .1; done; objdump -d 3_3.o -Msuffix
```
**expressing with `goto`**
```c {cmd=gcc args=[-Og -x c -rno-if-conversion -c $input_file -o 3_4.o]}
long absdiff_j(long x, long y) {
long result;
int ntest = x <= y;
if (ntest) goto Else;
result = x-y;
goto Done;
Else:
result = y-x;
Done:
return result;
}
```
#### Conditional Move
But this branchings are very disruptive to instruction flow through pipelines, **Conditional Moves** are highly used because they do not require control transfer.
```c {cmd=gcc args=[-O3 -x c -c $input_file -o 3_5.o]}
long absdiff(long x, long y) {
long result;
if (x > y) result = x - y;
else result = y - x;
return result;
}
```
```sh {cmd hide}
while ! [ -r 3_5.o ]; do sleep .1; done; objdump -d 3_5.o -Msuffix
```
However, there are several *bad cases* for conditional move.
* expansive computations
```c
val = Test(x) ? Hard1(x) : Hard2(x);
```
because both values are get computed. only simple computations are effective for conditional moves.
* risky computations
```c
val = p ? *p : 0;
```
both values get computed may have undesiarable effects.
* Computations with side effects
```c
val = x > 0 ? x*=7 : x+=3;
```
each expression has side-effect.
### Loop
#### do-while
<table>
<tr>
<td>
```c
long pcount_do(unsigned long x) {
long result = 0;
do {
result += x & 0x1;
x >>= 1;
} while (x);
return result;
}
```
</td>
<td>
```c {cmd=gcc args=[-Og -x c -c $input_file -o 3_6.o]}
long pcount_goto(unsigned long x) {
long result = 0;
loop:
result += x & 0x1;
x >>= 1;
if (x) goto loop;
return result;
}
```
</td>
</tr>
</table>
```sh {cmd hide}
while ! [ -r 3_6.o ]; do sleep .1; done; objdump -d 3_6.o -Msuffix
```
**general do-while translation**
<table>
<tr>
<td>
```c
do {
Body
} while (Test);
```
</td>
<td>
```c
loop:
Body
if (Test) goto loop;
```
</td>
</tr>
</table>
#### while
**general while translation#1**
it is called **jump-to-middle translation**, used with `-O0` (or `-Og`) flag.
<table>
<tr>
<td>
```c
while(Test) {
Body
}
```
</td>
<td>
```c
goto test;
loop:
Body
test:
if (Test)
goto loop;
done:
```
</td>
</tr>
</table>
```c {cmd=gcc args=[-Og -x c -c $input_file -o 3_7.o]}
long pcount_while(unsigned long x) {
long result = 0;
while (x) {
result += x & 0x1;
x >>= 1;
}
return result;
}
```
```sh {cmd hide}
echo "jmp-to-middle translation"
while ! [ -r 3_7.o ]; do sleep .1; done; objdump -d 3_7.o -Msuffix
```
**general while translation#2**
while to do-while conversion, used with `-O1` flag.
<table>
<tr>
<td>
```c
while(Test) {
Body
}
```
</td>
<td>
```c
if (!Test) goto done;
do {
Body
} while (Test);
done:
```
</td>
<td>
```c
if (!Test) goto done;
loop:
Body
if (Test) goto loop;
done:
```
</td>
</tr>
</table>
```c {cmd=gcc args=[-O1 -x c -c $input_file -o 3_8.o]}
long pcount_while(unsigned long x) {
long result = 0;
while (x) {
result += x & 0x1;
x >>= 1;
}
return result;
}
```
```sh {cmd hide}
echo "while to do-while conversion"
while ! [ -r 3_8.o ]; do sleep .1; done; objdump -d 3_8.o -Msuffix
```
#### for loop form
<table>
<tr>
<td>
```c
for (init; test; update) {
Body
}
```
</td>
</tr>
</table>
**for-to-while conversion**
<table>
<tr>
<td>
```c
for (Init; Test; Update) {
Body
}
```
</td>
<td>
```c
Init;
while(Test) {
Body
Update;
}
```
</td>
</tr>
<tr>
<td>
```c {cmd=gcc args=[-O3 -x c -c $input_file -o 3_9.o]}
#include <stddef.h>
#define WSIZE 8 * sizeof(int)
long pcount_for(unsigned long x) {
size_t i;
long result = 0;
for (i = 0; i < WSIZE; i++) {
unsigned bit = (x >> i) & 0x1;
result += bit;
}
return result;
}
```
</td> <td>
```c {cmd=gcc args=[-O3 -x c -c $input_file -o 3_10.o]}
#include <stddef.h>
#define WSIZE 8 * sizeof(int)
long pcount_for(unsigned long x) {
size_t i;
long result = 0;
i = 0;
while(i < WSIZE) {
unsigned bit = (x >> i) & 0x1;
result += bit;
i++;
}
return result;
}
```
</td>
</tr>
<tr>
<td>
```sh {cmd hide}
while ! [ -r 3_9.o ]; do sleep .1; done; objdump -d 3_9.o -Msuffix
```
</td>
<td>
```sh {cmd hide}
while ! [ -r 3_10.o ]; do sleep .1; done; objdump -d 3_10.o -Msuffix
```
</td>
</tr>
</table>
for to do-while conversion, initial test can be optimized away.
### Switch
#### Jump Table Structure
Switch form
<table>
<tr>
<td>
```c {cmd=gcc args=[-Og -fno-asynchronous-unwind-tables -fno-stack-protector -x c -S $input_file -o 3_11.s]}
long switch_eg (long x, long y, long z) {
long w = 1;
switch(x) {
case 1:
w = y*z;
break;
case 2:
w = y/z;
/* Fall Through */
case 3:
w += z;
break;
case 5:
case 6:
w -= z;
break;
case 7:
w *= z;
break;
default:
w = 2;
}
return w;
}
```
</td>
<td>
```sh {cmd hide}
while ! [ -r 3_11.s ]; do sleep .1; done; cat 3_11.s
```
</td>
</tr>
</table>
## Procedures
Mechanisms in Procedures
* **Passing control**
* to beginning of procedure code
* back to return point
* **Passing data**
* procedure arguments
* return value
* **Memory management**
* allocate during procedure execution
* deallocate upon return
this mechanisms are all implemented with machine instructions. **x86-64 implementation** of a procedure used only those mechanisms required.
### Stack Structure
**x86-64 Stack**
Region of memory managed with *stack discipline*. It grows toward lower addresses. `%rsp` contains lowest stack address(address of top element).
`pushq $src`
* fetches operand at src
* decrement `%rsp` by 8
* write operand at address given by `%rsp`
`popq $dest`
* read value at address given by `%rsp`
* increment `%rsp` by 8
* store value at dest(must be register)
### Procedure Control Flow
```c {cmd=gcc args=[-Og -x c -c $input_file -o 3_12.o]}
long mult2(long x, long y) {
long t = x * y;
return t;
}
void multstore(long x, long y, long *dest) {
long t = mult2(x, y);
*dest = t;
}
```
```sh {cmd hide}
while ! [ -r 3_12.o ]; do sleep .1; done; objdump -d 3_12.o -Msuffix
```
Procedure call `call label`
* push return address on stack
* jmp to label
Return address:
* Address of the next instruction right after call
Procedure return: `ret`
### Procedure Data Flow
* registers
* first 6 args: `%rdi`, `%rsi`, `%rdx`, `%rcx`, `%r8`, `%r9`
* return value: `rax`
* stack
for example with above example
```sh {cmd hide}
while ! [ -r 3_12.o ]; do sleep .1; done; objdump -d 3_12.o -Msuffix
```
* with above `mult2` variable `t` is already stored in `%rax`
* so `movq %rax,(%rbx)` where `%rbx` is `long*dest`
### Managing local data
**Stack-Based Languages**
In languages that support recursion
* Code must be "reentrant", which means multiple simultaneous instantiations of single procedure.
* Need some place to store ***state*** of each instantiation: (**args**, **local variables**, **return pointer**)
In order to get this, **stack discipline** is used. state for given procedure needed for limited time(from called to return): Calle returns before caller does.
Stack allocated in **frames**, state for single procdure instantiation.
When function is called, a new stack frame is created above stack top. And then when the function is returned, a corresponding frame is popped. and return to previous call state.
#### Stack Frame
is consist of **return information**, **local storage(if needed)** and **temporary space(if needed)**.
* `%rbp` frame pointer
* `%rsp` stack pointer
Space allocated when enter procedure, "set-up" code and includes push by `call`.
Deallocated when return, "finish" code and includes pop by `ret`.
#### x86-64/Linux Stack Frame
![stack frame image](/assets/3_1stackframe.png)
* Arguments
* Local variables
* Old `rbp`
### Register Saving Conventions
When calling function, the temporary value of registers could be removed by called function, it could be trouble. So there are **conventions** to save the registers value.
When procedure `yoo` calls `who`: `yoo` is `caller`, `who` is `callee`
* Caller saves temporary values in its frame before the call.
* Callee saves saves temporary values in its frame before using and restores them before returning to caller.
#### x86-64 Linux Register Usage
`%rbx`, `%r12`, `%r13`, `%r14`, `%r15`
* Callee-saved
* Callee must save & restore
`%rbp`
* Callee-saved
* Callee must save & restore
* May be used as frame pointer by callee
* Can mix & match
`%rsp`
* Special form of callee-saved
* Restored to original value upon exit from procedure
#### EX
* for compile w/o *stack canary*, add option `-fno-stack-protector`
```c {cmd=gcc args=[-Og -x c -fno-stack-protector -c $input_file -o 3_13.o]}
long incr(long *p, long val) {
long x = *p;
long y = x + val;
*p = y;
return x;
}
long call_incr() {
long v1 = 15213;
long v2 = incr(&v1, 3000);
return v1 + v2;
}
```
```sh {cmd hide}
while ! [ -r 3_13.o ]; do sleep .1; done; objdump -d 3_13.o -Msuffix
```
### Recursive Function
```c {cmd=gcc args=[-O1 -x c -fno-stack-protector -c $input_file -o 3_14.o]}
long pcount_r(unsigned long x) {
if (x == 0) {
return 0;
} else {
return (x & 1) + pcount_r(x >> 1);
}
}
```
```sh {cmd hide}
while ! [ -r 3_14.o ]; do sleep .1; done; objdump -d 3_14.o -Msuffix
```
Recursion is not a special function.
* Stack frames mean that each function call has private storage.
* Register saving conventions prevent one function call from corrupting another's data. *unless the explictly corrupting like buffer overflow*
* Stack discipline follows call/return pattern LIFO

284
notes/4.md Normal file
View File

@@ -0,0 +1,284 @@
# Optimization
There's more to performance than asymptotic complexity(time complexity).
But all the instructions are not consume the same amount of time. Constant factors matter too! So we need to understand system to optimize performance.
* How programs are compiled and executed
* How modern processors and memory system operate
* How to measure performance and identify bottlenecks
* How to improve performance without destroying code modularity and generality
Provide efficent mapping of program to machine code
* Register allocation
* Code selection and ordering (scheduling)
* Dead code elimination
* Elimininating minor inefficiencies
**Don't improve asymptotic efficiency**.
## Generally Useful Optimizations
### Code Motion(Hoisting)
Reduce frequecy where computation performed. If it will always produce the same result, then move it to a place where it is computed once and reused.
Especially moving code out of loop.
```c {cmd=gcc args=[-Og -x c -c $input_file -o 4_1.o]}
void set_row(double *a, double *b, long i, long n) {
long j;
for (j = 0; j < n; j++) {
a[i * n + j] = b[j];
}
}
```
<table>
<tr><th>Default</th><th>Optimized</th></tr>
<tr><td>
```c {cmd=gcc args=[-O1 -x c -c $input_file -o 4_2.o]}
void set_row(double *a, double *b, long i, long n) {
long j;
for (j = 0; j < n; j++) {
a[i * n + j] = b[j];
}
}
```
</td><td>
```c
void set_row_opt(double *a, double *b, long i, long n) {
long j;
int ni = n * i;
for (j = 0; j < n; j++) {
a[ni + j] = b[j];
}
}
```
</td></tr>
<tr>
<td>
```sh {cmd hide}
while ! [ -r 4_1.o ]; do sleep .1; done; objdump -d 4_1.o
```
`imul` is located in the loop.
</td>
<td>
```sh {cmd hide}
while ! [ -r 4_2.o ]; do sleep .1; done; objdump -d 4_2.o
```
can see that `imul` is located out of the loop.
</td>
</tr>
</table>
Above two codes have same number of instructions. But optimized version has **fewer executed instructions**.
GCC will do this with `-O1` options
### Reduction in Strength
Replace costly operation with simpler one.
for example: power of 2 multiply to shift operation. normally, multiply and divide are expensive exmaple. on Intel Nehalem, `imul` requires 3 CPU cylcles on the other hand, `add` requires 1 cycle.
<table>
<tr><th>Default</th><th>Optimized</th></tr>
<tr><td>
```c
void test_reduction(double *a, double *b, long i, long n) {
int i, j;
for (i = 0;i < n; i++) {
int ni = n * i;
for (j = 0; j < n; j++) {
a[ni + j] = b[j];
}
}
}
```
</td><td>
```c
void test_reduction_opt(double *a, double *b, long i, long n) {
int i, j;
int ni = 0;
for (i = 0;i < n; i++) {
for (j = 0; j < n; j++) {
a[ni + j] = b[j];
}
ni += n;
}
}
```
</td></tr>
</table>
### Share Common Subexpressions
Reuse portations of expressions
GCC will do this with `-O1`
<table>
<tr><th>Default</th><th>Optimized</th></tr>
<tr><td>
```c {cmd=gcc args=[-O1 -x c -c $input_file -o 4_3.o]}
double test_scs(double* val, long i, long j, long n) {
double up, down, left, right;
up = val[(i - 1) * n + j];
down = val[(i + 1) * n + j];
left = val[i * n + (j - 1)];
right = val[i * n + (j + 1)];
return up + down + left + right;
}
```
</td><td>
```c
double test_scs_opt(double *a, double *b, long i, long n) {
double up, down, left, right;
long inj = i * n + j;
up = a[inj - n];
down = a[inj + n];
left = b[inj - 1];
right = b[inj + 1];
return up + down + left + right;
}
```
</td></tr>
</table>
```sh {cmd hide}
while ! [ -r 4_3.o ]; do sleep .1; done; objdump -d 4_3.o
```
Above dump shows only one `imul`, which shows that share common subexpressions are applied.
### Remove Unnecessary Procedure
Think with your intuition.
## Optimization Blockers
Compilers cannot always optimize your code.
```c
void lower(char *s) {
size_t i;
for (i = 0; i < strlen(s); i++) {
if (s[i] >= 'A' && s[i] <= 'Z') {
s[i] -= ('A' - 'a');
}
}
}
```
Above code's performance is bad. time quadruples when double string length.
Because `strlen` is executed on every loop. so `strlen` is $O(n)$, therefore overall performance of `lower` is $O(n^2)$
Therefore we optimized by Code Motion by moving the calculation length parts to out of the loop.
```c
void lower(char *s) {
size_t i;
size_t len = strlen(s);
for (i = 0; i < len; i++) {
if (s[i] >= 'A' && s[i] <= 'Z') {
s[i] -= ('A' - 'a');
}
}
}
```
### #1 Procedure Calls
Procedure may have side effects. and Function may not return same value for given arguments.
So compiler treats procedure call as a black box. Weak optimizations near them. Therefore strong optimizations like **Code Motion** are not applied.
In order to apply strong optimizations, First, use of inline function with `-O1` option, or **do your self**.
### Memory Aliasing
```c {cmd=gcc args=[-O1 -x c -c $input_file -o 4_4.o]}
void sum_rows(double *a, double *b, long n) {
long i, j;
for (i = 0; i < n; i++) {
b[i] = 0;
for (j = 0; j < n; j++) {
b[i] += a[i * n + j];
}
}
}
```
```sh {cmd hide}
while ! [ -r 4_4.o ]; do sleep .1; done; objdump -d 4_4.o -Msuffix
```
Compiler leave `b[i]` on every iteration. Because compiler must consider possibility that the updates will affect program behavior. (`b` and `a` is shared, memory aliasing)
Memory aliasing means two different memory references specify single location.
in C, it is easy to have happen. because address arithmetic and direct access to storage structures.
```c {cmd=gcc args=[-O1 -x c -c $input_file -o 4_5.o]}
void sum_rows(double *a, double *b, long n) {
long i, j;
for (i = 0; i < n; i++) {
double val = 0;
for (j = 0; j < n; j++) {
val += a[i * n + j];
}
b[i] = val;
}
}
```
```sh {cmd hide}
while ! [ -r 4_5.o ]; do sleep .1; done; objdump -d 4_5.o -Msuffix
```
By introducing local local variables, we can easy to get optimized code.
## Exploiting Instruction-Level Parallelism(ILP)
Execute multiple instructions at the same time. it can reduce average instruction cycle, which needs general understanding of modern processor design: HW can execute many operations in parallel.
* performance limited by data dependency
simple transformations can yield dramatic performance improvement.
### Superscalar Processors
Issue and Execute Multiple Instructions in one cycle.
pipelining -> data dependency.
for example Haswell CPU Functional Units
* 2 load
* 1 store
* 4 integer
* 2 FP mult
* 1 FP add
* 1 FP div
* 1 int mult
### Programming with AVX2
YMM register: 256bit, total 16 registers.
**SIMD Operations**
for single precision
`vaddps %ymm0, %ymm1, %ymm1`:
for double precision
`vaddpd %ymm0, %ymm1, %ymm1`

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
out/notes/1.pdf (Stored with Git LFS) Normal file

Binary file not shown.

BIN
out/notes/2.pdf (Stored with Git LFS) Normal file

Binary file not shown.

BIN
out/notes/3.pdf (Stored with Git LFS) Normal file

Binary file not shown.

BIN
out/notes/4.pdf (Stored with Git LFS) Normal file

Binary file not shown.

6941
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

5
package.json Normal file
View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"crossnote": "^0.9.15"
}
}

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/L4.pdf (Stored with Git LFS) Normal file

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

BIN
pdf/L9.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.