├── practices ├── practice2 │ ├── examples │ │ ├── hello.sh │ │ ├── vars.sh │ │ ├── arithmetic.sh │ │ ├── ls.sh │ │ ├── functions.sh │ │ └── switch.sh │ └── practice2.md ├── practice1 │ ├── examples │ │ ├── main.c │ │ ├── hello1.c │ │ ├── hello2.c │ │ └── Makefile │ └── practice1.md └── practice3 │ ├── examples │ ├── Makefile │ ├── task3_easy.c │ └── task3.c │ └── practice3.md ├── lectures ├── OS-Init.pdf ├── lecture6.md ├── lecture3.md ├── lecture9.md ├── lecture0.md ├── lecture5.md ├── lecture4.md ├── lecture8.md ├── lecture7.md ├── lecture11.md ├── lecture10.md └── lecture2.md └── README.md /practices/practice2/examples/hello.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo hello 3 | -------------------------------------------------------------------------------- /practices/practice2/examples/vars.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | A=5 3 | echo $A "$A" '$A' 4 | -------------------------------------------------------------------------------- /practices/practice2/examples/arithmetic.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | N=3 3 | echo $((2+4+$N)) 4 | -------------------------------------------------------------------------------- /practices/practice2/examples/ls.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | for i in $(ls); do 3 | echo $i 4 | done 5 | -------------------------------------------------------------------------------- /lectures/OS-Init.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandwwraith/os2016-conspect/HEAD/lectures/OS-Init.pdf -------------------------------------------------------------------------------- /practices/practice1/examples/main.c: -------------------------------------------------------------------------------- 1 | void main (int argc, char **argv) { 2 | hello1(); 3 | hello2(); 4 | } 5 | -------------------------------------------------------------------------------- /practices/practice1/examples/hello1.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void hello1() { 4 | printf("Hello World!\n"); 5 | } 6 | -------------------------------------------------------------------------------- /practices/practice1/examples/hello2.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void hello2() { 4 | printf("Hello World!\n"); 5 | } 6 | -------------------------------------------------------------------------------- /practices/practice2/examples/functions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | add() { 3 | echo $* 4 | echo $(($1+$2)) 5 | } 6 | add 2 3 7 | -------------------------------------------------------------------------------- /practices/practice2/examples/switch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | A=3 3 | case $A in 4 | 3) echo "IT'S THREE" 5 | ;; 6 | *) echo "It's not three" 7 | ;; 8 | esac 9 | -------------------------------------------------------------------------------- /practices/practice3/examples/Makefile: -------------------------------------------------------------------------------- 1 | all: task3 task3_easy 2 | 3 | task3: task3.c 4 | gcc task3.c -o task3 5 | 6 | task3_easy: task3_easy.c 7 | gcc task3_easy.c -o task3_easy 8 | 9 | clean: 10 | rm -f task3 task3_easy 11 | -------------------------------------------------------------------------------- /practices/practice3/examples/task3_easy.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | void main(int argc, char* argv[]) { 6 | int pipefd[2]; 7 | pipe(pipefd); 8 | pid_t cpid; 9 | cpid = fork(); 10 | if (cpid == 0) { 11 | close(pipefd[0]); 12 | dup2(pipefd[1], STDOUT_FILENO); 13 | close(pipefd[1]); 14 | execlp("cat", "cat", argv[1], NULL); 15 | } 16 | else { 17 | close(pipefd[1]); 18 | dup2(pipefd[0], STDIN_FILENO); 19 | close(pipefd[0]); 20 | execlp("grep", "grep", "int", NULL); 21 | } 22 | return; 23 | } 24 | -------------------------------------------------------------------------------- /practices/practice1/examples/Makefile: -------------------------------------------------------------------------------- 1 | all: main 2 | 3 | main: main.o libhello1.a libhello2.so 4 | gcc -s main.o -o main -L. -lhello1 -lhello2 5 | 6 | main.o: main.c 7 | gcc main.c -o main.o -c 8 | 9 | libhello1.a: hello1.o 10 | ar rcs libhello1.a hello1.o 11 | 12 | hello1.o: hello1.c 13 | gcc hello1.c -o hello1.o -c 14 | 15 | libhello2.so: hello2.o 16 | gcc -fPIC hello2.c -c -o hello2.o 17 | gcc -shared hello2.o -o libhello2.so 18 | 19 | hello2.o: hello2.c 20 | gcc hello2.c -o hello2.o -c 21 | 22 | clean: 23 | rm -f hello1.o hello2.o libhello1.a libhello2.so main.o main 24 | -------------------------------------------------------------------------------- /practices/practice3/examples/task3.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | void main(int argc, char* argv[]) { 6 | int pipefd[2]; 7 | pipe(pipefd); 8 | pid_t cat, grep; 9 | int cat_status, grep_status; 10 | cat = fork(); 11 | if (cat == 0) { 12 | close(pipefd[0]); 13 | dup2(pipefd[1], STDOUT_FILENO); 14 | close(pipefd[1]); 15 | execlp("cat", "cat", argv[1], NULL); 16 | } 17 | else { 18 | grep = fork(); 19 | if (grep == 0) { 20 | close(pipefd[1]); 21 | dup2(pipefd[0], STDIN_FILENO); 22 | close(pipefd[0]); 23 | execlp("grep", "grep", "int", NULL); 24 | } 25 | else { 26 | waitpid(cat, &cat_status, WIFEXITED(cat_status)); 27 | waitpid(grep, &grep_status, WIFEXITED(grep_status)); 28 | } 29 | } 30 | return; 31 | } 32 | -------------------------------------------------------------------------------- /practices/practice3/practice3.md: -------------------------------------------------------------------------------- 1 | #Задание 2 | 3 | Написать программу на `C`, эквивалентную команде `cat $1 | grep int`. 4 | Рабочий код в файле: `task3_easy.c` 5 | 6 | #Подсказки 7 | 8 | В этом поможет функция `exec`, а точнее её спецификация `execlp`. 9 | Более подробная инфа: `man exec`. 10 | 11 | ```c 12 | int execlp("cat", "cat", argv[1], NULL) 13 | ``` 14 | 15 | Также надо использовать `pipe` и `dup2`. 16 | 17 | ```c 18 | int pipe(int pipefd[2]); 19 | int dup2(int oldfd, int newfd); 20 | ``` 21 | 22 | `dup2` закрывает newfd и в fd с таким же номером фигачит копию oldfd. 23 | 24 | #Модификация (*) 25 | 26 | Так как выполнение `cat` зависит от того, жив ли `grep`, то неплохо бы сделать dummy-процесс, который будет держать и `cat`, и `grep`, пока они оба не выполнятся. Это и попросили реализовать. 27 | 28 | Рабочий код в файле: `task3.c`. 29 | 30 | #Подсказки 31 | 32 | Тут нам поможет команда `waitpid`. 33 | 34 | ```c 35 | pid_t waitpid(pid_t pid, int *status, int options); 36 | ``` 37 | 38 | Тут есть особая проблема с тем, чтобы правильно подобрать `options`. Мы столкнулись на практике с тем, что на разных Линухах процессы выходят по разному. У Кича работало с опцией `WEXITED`, у меня — с `WIFEXITED(status)`. У Каменева — просто подлива. 39 | 40 | Я вообще вызывал `wait`, а не `waitpid`. Но тоже от трёх аргументов, в опции передавал 0. На Дебиане работает. 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Конспект лекций по курсу операционных систем 2 | 3 | *ИТМО, весна 2016* 4 | 5 | **Disclaimer:** конспекты не претендуют на академическую ценность, не нравится - не кукарекайте, а ещё лучше, если заметите ошибку, поправьте и сделайте pull request. 6 | 7 | Темы лекций: 8 | 9 | 1. 09.02.16 10 | * [Теория](lectures/lecture0.md): Текущие абстракции, системные вызовы, простая модель ОС. 11 | * Практика: На самом деле 2-ая лекция. Практически всё про make и Makefile 12 | 2. 16.02.16 13 | * Теория: Адресация памяти, файловая система (Inode, mount, etc...) *(not implemented yet)* 14 | * [Практика](practices/practice1/practice1.md): работа с файловыми дескрипторами (open, read, write, close). Статическая и динамическая линковки. 15 | 3. 01.03.16 16 | * [Теория](lectures/lecture2.md): права доступа, владельцы процессов/файлов (uid, gid). Команды sudo, chmod, chown. 17 | * [Практика](practices/practice2/practice2.md): скрипты на bash, некоторые консольные утилиты. 18 | 4. 12.03.16 19 | * [Теория](lectures/lecture3.md): umask, ссылки, треды, pipe/fifo 20 | * [Практика](practices/practice3/practice3.md): exec, wait, fork - создание дочерних процессов. 21 | 5. 15.03.16 22 | * [Теория](lectures/lecture4.md): межпроцессное взаимодействие, сигналы. В конце лекции зачем-то 10 минут про линковку. 23 | * Практика: сдача дз. 24 | 6. [22.03.16](lectures/lecture5.md) 25 | * Многозадачность (сессии, группы процессов, демоны) 26 | 7. [29.03.16](lectures/OS-Init.pdf) 27 | * Ход загрузки системы. Лекцию вел Гриша, он упоротый, поэтому конспект по этой лекции такой же. Зато презенташка есть. 28 | 8. [19.04.16](lectures/lecture7.md) 29 | * Терминалы и псевдотерминалы. 30 | 9. [26.04.16](lectures/lecture8.md) 31 | * Что делать, если много файловых дескрипторов (спойлер: `select/poll/epoll`), сеть. 32 | 10. [03.05.16](lectures/lecture9.md) 33 | * Сеть (продолжение), различные лицензии на ПО. 34 | 11. [10.05.16](lectures/lecture10.md) 35 | * Линковка 36 | 12. [17.05.16](lectures/lecture11.md) 37 | * Как на самом деле делаются системные вызовы, реализация виртуальной памяти, memory overcommit, сырые сокеты. -------------------------------------------------------------------------------- /practices/practice1/practice1.md: -------------------------------------------------------------------------------- 1 | *16.02.2016* 2 | 3 | #Файловые операции: 4 | *(ЧИТАЙТЕ man 2 XXX , ЁПТА!!! но briefly ниже)* 5 | 6 | **open 7 | read 8 | write 9 | close** 10 | 11 | ```c 12 | int open(char *path, int flags) 13 | ``` 14 | 15 | `O_RDONLY | O_WRONLY | O_RDWR | O_APPEND` - флаги доступа. 16 | 17 | > Вернёт число - файловый дескриптор 18 | 19 | ```c 20 | int read(int fd, void* dst, size_t size) 21 | ``` 22 | 23 | > fd - file descriptor; 24 | dst - where to read up (buffer); 25 | size - how much to read in bytes. 26 | Возвращает ssize_t (< 0 - ERROR, 0 - input is over). 27 | Может прочитать меньше (короче в мане всё есть) 28 | 29 | ```c 30 | int write(int fd, void* src, size_t size) 31 | ``` 32 | 33 | > fd - file descriptor; src - where to take (buffer); size - how much to write in bytes. 34 | 35 | ```c 36 | int close(int fd) 37 | ``` 38 | 39 | > Закрывает fd (как неожиданно-то, блять) 40 | 41 | #Динамические и статические библиотеки. 42 | Статическая библиотека при линковке засовывается вся в бинарник. 43 | Динамическая - выше этого (загружается по адресу из памяти). 44 | 45 | **hello.c** 46 | 47 | ```c 48 | #include 49 | void hello() { 50 | printf("Hello World!"\n"); 51 | } 52 | ``` 53 | 54 | **main.c** 55 | ```c 56 | void main(int argc, char** argv) { 57 | hello(); 58 | } 59 | ``` 60 | 61 | ## Создание статической библиотеки 62 | ``` 63 | gcc *.c -o *.o -c 64 | ar rcs libhello.a hello.o //собирает в хитрый архивчик, интерпретируемый gcc, как библиотечка 65 | gcc -static main.o -L. -l hello -o main 66 | ``` 67 | 68 | ## Создание динамической библиотеки 69 | ``` 70 | libhello.so //shared object 71 | gcc -fPIC hello.c -c -o hello.o //Position Independent Code 72 | gcc -shared hello.o -o libhello.so //немного магии (это и строка выше) и получаем динамическую библиотеку, ура 73 | gcc -s main.o -L. -lhello -o main 74 | ``` 75 | 76 | ``` 77 | LD_LIBRARY_PATH=. //prefix для запуска через динамическую библиотеку 78 | export [переменная среды]={путь} //запоминает env variable для текущего терминала 79 | ``` 80 | 81 | * Задание 1: слинковать hello1 и hello2 в один main. 82 | * Задание 2: сделать то же самое через Makefile. 83 | 84 | Примеры можете посмотреть в папке examples. 85 | -------------------------------------------------------------------------------- /lectures/lecture6.md: -------------------------------------------------------------------------------- 1 | #Power button to login prompt 2 | > Author: Greg Fefelov. Отчислен со второго курса КТ. Дважды. Работает в "ВКонтакте" 3 | 4 | #Как загружается ОС? 5 | 6 | * Embedded controller. 7 | Маленький контроллер со своей архитектурой и кучей пинов. На материнке. 8 | * Intel Managament. 9 | Внутри процессора. Код не получить. Может использоваться для remote management 10 | 11 | Порядок загрузки: 12 | 1. Нажали на кнопку. EC просыпается, проверяет батарею/доступность электричества, если ему всё нравится, пинает процессор с Intel Managament. 13 | 2. Процессор отправляется по адресу 0xFFFFFFF0 в незащищенном режиме. По историческим причинам (640Кб хватит всем) это оказывается джамп на какую-то часть биоса. 14 | 3. Два стула: BIOS/EFI. Они должны проинициализировать железо. BIOS ничего не знает о современных технологиях. Зато EFI знает про разметку диска, разделы и т.п. 15 | 4. Если мы загрузились из биоса, загрузчик лежит в первом секторе диска. EFI может просто выполнить код с /boot/efi/... , не используя загрузчики 16 | 5. Ядро: init(на самом деле два - один минимальный, чтобы смониторовать норм FS, другой реальный, который может находиться хрен пойми где). Udev - для Plug'n'Play 17 | 6. SysVinit - овно, upstart - что-то не взлетевшее из убунты, systemd - норм. 18 | 19 | ###Эпилог 20 | > All software is shit. Всё очень плохо. 21 | 22 | ###Копипаста письма 23 | Библиография: 24 | 25 | * Про не-x86 железки в компьютере: Joanna Rutkowska, x86 considered 26 | harmful: http://blog.invisiblethings.org/papers/2015/x86_harmful.pdf 27 | * Про socket activation, про которую я плохо рассказал: Lennart 28 | Poettering, systemd for Administrators XX 29 | http://0pointer.de/blog/projects/socket-activated-containers.html 30 | * Про то, как инициализируется процессор: Intel® 64 and IA-32 31 | Architectures Software Developer’s Manual Volume 3 (3A, 3B, 3C & 3D): 32 | System Programming Guide, Chapter 9: 33 | https://www-ssl.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-system-programming-manual-325384.pdf 34 | * Про EFI: Beyond BIOS 35 | http://www.microbe.cz/docs/Beyond_BIOS_Second_Edition_Digital_Edition_(15-12-10)%20.pdf 36 | 37 | Презенташка называется OS-init -------------------------------------------------------------------------------- /lectures/lecture3.md: -------------------------------------------------------------------------------- 1 | # Файловая система (что, опять?) 2 | 3 | Вспоминаем `struct proc_info` - информацию о процессе. 4 | Допустим, процесс хочет создать директорию или файл. Как выставить права доступа? 5 | По дефолту, это `rw-rw-rw-`. Если мы хотим другие права, нужно об этом `mkdir`'у сказать. 6 | Для этого в `proc_info` есть `umask`. Делается `& ~umask` с правами, которые мы указали при создании файла (дефолтные для файла 666, для директории 777). 7 | Например, если мы вызовем `mkdir` с `umask = 022`, мы получим `755 = rwxr-xr-x` 8 | 9 | *NB: `umask` наследуется при fork'e. Стандартная для bash'a - 022.* 10 | 11 | ## Links 12 | 13 | Знакомые всем ссылки-ярлыки 14 | 15 | * Hard links 16 | * Soft (symbolic) links 17 | 18 | **SOFT** 19 | В структуре `inode` указано, что эта нода - символьная ссылка, так же там содержится `path`. 20 | Все обращения к этой `inode` переадресуются на `path` 21 | 22 | **HARD** 23 | Два имени в разных папках содержат на одну и ту же `inode`. Работает только в пределах одной FS. 24 | Чтобы узнать, когда на самом деле удалять файл, `inode` хранит количество hard ссылок на неё. 25 | Так как все записи у нас одинаковые, мы не можем узнать, кто оригинал. 26 | 27 | `man ln` расскажет всё о том, как делать ссылки. 28 | 29 | # Треды и т. д. 30 | 31 | У нас в `proc_info` есть всякие pid, ppid, fdtable, regs и т. д. Часть из них - числа и влезают в регистр. 32 | Большие же структуры (типа fdtable) хранятся по указателям. Какие-то из них наследуются при `fork`'e, но, например, не общая память. 33 | Если же мы хотим расшарить больше, есть системный вызов `clone`. Например, если мы передадим наследнику всё, кроме регистров, у нас получится не новый процесс, а новый тред. 34 | 35 | pthreads - сокращение от POSIX Threads. Можно юзать из С. 36 | 37 | При создании нового треда все страницы памяти из стека становятся copy on write (т.е., создается новый стек), если ты не зааллоцировал его сам. 38 | 39 | # Межпроцессное взаимодействие 40 | 41 | `IPC` - InterProcess communication 42 | 43 | * pipe - 2 fd, в один валится, из другого вываливается. Если закрывают все fd на один из концов, в другой прилетает EOF(в читающий) или EPIPE (в пишущий). 44 | * fifo - 1 fd, один процесс открывает на чтение, другой на запись. `man 3 mkfifo` 45 | * Сигналы, см. следующую лекцию 46 | -------------------------------------------------------------------------------- /lectures/lecture9.md: -------------------------------------------------------------------------------- 1 | # Cеть (продолжение). Пакетная маршрутизация 2 | 3 | Устанавливать прямое физическое соединение с сервером как, например, на телефонной станции - дорого, долго и больно. 4 | Когда ваше сообщение нарезается на пакеты и как-то (через кучу промежуточных маршрутизаторов) идёт по сети на сервер. 5 | Пакеты не обязаны идти по одному маршруту, маршрут выбирается какими-то интересными способами. 6 | 7 | Из чего состоит пакет? Например, мы отправляем HTTP-запрос `GET`. К `GET`'у и его параметрам добавляется HTTP заголовок - уровень приложения, 8 | TCP-заголовок - транспортный уровень, IP-заголовок - сетевой уровень, и наконец Ethernet-заголовок - канальный уровень. Поэтому всё это дерьмо называется стеком - 9 | каждый уровень абстракции лежит под предыдущим. 10 | 11 | На уровне TCP пункт назначения определяется адресом и портом, на уровне IP - IP-адресом, на уровне Ethernet - MAC-адресом. 12 | 13 | ## IP-пакет 14 | 15 | Состоит из версии (4/6), длины, протокола, адреса назначения/отправления и TTL (Time to live) - 16 | на каждом прохождении через промежуточный маршрутизатор это число уменьшается на 1. Если оно стало 0, пакет считается заблудившимся/никому не нужным ~~(как и ты)~~, и выбрасывается. 17 | На этом работает TraceRoute и TracePath из протокола `ICMP`. 18 | 19 | # Лицензии на программы (ВНЕЗАПНО) 20 | 21 | Изначально всем было пофиг. А потом появился Столлман. 4 свободы: 22 | 23 | 1. Запускать 24 | 2. Изучать (смотреть исходники) 25 | 3. Распространять 26 | 4. Изменять и делиться. 27 | 28 | Это GNU GPL. `copyleft` - это как копирайт, только наоборот. Если кто-то использует ваш код под GNU GPL, 29 | его разработка тоже должна быть под этой лицензией. 30 | 31 | В `GPLv2` завезли LGPL и AGPL - первая более мягкая, вторая более жесткая по сравнению с обычной GPL. 32 | `LGPL` разрешает не лицензировать под GPL скопированный код, если его не меняли. 33 | `AGPL` требует открывать ваш исходный код, даже если вы не используете сам продукт под AGPL, но даже если взаимодействуете с ним (по сети, например). 34 | 35 | `GPLv3` содержит ограничения и на железную часть. (В v2 был прецедент с тем, что исходники под какую-то приставку были выложены, но в самой приставке аппаратно были заблокированы сторонние прошивки). 36 | 37 | `BSD, MIT, Apache` - можно использовать код под этой лицензией в своей закрытой разработке. 38 | 39 | `Creative Commons` - лицензии на медиа (картинки, музыку, ...). В наборе есть лицензии и типа "Общественное достояние", и copyleft, и запрет изменений. 40 | 41 | > Thomson's compiler hack. 42 | Допустим, у нас есть компилятор и его исходники. Мы хотим сделать себе бэкдор в систему, для этого нужно заменить программу `login`. 43 | Встроим в компилятор штуку типа "если мы компилируем что-то типа `login.c`, то встроить бэкдор". 44 | Теперь, даже если я прочитал исходник login'а и уверен, что всё в порядке, это не так. 45 | Даже если мы захотим скомпилировать заново хороший компилятор, плохой бинарник может скомпилировать его в себя. 46 | Мораль: **НЕ ДОВЕРЯЙ НИКОМУ** -------------------------------------------------------------------------------- /lectures/lecture0.md: -------------------------------------------------------------------------------- 1 | # Операционные системы 2 | 3 | Запускают программы, изолируют их друг от друга, чтобы крах одной не валил всё, 4 | предоставляют унифицированный интерфейс для работы с железом (драйвера там всякие). 5 | 6 | Тезис курса: **АБСТРАКЦИИ ПРОТЕКАЮТ**. 7 | Хоть интерфейс может быть достаточно абстрактным, не зная, как он работает изнутри, используя 8 | его по-разному, можно в разных случаях наткнуться на разные результаты, например, на сильное ухудшение производительности. 9 | Хорошая [статья на эту тему](http://russian.joelonsoftware.com/Articles/LeakyAbstractions.html). 10 | 11 | У ОС есть внешняя часть и есть *ядро*. Типичная работа программы: она что-то делает -> ей что-то понадобилось от ядра или оно её прервало -> 12 | ядро что-то поделало -> программа выполняется дальше. 13 | Рассмотрим примерчик (псевдокод имеет много общего с питоном): 14 | 15 | ```python 16 | def main(): 17 | name = readln() 18 | string = "Hello, " + name + "!" 19 | writeln(string) 20 | exit(0) 21 | ``` 22 | 23 | Здесь `readln`, `writeln` и `exit` - системные вызовы, которые работают в ядре. 24 | Давайте поймем, как ядро взаимодействует с программой. Перепишем наш код на несколько функций так, чтобы каждая из них 25 | возвращала тройку `(что_сделать_ядру, с_какимим_аргументами, откуда_продолжать)` 26 | 27 | ```python 28 | def main(): 29 | return (Read, [], cont_main) 30 | def cont_main(name): #здесь мы приняли то что вернул системный вызов 31 | string = "Hello, " + name + "!" 32 | return (Write, [string], cont_main2) 33 | def cont_main2(wr_result): 34 | return (Exit, [0], NONE) 35 | ``` 36 | 37 | Теперь ядро может интерпретировать эти тройки в цикле типа 38 | 39 | ```python 40 | while (not processes.empty()): 41 | (p, pargs) = processes.pop() 42 | (syscall, args, cont) = p(pargs) 43 | switch(syscall): 44 | case Read: 45 | read_res = readln(args) #Ядро умеет обращаться с железом и читать из него 46 | processes.push(cont, read_res) 47 | case Write: 48 | ... 49 | # И так далее... 50 | ``` 51 | 52 | Следует заметить, что при такой реализации любой блокирующий вызов от программы будет останавливать ядро. 53 | Чтобы этого не происходило, будем считать, что у нас есть некая *магия*, которая позволяет определить, 54 | можем ли мы сейчас выполнить конкретный процесс. Например, в `case N` будем класть процессы в массив `blocked`, 55 | а после `switch`'a делать 56 | 57 | ```python 58 | for p in blocked: 59 | if (can_exec(p)): 60 | exec(p) 61 | ``` 62 | 63 | Далее код я приводить не буду, ибо работающая(если им верить) модель ядра на питоне есть 64 | на [странице курса](http://neerc.ifmo.ru/~os/static/model.py). 65 | 66 | ## Иерарахия 67 | 68 | Процессы организованы в дерево. У каждого процесса есть родитель. 69 | Порождаем процессы мы с помощью `fork` и `exec`. 70 | 71 | `fork` дублирует процесс в текущем его состоянии и возвращает флажок, выполняется сейчас родитель или ребенок 72 | (на самом деле 0 в ребёнка и `pid` в родителя, но об этом позже). 73 | 74 | `exec` замещает полностью текущий контекст на выбранный (загруженный, например, из бинарника или откуда-то ещё). -------------------------------------------------------------------------------- /lectures/lecture5.md: -------------------------------------------------------------------------------- 1 | # Многозадачность 2 | 3 | *Группа процессов* - то, что запустил shell одной командой. Т. е. запущенный `cat` группа процессов, 4 | и `cat t.txt | grep smth | xargs ...` тоже одна группа процессов. 5 | Когда мы хотим завершить работу команды, очевидно, мы хотим послать `SIGINT` всем сразу. Именно это группы процессов и позволяют. 6 | 7 | Узнать, какой группе принадлежит процесс, можно с помощью `getpgid()`. Он возвращает номер лидера группы. 8 | 9 | *Лидер группы* - процесс, по которому определяется группа. 10 | 11 | `kill -pid SIGINT` (в смысле с отрицательным pid) - убивает всю группу с лидером `pid`. 12 | 13 | *Сессия* (нет, не та, на которой вы это будете читать) - мн-во групп процессов. У неё тоже есть лидер. 14 | Типичный лидер сессии - `bash`. К каждой сессии прикреплено не более одного терминала. 15 | 16 | *foreground group* - та группа, которой дали (читать с терминала/выводить/etc). *background* - соответственно, остальные. 17 | За их переключение отвечает лидер сессии. Переключение происходит, когда все процессы в группе остановлены. (например, по `Crtl+Z` = `SIGTSTP`). 18 | Как об этом узнает лидер? С помощью `waitpid` (он может отслеживать не только когда процессы умирают, но и просто когда меняют состояние). 19 | 20 | `SIGTTIN` - то что прилетает background группам. Но всякие `read` рестартуются. Это поведение зависит от каких-то упоротых флажков 21 | в sighandler-ах. 22 | 23 | `setpgid(pid, pgid)` - перкидывает процесс `pid` в группу `pgid`. Можно менять, если: 24 | `pid` - ребёнок того, кто делает set и он не делал `exec`. Проблема: 25 | 26 | ```c 27 | pid = fork(); 28 | if (pid == 0) { 29 | ... 30 | exec(...); 31 | } else { 32 | ... 33 | kill(-pid, SIGUSR1); // что-то послали 34 | } 35 | ``` 36 | 37 | Мы не знаем, кто будет выполняться раньше: ребёнок или родитель. Но нам где-то нужно поменять группу, чтобы слать сигналы, и сделать это до `exec`. 38 | Ноооо..... ДАВАЙТЕ СДЕЛАЕМ ЕГО ДВАЖДЫ!!! 39 | 40 | ```c 41 | pid = fork(); 42 | if (pid == 0) { 43 | setpgid(pid, ...); 44 | ... 45 | exec(...); 46 | } else { 47 | setpgid(pid, ...); 48 | ... 49 | kill(-pid, SIGUSR1); // что-то послали 50 | } 51 | ``` 52 | 53 | Чтобы сделать себя лидером сессии, есть `setsid`. На самом деле, процесс, вызвавший это, создает себе отдельную сессию и становится её лидером (и лидером новой группы в этой сессии). 54 | *NB: нельзя делать `setsid` тем, кто является лидером группы. Потому что `pid` сохраняется, и у нас получится два процесса вроде как одной группы, но в разных сессиях.* 55 | 56 | ## ДЕМОНЫ 57 | 58 | Мы хотим процесс, который не был бы привязан ни к какому терминалу. Надо бы создать новую сессию. 59 | Напрямую вызвать от демона `setsid` мы не можем, т.к. он лидер группы. Давайте заведем процесс, который будет запускать демонов - `run_daemon`. 60 | Но теперь наш новый процесс - лидер сессии, и может управлять терминалами, а мы не хотим этого. Давайте породим ЕЩЁ один процесс, который уже не будет лидером сессии, а его ребёнком. 61 | Вот уже он может являться демоном. Что делать с 2мя лишними процессами? ~~ПРОСТО УБЕЙ ИХ.~~ Сессия и группа могут нормально жить без лидера. 62 | 63 | ## Терминалы 64 | 65 | Когда терминал отключается, он посылает `SIGHUP`. `bash` обычно пересылает этот сигнал дальше. 66 | Действие по умолчанию - завершить процесс. Демонам он придти просто так не может, поэтому есть соглашение, что по `SIGHUP` он перечитывает конфиг. 67 | -------------------------------------------------------------------------------- /lectures/lecture4.md: -------------------------------------------------------------------------------- 1 | # Межпроцессное взаимодействие (сигналы) 2 | 3 | Чтобы взаимодействовать пайпом, надо, чтобы оба процесса заранее знали об этом. Сигналы позволяют "неожиданно" обратиться к программе. 4 | `man 7 signal` 5 | 6 | Посылаются они системным вызовом `kill` (не перепутайте с одноименной утилитой). 7 | "Давайте снова рисовать структурку" (`proc_info`). Теперь там есть маска `sigpending`. У каждого типа сигнала есть номер и соответствующее ему имя. 8 | Напр., `SIGINT` это 2, `SIGKILL` - 9. Битовая маска показывает, какие сигналы пришли в процесс, но ещё не обработаны. 9 | Из этого следует, что мы не запоминаем порядок и не можем одновременно "держать" два одинаковых сигнала. 10 | 11 | Ещё в `proc_info` есть массив `sighandlers`, который содержит указатели на функции - обработчики соответствующих сигналов. 12 | `sigblocked` - ещё одно поле в `proc_info`, которое позволяет игнорировать сигналы по той же маске. 13 | 14 | kernel() { 15 | ... 16 | p,args = pop_process() 17 | if (sigpending[1] && !sigblocked[1]) 18 | sighandlers[1]() 19 | ... 20 | } 21 | 22 | `man 2 signals` - deprecated. Перенаправляет на `man 2 sigaction` 23 | 24 | 25 | 26 | **SIGKILL, SIGSTOP** - на самом деле не сигналы. Точнее, они, но они не доходят до процесса, а обрабатываются ядром. По `SIGKILL` мы просто выкидываем процесс из списка. 27 | `SIGTERM` - аналог `SIGKILL`, только доходит до приложения, *намекая* ему, что время умирать. 28 | `SIGSTOP` - означает приостановить процесс, он будет лежать в списке заблокированных, как будто совершен системный вызов. Пока не придет `SIGCONT`. 29 | `SIGTSTP` - "мягкий" `SIGSTOP`. (Ctrl+Z) 30 | `SIGINT` - (Ctrl+C) 31 | 32 | Что происходит, когда во время выполнения обработки сигнала приходит другой? 33 | Очень жаль. Функция-обработчик должна обладать `reentrancy` - не затрагивать внешние данные, поддерживать инварианты всегда внутри себя, а не только при входе и выходе из неё. 34 | Короче говоря, обработчик сигнала может **ВНЕЗАПНО** вызваться во время своего же выполнения. 35 | 36 | Если сигнал пришёл во время системного вызова, они могут прерваться. Например `read()` по сигналу возвращает -1 и выставляет код ошибки `EINTR`. 37 | Сигналы приходят и для незаблокированных, и для заблокированных процессов. После обработки процесс "разблокируется". Исключением является `SIGSTOP` - после него прилетает только `SIGCONT`. 38 | 39 | ## Realtime сигналы 40 | 41 | Приходят по порядку, могут таскать за собой данные (небольшие, размером с регистр). 42 | В `proc_info` появляется поле со списком (очередь) из таких сигналов. 43 | *Никому не нужны~~, как и ты~~* 44 | 45 | ## Ещё один способ IPC 46 | 47 | Для тех процессов, что состоят в отношении "родитель-ребенок". 48 | Shared memory - когда виртуальные страницы памяти двух процессов указывают на один и тот же участок физической памяти. 49 | 50 | Системный вызов `mmap` занимается маппингом виртуальной памяти куда-нибудь. В частности, им можно создать shared memory. 51 | 52 | 53 | # Динамическая линковка 54 | 55 | *понятия не имею, как это оказалось здесь* 56 | 57 | Ну типа программа - граф вызовов функций, который мы попилили на куски. Задача динамической линковки - загружать одни и те же общие библиотеки, по разным адресам в памяти. 58 | Т.е. чтобы вызвать функцию в ассемблере, нужно знать её адрес. Но два процесса могут положить библиотеку в своей памяти в разные участки. Нужно вычислить адреса всех её функций *динамически* и замапить их в одну и ту же общую память. -------------------------------------------------------------------------------- /lectures/lecture8.md: -------------------------------------------------------------------------------- 1 | # Много файловых дескрипторов 2 | 3 | > Проблема: у нас их дофига, и мы хотим работать одновременно со всеми. 4 | (Например, мы сервер и к нам подключается куча клиентов). У каждого своя скорость доступа, и не хочется, чтобы 5 | один подвисший `read` замедлял работу остальных. 6 | 7 | Первое решение: неблокирующее чтение. Ставим на все дескрипторы флаг `O_NONBLOCK`, 8 | бегаем по ним в цикле. Что не очень: активное ожидание (процессор крутится в цикле и жрёт энергию). 9 | 10 | Второе: по процессу/треду на дескриптор. Ну понятно, что тут не так (создание тредов - дорого, 11 | переключение - дорого, блокировки - дорого... ). Но если клиентов не очень много, то сойдёт. 12 | 13 | Это были плохие решения. Теперь первое хорошее: 14 | Дескрипторы - маленькие числа. Сделаем вызов `select(read_mask, write_mask)` - битовые маски интересующих нас fd. 15 | Он заблокируется до того момента, пока один из дескрипторов в маске не станет доступен. Затем он выключит в масках те биты, которые по прежнему недоступны. 16 | Небольшое улучшение - т.к. маски очень большие (~1024), передадим ещё `nmax` - максимальный используемый fd. 17 | Но если, например, у нас используются два fd, но они с номерами `7` и `1000`, то нам всё равно нужно бегать по всей маске. 18 | 19 | Следующее улучшение - вместо масок передавать просто список дескрипторов. 20 | Получили `poll(n, pollfd* ev)`. `pollfd` - структура запроса. (Номер fd, что с ним делать - читать/писать/ещё, место под ответ). 21 | Решили проблему разрозненных номеров fd, но не решили проблему, когда их много, но они малоактивны. 22 | 23 | *Барабанная дробь...* `EPOLL`!!! 24 | > В отличие от `select` и `poll`, которые есть в POSIX, `epoll` только в Linux. 25 | 26 | Заметим, что в типичных юзкейсах список "слушаемых" дескрипторов и их флаги редко меняются. 27 | `epoll` это использует. В ядре хранится список, аналогичный тому, что мы передали бы в `poll`. 28 | Системными вызовами мы можем редактировать этот список. `epoll_wait` блокируется, пока что-то в списке не произойдёт. 29 | 30 | Этот режим работы называется `Level-triggered` - нам говорят, когда можно что-то сделать. 31 | Есть ещё `Edge-triggered` - нам говорят, когда произошло что-то новое. 32 | 33 | # Сеть 34 | 35 | `socket, bind, connect, listen, accept, recv, send` - вот это всё. 36 | 37 | Сервер создает сокет `socket()`, затем вешает его на какой-то адрес и порт `bind()`, 38 | потом ожидает подключения на него `listen()`, и когда там что-то появится, получает fd клиента `accept()`. 39 | Клиент создает сокет `socket()` и подключается `connect()`. 40 | > OSI - попытка сделать универсальную абстракцию под любой протокол. НЕ ВЫШЛО. Продолбали время, появился TCP/IP. 41 | 42 | Уровни `OSI`: 43 | 44 | 1. Физический 45 | 2. Канальный 46 | 3. Сетевой 47 | 4. Транспортный 48 | 5. Сеансовый 49 | 6. Представления 50 | 7. Приложения 51 | 52 | Что есть в `TCP/IP`: 53 | 54 | * `IPv6, IPv4` - сетевые протоколы 55 | * `TCP, UDP` - транспортные 56 | 57 | Дальше рассказ про то, как пользоваться системными вызовами под заголовком. 58 | 59 | ```cpp 60 | s = socket(domain, type, protocol); 61 | //domain: Сетевой уровень, например IPv4 или Unix для unix-сокетов 62 | //type: SOCK_STREAM, SOCK_DGRAM. Первый норм, второй ничего не гарантирует. 63 | //protocol: IPPROTO_TCP, ... можно указать 0, тогда выберется автоматически. Напр., TCP для SOCK_STREAM 64 | ``` 65 | 66 | Что-то про DNS... Ну ведь все знают, что это такое, да? 67 | Сервер, который переводит ваш человекочитаемый `vk.com` в IP-адрес. 68 | `getaddrinfo(host, port, hints)` -------------------------------------------------------------------------------- /lectures/lecture7.md: -------------------------------------------------------------------------------- 1 | # Терминалы и псевдотерминалы 2 | 3 | Терминал - как файловый дескриптор, можно писать и читать. 4 | 5 | **Canonical mode** - основной режим терминала. Нажимаете клавиши, символы отображаются и отправляются в read при нажатии `Enter`. 6 | 7 | **Noncanonical mode** - всё остальное. 8 | 9 | `termios` - структура для работы с терминалами. 10 | 11 | ```c 12 | struct termios 13 | { 14 | int c_iflags; //Input flags 15 | int c_oflags; //Output flags 16 | int c_cflags; //Control flags 17 | int c_lflags; //Local options 18 | int c_cc[]; //Control characters mapping 19 | } 20 | ``` 21 | 22 | В `i,o` флагах можно задавать опции типа "поддержка больших букв, битность символов, ...". 23 | Сейчас мало что используется, большинство интересных флагов берутся из `lflags`. 24 | Например, флаг `ECHO`. Если он выставлен, то символы, которые вы вводите, посылаются на вывод (т.е. видно, как вы их печатаете). 25 | Это отключается, например, при вводе пароля. 26 | `ECHOE` отвечает за обработку `backspace`. (В `cannonical mode` просто выкидывается символ из внутреннего буфера терминала, в `noncannonical` `backspace` обрабатывают сами программы). 27 | `ECHO[K|NL]` - ещё что-то полезное для вывода... `ICANON` - переключение между режимами. 28 | `ISIG` - надо ли посылать сигналы при всяких `Ctrl+C` 29 | 30 | `break condition` - когда произошло отключение устройства (нашего терминала). 31 | *Вспоминаем, что когда то терминалы были физическими устройствами.* 32 | Флаги `IGNBRK` и `BRKINT` отвечают за поведение при этом. Вообще, есть `man termios` 33 | 34 | ## Системные вызовы для работы с терминалом 35 | 36 | ```c 37 | tcgetattr(fd, termios*); //Читает настройки 38 | tcsetattr(fd, options, termios*); //Устанавливает настройки 39 | 40 | isatty(fd); //Проверяет, является ли fd дескриптором терминала 41 | 42 | ioctl(...); //Вызов с кучей параметров, который отвечает за ВСЁ. tcget/set - обертки над ним. 43 | ``` 44 | 45 | Типичное использование - прочитали настройки, изменили в них флажок, записали. 46 | Зачем `options`? Определяют, когда настройки вступят в силу. `TCSANOW` - сейчас; `TCSAFLUSH` - когда закончится текущая очередь на вывод; 47 | `TCSADRAIN` - как `FLUSH`, только ещё выбрасывает очередь на вход. 48 | 49 | `c_cc[]` - массив, который хранит, какие клавиши являются управляющими (посылают сигналы и т.п.) и просто настройки, которые не выражаются флагами. 50 | Там замэплены всякие `^C, ^D, ^S, ^Q`. Два интересных параметра: `VTIME` - максимальное время ожидания, `VMIN` - сколько минимум символов можно выводить. 51 | 52 | * `TIME = 0, MIN = 0` - неблокирующий read 53 | * `TIME = 0, MIN > 0` - блокирующий read 54 | * `TIME > 0, MIN = 0` - read c таймаутом 55 | * `TIME > 0, MIN > 0` - отсеивать "пачки" байт. Напр., ставим маленький таймаут и MIN=3. Обычная клавиша вернет байт по таймауту, "специальная" клавиша, которую мы ждем, вернет сразу 3. 56 | 57 | Когда мы меняем размер окна, прилетает `SIGWINCH`. 58 | 59 | ## Как это устроено 60 | 61 | Нижний уровень - `UART`. Серийный ввод-вывод. 62 | Поверх него навешивается `line discipline` - штука с буферами и обработкой всяких `backspace`. 63 | Уровнем выше `session management`, так же называется `tty driver` - обработка процессов и их групп, `job control` и прочее. 64 | 65 | Так вот, `TTY` - это "legacy говно"©. Все нормальные ОС с окошечками теперь просто эмулируют эти TTY. 66 | *The tty demystified* - статья на тему. 67 | 68 | ## Как работает ssh 69 | 70 | Псевдотерминал - два устройства(fd), master и slave. Типа пайп. С `slave` концом можно делать всё то же самое, что с терминалами. 71 | `master` контролирует его. 72 | 73 | Удалённый доступ подключатеся к нужной машине, создаёт master-slave, и передаёт программе, с которой хочет работать (например `bash`) в качестве stdin/stdout slave терминал. 74 | Если `bash` форкнется, создастся ещё один slave. Он спокойно может подключиться к тому же самому master'у. 75 | 76 | Всё это делается системными вызовами `posix_openpt, grantpt, unlockpt, ptsname`. 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /lectures/lecture11.md: -------------------------------------------------------------------------------- 1 | # УГЛУБЛЕНИЯ и РАЗОБЛАЧЕНИЯ 2 | 3 | ## Системные вызовы 4 | 5 | Как именно вызываются эти функции? Через прерывания. 6 | 7 | Когда происходит прерывание, выполнение пользовательского кода приостанавливается, 8 | и вызывается функция по адресу, который записан как обработчик этого прерывания. 9 | Прерывания бывают разные, с разными обработчиками - таймер, `Double Fault` (если произошла ошибка при вызове прерывания), `Triple Fault` (да-да, если произошла ошибка в `Double Fault`. 10 | Четвертого нет, ибо третий просто перезагружает машину), `General protection fault` - когда ошибка прав доступа, `Page fault` - ошибка страничной адресации, и ещё куча ошибок. 11 | Нормальные тоже есть (типа пришли данные с сети/диска). 12 | 13 | Прерывание на самом деле - просто число. Есть инстуркция ассемблера `int N`, которое вызывает прерывание N. 14 | Номер прерывания системных вызовов - `0x80`. На самом деле, с каждым прерыванием ещё связан минимальный уровень привелегий, 15 | которым нужно обладать, чтобы его вызывать - мы же не хотим, чтобы пользователь дергал прерывания фейлов или каких-то обновлений данных. (см. `runlevel` из курса ассемблера). 16 | 17 | Чуть позже появились специальные команды в процессоре `sysenter/sysexit`. 18 | 19 | Ещё есть очень интересный способ - повесить обработчик ошибки "несуществущая инструкция" туда же, 20 | куда указывает обработчик системных вызовов, и вызывать... несуществующую инструкцию. Например, `ud2`. Её не существует. 21 | *К счастью, системы, которые так устроены, не выжили, поэтому такой способ сейчас не работает.* 22 | 23 | Аргументы системным вызовам обычно передаются через стек (в x64, на самом деле, какая-то часть идёт через регистры - вспоминаем calling conventions) 24 | > Когда мы рассматривали модель ОС, мы говорили, что наш код прерывается в месте системных вызовов и возвращает место, откуда продолжать и код вызова. 25 | На самом деле, ОС пофиг, когда прерывать наш код - она просто сохранит контекст и instruction/stack pointer. 26 | 27 | ## MMU 28 | 29 | Тут про то, что страницы памяти в виртуальном адресном пространстве хранятся не в огромном линейном массиве, а в дереве - 30 | на первом уровне 2^10 ячеек, каждая из которых указывает на массив размером 2^10, каждая ячейка 31 | которого - страница (на x86) или указывает на ещё один массив, и ещё... (на х64, только там блоки не по 2^10, а по 2^9). 32 | Короче, это всё было на ассемблере и плюсах. Флажки там доступа всякие, shared и пр. 33 | 34 | ### Как реализован swap 35 | 36 | Пусть мы магическим образом поняли, что страница неиспользуется. Тогда запишем её на диск, а в памяти пометим как отсутствующую. 37 | Когда пользователь обратится к странице, произойдёт ошибка адресации, вызовется обработчик `page fault`, посмотрит, что эта страница на самом деле есть, и загрузит её обратно. 38 | 39 | Как определить, что страница не нужна? Периодически у неё сбрасывается `access` бит, и та, у которой он долго не выставлялся, 40 | скорее всего не использутеся. 41 | 42 | ### Ленивый mmap - memory overcommit 43 | 44 | *было на Сорокине, если что* 45 | 46 | Когда процесс запрашивает память, даже если больше, чем у нас есть, 47 | мы просто помечаем страницы как выделенные, но не ставим `present`. А уже при первом обращении подготовим страницу (найдем физическую и забьем нулями). 48 | Поэтому делать больший `mmap/munmap` - дешево, а если обращаться к выделенной памяти - уже нет. 49 | 50 | Так же, если у нас все процессы используют всю запрошенную память, а у нас столько нет, запускатся `OOM Killer`, 51 | который использует кучу различных эвристик, чтобы понять, кого лучше убить. 52 | 53 | ## Сырые сокеты 54 | 55 | `socket(AF_INET, SOCK_RAW, proto)` 56 | 57 | На сырой сокет валятся ВСЕ данные выбранного протокола, причём с его заголовком (обычно нам доступно только содержимое пакета). 58 | На этом основаны программы-снифферы типа Wireshark. Так как это небезопасно, создавать их может только root. 59 | Если указать опцию `IP_HDRINC` получим заголовок не только протокольный (`TCP/UDP/etc`), но и транспортный (`IP`). 60 | -------------------------------------------------------------------------------- /lectures/lecture10.md: -------------------------------------------------------------------------------- 1 | # Линковка 2 | 3 | Что есть библиотека? Набор кода, функции, которые импортируются в библиотеку и экспортируются из неё. 4 | 5 | Рассмотрим программу как граф вызова функций. Есть начальная вершина (`_start`). Можно попилить граф на подграфы - это и будут 6 | разные библиотеки. Компилятор производит код какой-то части, о которой он знает, а так же таблицу символов (для экспорта функций). 7 | Вызовы внешних функций, о который он не знает, называется таблицей релокаций. На низком уровне табличка внешних функций 8 | представляет собой список из пар `[(Offset, Relocation)]`, где `Relocation::==(Name, OffsetType)`. `OffsetType` - относительное смещение в файле или абсолютное. 9 | `Offset` указывает на место вызова функции, которой пока что не существует. 10 | 11 | ## Статический линковщик 12 | 13 | Все релокации имеют абсолютное адреса. Статический линковщик берет бинарники, объединяет секции кода и таблицы символов, и 14 | забивает "дырки". После его работы таблица релокаций по-прежнему может быть не пустой. (мы можем долинковать потом) 15 | 16 | Исполняемым файлом является тот бинарник, у которого пустая таблица релокаций(нет неизвестных ф-ий) и определён символ `_start`. Что с этим делать - понятно. 17 | 18 | ## Динамический линковщик 19 | 20 | Дырки заполняются в рантайме. В бинарник мы добавляем `needed_libs` - 21 | список библиотек, которые мы будем подключать, "обещание" того, чем мы будем закрывать дырки. 22 | Проверяется, что таблица релокаций закрывается, но сама подстановка вызовов не происходит. 23 | 24 | > **Проблема:** две проги юзают одну и ту же либу. Мы хотим загрузить её в память один раз и пошарить страницы памяти с ней, но в 25 | виртуальной памяти процессов-пользователей либы она может попасть по разным адресам. 26 | 27 | `GOT/PLT` - секции в бинарнике, заполняемые рантайм линковщиком. 28 | Первое - отображение из имени в абсолютные адреса глобальных переменных, которые мы хотим получать из либы. 29 | (Адрес куда загрузилась либа + смещение из таблицы символов либы). 30 | 31 | `PLT` устроен почти так же, но хранит функции. Когда мы вызываем функцию динамической библиотеки, 32 | просто делаем `jmp` на адрес оттуда. 33 | 34 | Теперь каждая библиотека ссылается только на свой `GOT/PLT`, и её код можно шарить, 35 | а эти таблицы заполнять для каждого процесса перед запуском. 36 | 37 | Такое называется `PIC - position independet code`. Все обращения по адресам - относительные. 38 | 39 | **(ТАМ ТАМ ТААААМ)** Не работает на `x86_32`, потому что там нет `mov` по относительному адресу - не можем пользоваться `GOT`. 40 | (`mov rax, [GOT[n]]` - второй аргумент относителен.) 41 | 42 | Придётся применять, как всегда, ~~грязные хаки и костыли~~ Enterprise решение. 43 | 44 | `mov rax, [cur_adr+GOT]` - прибавили к текущему адресу относительный. 45 | Как узнать текущий адрес? 46 | 47 | ```asm 48 | main: 49 | call: foo ; вызвали функцию, но не сделали ret 50 | foo: 51 | pop rax ; теперь на вершине стека адрес возврата foo - почти текущий 52 | mov rax, [rax + ? + GOT] ; ? - размер инструкции. 53 | ``` 54 | 55 | Чтобы не было проблем с branch prediction'ом (он ожидает, что из функции возвращаются), 56 | оформим это в отдельную функцию: 57 | 58 | ```asm 59 | main: 60 | call bar 61 | mov rax, [rax + ? + GOT] 62 | ... ; что то дальше с выходом 63 | 64 | bar: 65 | pop rax ; Положили со стека в rax 66 | push rax ; Вернули, чтобы сработал ret 67 | ret 68 | ``` 69 | 70 | `LD_PRELOAD` - можно установить этой переменной какое-то строковое значение, 71 | и либы из этой строчки будут использоваться с большим приоритетом в динамической линковке. 72 | Например, `LD_PRELOAD=mymalloc.so emacs` запускает `emacs` со своим аллокатором памяти, который должен быть 73 | определён в вашей `mymalloc.so`. 74 | 75 | ## Ленивая линковка 76 | 77 | Если мы, например, программе передали не те аргументы, 78 | то неразумно линковать её всю, ведь она завершится с ошибкой в самом начале. 79 | 80 | Сделаем так, чтобы библиотеки линковались при первом вызове функции. 81 | Добавим в `GOT` секцию `GOT.PLT`, теперь она будет содержать указатели на функции, а 82 | `PLT` будет делать `jmp GOT.PLT.name`. Заодно, кстати, мы сделали секцию `PLT` констатной. (Это хорошо, так как `PLT` - выполняемый код, а `GOT` - просто секция данных). 83 | После `jmp` поместим вызов линкера, а в `GOT.PLT` положим по умолчанию возврат в `PLT`. 84 | Тогда при первом обращении к нему вызовется линкер и запишет туда адрес динамической функции. 85 | 86 | > Так как линкер некому линковать, то в `GOT[0]` записывается его адрес, а в `GOT[1]` - его глобальные переменные. -------------------------------------------------------------------------------- /lectures/lecture2.md: -------------------------------------------------------------------------------- 1 | # Права доступа 2 | 3 | У каждого пользователя есть уникальный числовой идентификатор `uid`, который определяет его права в системе. 4 | Может быть несколько пользователей с одним `uid`, но зачем. 5 | У root'a всегда `uid=0`, у "обычных" пользователей они начинаются обычно с 1000. 6 | Также можно разделять пользователей на группы, идентификатор группы называется `gid`. 7 | 8 | В структуре `inode`, рассмотренной ранее, хранится `uid` пользователя - владельца этого файла. И 12 бит, которые называются `mode` и собственно являются правами доступа. 9 | Они разделены на 4 октета. 10 | Первые три бита рассмотрим позже, оставшиеся 9 отвечают (от старших к младшим) за права доступа владельца файла, его группы и всех остальных. 11 | Первый (старший) бит октета означает наличие или отсутствие права на чтение (`r - read`) , второй - на запись (`w - write`) , тертий - на исполнение (`x - execute`). 12 | Пример: `rwxr-xr-x`. У владельца файла есть полный доступ, у его группы и всех остальных - только на чтение и исполнение. 13 | Довольно часто используют запись числами, тогда верхний пример будет выглядеть как `755`. 14 | 15 | С директориями чуть сложнее. Как мы знаем, директория - это таблица (или map) из названия в соответствующий `inode`. 16 | Право `r` дает возможность смотреть левую колонку таблицы (с именами), право `x` дает просмотр правой (с `inode`), право `w` дает возможность изменять содержимое обоих колонок. 17 | То есть, если на папку права `r--`, можно посмотреть список файлов в ней, но к ним нельзя никак перейти (забавный факт №42). 18 | 19 | Тут у нас возникает проблема. Представим директорию `/tmp`, в которой хранятся временные файлы всех пользователей. 20 | Допустим, пользователи `petya` и `vasya` хранят там свои картиночки. Если им обоим дать право `w`, они смогут удалить файлы друг друга, что не есть хорошо. Если им не давать этого права, они не смогут не то что удалить за собой файлы, они не смогут их даже создать. 21 | Тут на помощь приходит первый октет в `mode`. Его последний бит называется sticky bit. Раньше от отвечал за что-то вроде "запрет выгружения исполняемого файла из памяти в swap", но теперь это используется по другому: 22 | Если у директории установлен sticky bit, удалять файлы из неё может только владелец файла. Собственно, так решается проблема - устанавливаем у `tmp` sticky bit, даём Пете с Васей права `w`, они могут создавать там любые временные файлы, но удалять они смогут только то, что создали. 23 | Такие типичные права доступа на общую папку выглядят как `1777` 24 | 25 | Что мы с этим можем сделать? Есть команда `chmod`, которая изменяет права доступа (их может менять владелец файла) и `chown`, которая изменяет владельца файла (выполнять могут привелигированные пользователи, т.е. root) 26 | 27 | # Процессы и их иерархия 28 | 29 | У любого процесса есть `pid` - его уникальный id, и `ppid` - id его родителя. Также процесс хранит `uid` пользователя, который его запустил, и с правами которого он выполняется. 30 | 31 | Процессы представлены в виде дерева, где корнем является `init`, который запускает все остальные процессы. 32 | 33 | ## Zombie 34 | 35 | Когда мы запускаем дочерний процесс, мы _скорее всего_ хотим знать, что с ним произошло, как минимум, код возврата. 36 | Если наш "сын" завершилмся, то система не удаляет о нем информацию полностью, а хранит где-то некоторую инфу о нём (retcode, статистика использования ресурсов, и.т.д.) 37 | Такие завершившиеся, но не удаленные процессы и называются **зомби**. Правила гласят, что родитель должен сам закапывать своих зомби. 38 | Например, если родитель создал процесс с помощью `fork`, то затем он должен вызвать `wait`, чтобы "забрать" информацию о сыне и удалить его из системы. 39 | 40 | ```c 41 | pid_t wait(int *status); //Ждёт любого сына 42 | pid_t waitpid(pid_t pid, int *status, int options); //Ждёт процесс с заданным pid 43 | ``` 44 | 45 | На этом основано действие **fork-бомбы**. Если процесс-родитель не закапывает своих зомби, а только порождает новых, рано или поздно в системе кончатся дескрипторы для процессов, и вам будет очень-очень грустно. 46 | 47 | Последнее замечание: если мы удаляем процесс, у которого ещё остались сыновья, то они переподвешиваются напрямую к `init`'у и считаются "осиротевшими". `init` их обычно должен убивать, но есть баганные реализации. Упс. 48 | 49 | ## Немного о sudo 50 | 51 | > Процессы представлены в виде дерева, где корнем является `init`, который запускает все остальные процессы. 52 | 53 | Где-то там есть и наш `bash`, в который мы залогинились, например, за Петю с `uid=1000`. 54 | Теперь мы хотим сделать что-то, что требует повышенных привелегий, например, установить программу. Разлогиниваться и входить за root'a - долго и небезопасно. 55 | Прошаренные линуксоиды, конечно, знают ответ: `sudo apt-get install`. 56 | 57 | *NB: 58 | у процесса нет `uid`, а есть целых три:* 59 | 60 | ruid = real uid //uid пользователя, который его запустил 61 | euid = effective uid //uid пользователя, по которому проверяются права 62 | suid = ... пользователь, который был до смены 63 | 64 | `sudo` делает так, что процесс, который он вызывает (`apt` в нашем случае), выполняется с правами другого пользователя, в т.ч. `sudo` может запустить программу с администраторскими правами - как будто его `uid` равен 0. 65 | Как это происходит? Вспоминаем два последние бита `mode`, которые мы до сих пор не рассмотрели. Они называются `setuid` и `setgid`. Если они установлены в 1, то процесс запускается с правами, не текущего пользователя, а **владельца (группы владельца, если gid)** файла. 66 | `sudo` лежит в `/usr/bin` и его владельцем является root. Он получает соответствующие права, проверяет пароль, и если Вася действительно обладает необходимыми правами, то запускает `apt`. 67 | > Все программы, которые хотят проверять пароли (`/etc/shadow`, в котором хранятся хэши паролей, принадлежит root'у), или делать что-то ещё привелигированное, должны заранее обладать `setuid`, чтобы иметь возможность это проверять. 68 | Проверка прав пользователя, который их запустил, лежит также на их совести. 69 | 70 | Однако так же есть функция `setuid`, которая позволяет менять эффективный `uid`. 71 | Очевидно, что нам бы не хотелось, чтобы кто-то вдруг сделал `setuid(0)` и стал рутом, поэтому этой функцией может пользоваться только тот, у кого есть разрешение менять `uid` - `CAP_SETUID` (курите `man 7 capabilities`, чтобы познать). 72 | У шелла рута и его потомков он обычно есть. 73 | Впрочем, кто угодно может вызвать `setuid(n)`, где `n` - сохранённый или реальный `uid` процесса. 74 | 75 | Забавный факт - `setuid` не гарантирует своей работы и может завершиться с ошибкой `EAGAIN` (вызови меня ещё раз, может получиться). 76 | -------------------------------------------------------------------------------- /practices/practice2/practice2.md: -------------------------------------------------------------------------------- 1 | *01.03.2016* 2 | 3 | *Устаревшие комментарии Стаса* 4 | > Да начнётся вторая практика по ОСям, ещё тёпленькая после ох$нной Джавки :333 5 | Однажды я может сделаю это не в txt-шнике, а как элитный Артем с tex-ом, но пока 6 | да здравствует минимализм. 7 | 8 | В прилагающейся папке всякие простенькие скриптики на баше. 9 | 10 | #Bash Scripts 11 | 12 | Система при попытке запустить файл смотрит в него. Если файлик начинается с `#!`, то 13 | система интерпретирует оставшуюся часть файла, как скрипт. 14 | 15 | Например: 16 | 17 | **hello.sh** 18 | ``` 19 | #!/bin/sh 20 | echo hello 21 | ``` 22 | 23 | Система делает: 24 | ``` 25 | /bin/sh echo hello 26 | ``` 27 | 28 | Даём права на запуск `chmod +x ./hello.sh`. И радуемся жизни. 29 | 30 | **vars.sh** 31 | 32 | A=5 33 | echo $A "$A" '$A' 34 | 35 | Вывод: 36 | > 5 5 $A 37 | 38 | Всякие циклики 39 | 40 | for i in 1 2 3 4 41 | do 42 | echo $i 43 | done 44 | 45 | Вывод: 46 | > 1 2 3 4 47 | 48 | **ls.sh** 49 | 50 | for i in `ls` ; do 51 | echo $i 52 | done 53 | 54 | Вывод: аналогичен ls 55 | 56 | \`ls\` эквивалентно $(ls) (команда в апострофах выполняется и результат подставляется в код) 57 | 58 | Есть чудесный while. 59 | Код 0 - true. 60 | Код не 0 - false. 61 | 62 | test - хорошая команда, которая проверяет expression-ы и выходит с каким-то кодом. 63 | Можно просто обрамить квадратными скобками. Будет аналогично. 64 | Но есть проблемы с <, >, ||, &&. Поэтому сделали двойные квадратные скобочки. 65 | Внутри них не действуют правила для специальных символов. Хихикс. 66 | 67 | Баш может в арифметику: 68 | `$((2 + 3 + $N))` 69 | 70 | "Advanced Bash Scripting Guide" - для любителей 71 | поизвращаться есть целая библия по Башу. 72 | 73 | case $var in 74 | a) 75 | ;; 76 | b) 77 | ;; 78 | *) //default 79 | ;; 80 | esac //ВОТ ТАКОЙ ВОТ СИНТАКСИС ММ 81 | 82 | ТУТ ДАЖЕ ЕСТЬ ФУНКЦИИ (ГОСПОДИ 10 ИЗ 10) 83 | 84 | name() { 85 | echo $1, $2, $* //$i - итый аргумент, $* - все аргументы 86 | } 87 | 88 | Вызов 89 | `name arg1 arg2` 90 | 91 | Немного очевидностей. 92 | 93 | a && b - если а, то запустится b. 94 | a || b - если a вернёт не ноль, то делает b. 95 | a;b - просто выполнит по порядку. 96 | 97 | globbing - фича баша 98 | 99 | `ls *.c` Распарсит вам масочку в `ls a.c b.c` 100 | 101 | * \* - какая-то последовательность 102 | * ? - любой символ 103 | 104 | #Регулярные выражения. 105 | Это как бы язык такой, который позволяет задавать шаблоны для строк. 106 | 107 | . - любой символ 108 | [abc], [a-zA-z] - интервалы 109 | [^a-k] - дополнение к группе. Всё кроме (a-k). 110 | R* - R 0 или более раз 111 | R+ - R 1 или более раз 112 | R? - 0 или 1 раз 113 | A|B - A или B 114 | (R) - найти и запомнить 115 | \1, \2 ... \i - взять значение i-ой скобочки 116 | ^ - начало строки 117 | $ - конец строки 118 | 119 | Туториал по регуляркам есть в этих ваших интернетах. 120 | 121 | Примеры: 122 | Рассмотрим регулярочку 123 | `(a+b)c\1` 124 | 125 | Посмотрим, как регулярка подходит под строки: 126 | * abcab - match. Потому что в первую скобочку запомнили ab. 127 | * abcaab - ничего :( В первую скобочку запомнили ab, а вот aab не подходит. 128 | * aabcaab - match. Потому что в первую скобочку запомнили aab 129 | 130 | 131 | #Немного про консольные утилиты (grep, sed, xargs) 132 | ##grep 133 | Берёт регулярку и выделяет в stdout, те строчки из stdin, что подходит под эту регулярку. 134 | Штука довольно примитивная, но в расширенный синтаксис с помощью флажка -E умеет. 135 | Так например, если сделать: 136 | 137 | echo -e > test.txt abcab'\n'abcaab'\n'aabcaab 138 | grep -E '(a+b)c\1' < test.txt 139 | 140 | То в консольку выведется abcab и aabcaab. 141 | 142 | ##sed - Stream EDitor 143 | Истинный текстовый редактор для тех, кому даже Vim кажется чересчур 144 | громоздким и слишком элегантным. Работать с ним в принципе, как с Vim-ом, 145 | только вслепую. Здорово, правда? 146 | Из функционала регулярок нас интересует команда 147 | 148 | 's/[regexp]/[чем]/[флаги]' 149 | 150 | Собственно говоря, s значит substitute, и всё подходящее под regexp, наш доблестный sed 151 | заменит на то, что в [чем]. (В Vim тоже такая команда есть, кук-карекууу!) Примечателен флаг g, значащий global, так как он поменяет 152 | все вхождения регулярки, а не только первое встреченное. Флаг самой команды -re позволит 153 | нам применять регулярку с расширенным функционалом. 154 | Пример: 155 | 156 | echo > test2.txt 123454321 157 | sed -re 's/([0-9]+)/\1\1/g' 158 | 159 | Выведет нам в консольку: 160 | `123454321123454321`. 161 | Где найти больше информации вы знаете (MAN). 162 | 163 | 164 | ##xargs cmd. 165 | Читает строки из ввода и запускает cmd для каждой строки. 166 | Например: 167 | 168 | ls > files 169 | xargs rm < files 170 | 171 | Результат попробуйте угадать или запустите данную последовательность команд 172 | сами в самой важной папке на вашей машине. 173 | 174 | Достойные внимания флажки xargs: 175 | 176 | -P - запустить xargs в n потоков. 177 | -n - применять команду не к каждой строке, а к каждым n строк. 178 | -I replace-str - заменяет replace-str в аргументах на что-то из stdin. 179 | 180 | Пример с последним флажком: 181 | 182 | **a.c, b.c** 183 | 184 | int main(int argc, char* argv[]) { 185 | return 0; 186 | } 187 | 188 | **src** 189 | ``` 190 | a 191 | b 192 | ``` 193 | 194 | `xargs -I FILE gcc "FILE".c -o "FILE".o < src` 195 | 196 | Соберёт нам два объектника a.o и b.o. 197 | 198 | ##pipe 199 | Есть ~~два стула~~ несколько программ. a, b, c, d. 200 | И мы хотим, чтобы вывод a пошёл на ввод к b, вывод b на ввод к c и так вся 201 | эта программная многоножка бы заключилась между stdin и stdout. 202 | Окей. 203 | `a|b|c|d` cделает это за нас. 204 | Так например самый первый пример из xargs можно переписать как: 205 | 206 | ls|xargs rm 207 | 208 | Почему это хорошо? Потому что реализована параллельность. 209 | Более точное описание реализации есть в `man 2 pipe` (невероятно, но да). 210 | Примерно так: есть буфер фиксированного размера (чёт про ~64Кб заехало), 211 | куда пишет программа. Если он заполнился, то программа туда не пишет. 212 | Из этого же буфера читает следующая в пайпе программа, поэтому по мере 213 | чтения в буфере освобождается место. Так всё и происходит. 214 | Если есть желание явно создать файл, исполняющий роль буфера и работающий 215 | именно по такой технологии: 216 | 217 | mkfifo [filename] 218 | 219 | Так например `a|b|c` можно записать, как: 220 | 221 | mkfifo ab; mkfifo bc 222 | a > ab 223 | b < ab > bc 224 | c < bc 225 | 226 | Но чтобы обеспечить параллельность добавьте в конец комманды амперсанд, тогда 227 | она запустится в фоне (bg aka background). Иначе пока не выполнится первая, вы ничерта не введёте. 228 | 229 | Интересные команды: 230 | 231 | `fg [job_id]` - переключает команду с id=job_id на передний план с фона. 232 | 233 | `jobs` - выводит список программ, выполняющихся в фоне. 234 | Подробности (НЕТ НЕ В MAN-е! По ним почему-то страниц в мане нет) в интернетах. 235 | --------------------------------------------------------------------------------