Семинар 04. Терминал Unix, процессы, оптимизация fork. - chrislvt/OS GitHub Wiki
Линукс – это unix-подобная операционная система. При включении запускается большое количество демонов, и они бегут столбиком показывают, какие модули ядра загружаются. Терминал – псевдотерминал. Можно запустить любое количество псевдотерминалов.
В Unix имеется мануал. Если набрать man man
, то мы войдём в описание этого мануала. Есть еще и help
.
Юниксовая командная строка критична к пробелу.
cd
– изменить директорию
mkdir
– создание директории
Команда ps
. F - флаги: флаг = 1 – был fork(), не было exec();
Системный вызов fork(). Создает новый процесс.
Процесс-потомок. Для этого процесса создаются собственные таблицы страниц (оптимизация fork). Дескрипторы страниц адресного пространства потомка ссылаются на страницы адресного пространства предка. Для страниц адресного пространства предка права доступа меняются на only-read. И устанавливается флаг copy-on-write (копирование при записи). Если предок или потомок пытаются изменить какую-либо страницу, то возникает исключение по правам доступа. Выполняя это исключение, обнаруживается установленный флаг copy-on-write и в результате создается копия страницы в адресном пространстве того процесса, который пытался ее изменить. Таким образом создаются только копии некоторых страниц. Такая ситуация с измененными правами доступа и установленным флагом copy-on-write существует до тех пор, пока процесс-потомок не вызовет системный вызов exec или системный вызов exit. Тогда права доступа к страницам предка возвращаются к обычным read/write для дата и стека, и флаг copy-on-write сбрасывается. Использование флага позволило сделать основным страничное управление памятью (до этого возникали проблемы с коллективным использованием страниц, флаг решил эту проблему).
Флаг = 1 - был fork(), не было exec(). Показывает, что права доступа к страницам адресного пространства предка изменены и установлен флаг copy-on-write. Эта информация присутствует в информации о процессе, кроме того, что эта информация - флаг copy-on-write - устанавливается в дескрипторах страниц. Эта информация находится в процессе.
Флаг = 4. - used superuser privileges. Позволяет использовать привилегии суперюзера – может использовать привилегированные команды и имеет доступ к структурам ядра. Ядро – совокупность огромного количества структур. Суперюзер имеет доступ к коду ядра. В частности, только имея права суперюзера, можно включать в ядро собственные загружаемые модули ядра. Иначе невозможно.
Флаг = 0 – был fork() и был exec(). Никакой флаг copy-on-write не устанавливается.
ps -al
.
-
1 столбик – F – флаги.
-
2 столбик – S – стейты (состояния).
- R – running or runnable – исполняется или может исполняться. Система не различает выполняется процесс или находится в очереди готовых процессов. То есть система не различает, получил процесс процессорное время или ждет, когда оно будет ему выделено.
- S – interruptible sleep
- D - uninterruptible sleep (usually IO) – обычно ввод-вывод. Блокировку в ожидании завершения ввода-вывода прервать нельзя. Почему? Потому что это многоэтапный процесс. Начинается он с системного вызова, который выполняет приложение – с запроса на ввод-вывод. В результате система переходит в режим ядра (переключается контекст). В режиме ядра вызывается драйвер соответствующего устройства, например драйвер клавиатуры. Драйвер формирует команду, которая по шине данных передается контроллеру устройства. После этого контроллер начинает управлять операцией ввода-вывода, процессор отключается. По завершении операции ввода-вывода контроллер формирует сигнал прерывания, который поступает на контроллер прерывания, а от него на вход процессора. И процессор переключается на выполнение обработчика прерывания. Задача обработчика прерывания – скопировать данные из регистра данных контроллера в буфер ядра. Обработчик является одной из точек входа драйвера устройства. Затем срабатывают обратные вызовы. Процесс, запросивший ввод-вывод, должен быть активирован (то есть все время он блокирован, а когда завершается операция ввода-вывода он должен быть активирован, чтобы передать ему данные). Это будет uninterruptable sleep.
- T – terminate
- Z – zombie. Процесс, у которого отобраны все ресурсы, кроме последнего – строки в таблице процессов. Название “таблица процессов” является условным, в системе ее нет. В системе есть связные списки. Таблицы неудобны для работы системы, их сложно редактировать. Все процессы при завершении проходят через состояние зомби, но некоторые задерживаются в этом состоянии. Связано это с системным вызовом wait. Поскольку процесс-предок может с помощью wait блокироваться в ожидании завершения своих потомков (а собственно системный вызов wait возвращает процессу-предку статус завершения потомка), если процесс потомок завершается раньше, чем предок вызовет wait, и не предпринять специальных действий, то получится, что предок будет заблокирован на wait’е навсегда.
-
3 столбик – юзер id
-
4 – process id
-
5 - parent process id – идентификатор процесса-предка.
-
6 – pri – приоритет. Системная составляющая приоритета.
-
7 – nice пользовательская составляющая приоритета.
-
8 – wchan – на чем блокирован процесс. Блокировку на вводе-выводе мы здесь не увидим.
-
9 – tty – terminal
-
10 – time
тут начались расхождения
- 11 – size – размер чего-то ???
- 12 – имя
- 13 – c – процент использования процессорного времени
ps -ajx
. Системные демоны.
В системе всегда есть 2 процесса: с id = 0 и id = 1.
- 0 – init – процесс, запустивший систему.
- 1 – процесс, как говорят, открывший терминал (тоже называется init на компе).
kernel thread daemon – идентификатор pid = 2 – процесс, который запускает остальных демонов. У остальных демонов parent id = 2. Демоны – это потоки ядра.
Процесс-сирота - процесс, у которого завершился предок.
Структура, описывающая процесс:
Struct proc.
/*
* Description of a process.
*
* This structure contains the information needed to manage a thread of
* control, known in UN*X as a process; it has references to substructures
* containing descriptions of things that the process uses, but may share
* with related processes. The process structure and the substructures
* are always addressable except for those marked "(PROC ONLY)" below,
* which might be addressable only on a processor on which the process
* is running.
*/
struct proc {
struct proc *p_forw; /* Doubly-linked run/sleep queue. */
struct proc *p_back;
struct proc *p_next; /* Linked list of active procs */
struct proc **p_prev; /* and zombies. */
/* substructures: */
struct pcred *p_cred; /* Process owner's identity. */
struct filedesc *p_fd; /* Ptr to open files structure. */
struct pstats *p_stats; /* Accounting/statistics (PROC ONLY). */ struct plimit *p_limit; /* Process limits. */
struct vmspace *p_vmspace; /* Address space. */
struct sigacts *p_sigacts; /* Signal actions, state (PROC ONLY). */
#define p_ucred p_cred->pc_ucred
#define p_rlimit p_limit->pl_rlimit
int p_flag; /* P_* flags. */
char p_stat; /* S* process status. */
char p_pad1[3];
pid_t p_pid; /* Process identifier. */
struct proc *p_hash; /* Hashed based on p_pid for kill+exit+... */
struct proc *p_pgrpnxt; /* Pointer to next process in process group. */
struct proc *p_pptr; /* Pointer to process structure of parent. */
struct proc *p_osptr; /* Pointer to older sibling processes. */
/* The following fields are all zeroed upon creation in fork. */
#define p_startzero p_ysptr
struct proc *p_ysptr; /* Pointer to younger siblings. */
struct proc *p_cptr; /* Pointer to youngest living child. */
pid_t p_oppid; /* Save parent pid during ptrace. XXX */
int p_dupfd; /* Sideways return value from fdopen. XXX */
/* scheduling */
u_int p_estcpu; /* Time averaged value of p_cpticks. */
int p_cpticks; /* Ticks of cpu time. */
fixpt_t p_pctcpu; /* %cpu for this process during p_swtime */
void *p_wchan; /* Sleep address. */
char *p_wmesg; /* Reason for sleep. */
u_int p_swtime; /* Time swapped in or out. */
u_int p_slptime; /* Time since last blocked. */
struct itimerval p_realtimer; /* Alarm timer. */
struct timeval p_rtime; /* Real time. */
u_quad_t p_uticks; /* Statclock hits in user mode. */
u_quad_t p_sticks; /* Statclock hits in system mode. */
u_quad_t p_iticks; /* Statclock hits processing intr. */
int p_traceflag; /* Kernel trace points. */
struct vnode *p_tracep; /* Trace to vnode. */
int p_siglist; /* Signals arrived but not delivered. */
struct vnode *p_textvp; /* Vnode of executable. */
char p_lock; /* Process lock (prevent swap) count. */
char p_pad2[3]; /* alignment */
/* End area that is zeroed on creation. */
#define p_endzero p_startcopy
/* The following fields are all copied upon creation in fork. */
#define p_startcopy p_sigmask
sigset_t p_sigmask; /* Current signal mask. */
sigset_t p_sigignore; /* Signals being ignored. */
sigset_t p_sigcatch; /* Signals being caught by user. */
u_char p_priority; /* Process priority. */
u_char p_usrpri; /* User-priority based on p_cpu and p_nice. */
char p_nice; /* Process "nice" value. */
char p_comm[MAXCOMLEN+1];
struct pgrp *p_pgrp; /* Pointer to process group. */
struct sysentvec *p_sysent; /* System call dispatch information. */
struct rtprio p_rtprio; /* Realtime priority. */
/* End area that is copied on creation. */
#define p_endcopy p_addr
struct user *p_addr; /* Kernel virtual addr of u-area (PROC ONLY). */
struct mdproc p_md; /* Any machine-dependent fields. */
u_short p_xstat; /* Exit status for wait; also stop signal. */
u_short p_acflag; /* Accounting flags. */
struct rusage *p_ru; /* Exit information. XXX */
};
Это структура, которая описывает процесс в unix bsd. В линукс процесс описывается структурой struct task_struct. Рязанова сказала найти самим, большая структура.
/* Doubly-linked run/sleep queue. */
Видим в начале указатели, список двусвязный. Доказательство того, что не таблица, а список.
struct vmspace *p_vmspace; /* Address space. */
указатель на таблицу страниц.
Дальше сигналы, дальше флаги, снова тот же статус, как ранее, pid, ид группы, далее
struct proc *p_pptr; /* Pointer to process structure of parent. */
указатель на структуру процесса-предка. То есть процесс потомок имеет указатель на процесс-предок.
Дальше
struct proc *p_osptr; /* Pointer to older sibling processes. */
самый старый процесс из родственников
struct proc *p_ysptr; /* Pointer to younger siblings. */
самый молоденький, это созданный после всех.
struct proc *p_pgrpnxt; /* Pointer to next process in process group. */
группа процессов объединяется указателями.
struct proc *p_cptr; /* Pointer to youngest living child. */
указатель на самого молодого живущего потомка.
Эти указатели создают иерархию процессов в отношении предок-потомок. То есть Unix поддерживает иерархию процессов в отношении предок-потомок и охраняет эту иерархию.
Сирота – процесс, у которого завершился предок. Значит он потеряет указатель на процесс-предок и иерархия накрылась медным тазом, а система этого допустить не может. Когда завершается процесс-предок, система проверяет, есть ли у него живущие потомки и начинает выполняться процесс усыновления. Система не может знать, что это процесс-предок. То есть при завершении любого процесса система будет проверять, не остались ли у него не завершившиеся потомки. Также как процесс-потомок не может знать, вызывается ли в предке wait или нет.
Далее рассматривается программа из методы по 1-ой лабе по Unix:
#include <stdio.h>
int main()
{
int childpid;
if ((childpid = fork())== -1)
{
perror(“Can’t fork.\n”);
return 1;
}
else if (childpid == 0)
{
while (1) printf(“ %d “, getpid());
return 0;
}
else
{
while(1) printf(“ %d “, getpid());
return 0;
}
}
Если запустить эту программу, то сначала выводится полоска только child, потом только parent.
Это говорит о том, что время квантуется. Значит Linux - ОС разделения времени.
В Unix BSD сироту усыновляет процесс с идентификатором = 1, а в Ubuntu - User init. В Ubuntu между процессом, открывшим терминал и вот этими процессами, которые запускаются в системе, есть несколько посредником. Они часто называются User init.
Каких-то демонов (процессы Daemon) можно убить, например, своего демона. А системных нет, при попытке не будет никакой реакции.Безусловное убийство: kill -9
Если убить всех, то у parent-a будет флаг = 0, у child-a флаг = 1. Флаг 0 нам говорит, что был fork и был exec. Они оба были в командной строке, в shell-е ты указал имя программы, вот был fork и был exec. А в child-e нет exec, поэтому флаг = 1.
Если ввести команду ls -al видим вывод в консоли, разберем, что там есть:
Система различает 7 типов файлов:
- d - directory(директория)
- -(прочерк) - обычный файл (regular)
- l - softlink
- p - именованный программный канал
- s - socket
- c - char(спец. файл символьного устройства)
- b - block(спец. файл блочного устройства)
Обычный файл - это структура, предназначенная для долговременного хранения информации. Файлы располагаются на энергонезависимых устройствах, таких как винчестер, SSD, Flash-ки.
Файл - это любая поименованная совокупность данных (мб бессмысленная), главное, чтоб она была поименованной.
Кроме обычных файлов есть другие типы файлов - это парадигма Unix. В Unix - всё файл, устройство - тоже файл.
Затем следующие 9 букв - права доступа. В Unix всегда устанавливаются 3 типа прав доступа:
- User
- Group
- Others(все остальные)
Группа - важнейшее понятие Unix-a. Мы уже видели указатель на процессы группы. Права доступа:
- read
- write
- execute
Затем идет столбик чисел - это Hardlink-и.
В Unix-e поддерживаются 2 типа линков. Hardlink - это еще одно равноправное имя файла. Имя файла - не является идентификатором файла, в Unix - это INode-ы (indexNode - смысла название не имеет, так исторически сложилось)
Набираем ls -ail (видим INode-ы - очень большое целое число). Номер INode-ы и является идентификатором файла.
Softlink - спец. файл, обозначается буквой l, который содержит в себе путь к файлу - символьная строка (НЕ указатель на файл, адрес!!!!! Скажете так, вам будет легче самому числануться). Разбирая эту строку, система находит этот файл.
Дальше видим фактически директорию, затем идет size, дата.
Затем мы видим точку, две точки - dot-to-dot, и имя файла.