Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Операционные системы (часть 1)

.pdf
Скачиваний:
11
Добавлен:
24.03.2015
Размер:
219.83 Кб
Скачать

}

Программа date вытесняет текущий процесс, поэтому строка с сообщением об ошибке будет выполнена только в случае, если загрузка date не удалась.

Сохранить текущий процесс при вызове нового можно только с помощью fork().

#include<iostream>

#include<unistd.h> main() {

if (fork() == 0) {

std::cout << "This is child\n"; execlp("date", "date", 0);

std::cerr << "Can't execute program 'date'\n";

}

else

std::cout << "This is parent\n";

}

Функция wait() из <sys/wait.h> позволяет дождаться окончания выполнения какого-нибудь порождаемого процесса.

#include<iostream>

#include<unistd.h>

#include<sys/wait.h> main() {

if (fork() == 0) {

std::cout << "This is child\n"; execlp("sleep", "sleep", "10", 0);

std::cerr << "Can't execute program 'sleep'\n";

}

else {

int status;

std::cout << "This is parent\n"; wait(&status);

std::cout << "ok\n"; //this message appeared after 10 sec

}

}

Значение status, возвращаемое wait(), содержит, в частности, код возврата процесса потомка. Результат wait() --- это номер завершившегося процесса или -1 в случае ошибки. Можно вызывать wait() с аргументом 0, если возвращаемое значение неважно.

Ввод-вывод системного уровня обеспечивается функциями из <unistd.h> read() --- читать из файла,

write() --- писать в файл,

dup() --- создать копию дескриптора файла, close() --- закрыть файл,

unlink() --- отсоединить, уничтожить жесткий соединитель (файл),

lseek() --- искать позицию в файле.

И функциями из <fcntl.h> creat() --- создать файл, open() --- открыть файл.

Некоторые стандартные константы для некоторых из этих функций определены в <sys/types.h> и <sys/stat.h>.

Файл на низком уровне задается дескриптором --- целым числом, оно используется, в частности, при перенаправлении потоков ввода-вывода.

Рассмотрим программу, которая печатает два раза все, что будет введено с клавиатуры.

#include<unistd.h> main() {

char buf[8], n, cp1;

cp1 = dup(1); //copy of std output while ((n = read(0, buf, 8)) > 0) { write(1, buf, n);

write(cp1, buf, n);

}

}

Создание и работа с файлом.

#include<unistd.h>

//#include<sys/types.h>

//#include<sys/stat.h>

#include<fcntl.h> main() {

char fn[] = "testio.txt", buf[11] = "123456\nok\n"; int fd = creat(fn, 0664); //0664 - mode

for(int i = 0; i < 3; i++) write(fd, buf, 7); close(fd);

fd = open(fn, 1); //0=O_RDONLY - read, 1=O_WRONLY - write, 2=O_RDWR - read & write;

//можно устанавливать и другие флаги, см. man 2 open lseek(fd, 14, 0); //3rd arg is the same as at fseek

buf[0] = '*'; write(fd, buf, 1); close(fd);

write(1, buf+7, 3); //prints "ok"

} //creates text file 'testio.txt' with 3 lines: 123456 // 123456 // *23456

Для обмена данными между процессами можно использовать трубопроводы, создаваемые функцией pipe() из <unistd.h>, у которой один аргумент --- массив из двух целых чисел. Первый элемент массива --- это выход из трубопровода, для

чтения данных, а второй --- это вход.

#include <sys/wait.h> #include <cstdio> #include <unistd.h> #include <cstring> using namespace std; int main() {

int pipefd[2]; pid_t cpid; char buf;

if (pipe(pipefd) == -1) { fputs("pipe\n", stderr); return 1;

}

cpid = fork(); if (cpid == -1) {

fputs("fork\n", stderr); return 2;

}

puts("ok"); //напечатается 2 раза

if (cpid == 0) { //Порожденный процесс читает из трубопровода close(pipefd[1]); //Закрытие ненужного входа в трубопровод while (read(pipefd[0], &buf, 1) > 0)

write(STDOUT_FILENO, &buf, 1); write(STDOUT_FILENO, "\n", 1); close(pipefd[0]);

return 0;

}

 

 

else {

//Базовый процесс пишет строку в трубопровод

char string[] = "Hello from the parent to the child";

close(pipefd[0]);

//Закрытие ненужного выхода из трубопровода

write(pipefd[1], string, strlen(string));

close(pipefd[1]);

//получатель данных получит EOF

wait(0);

//ждём завершения работы получателя

return 0;

 

 

}

 

 

}

Вызов dup в подобных программах позволяет связать концы трубопровода со стандартными потоками ввода-вывода, что соответствует | в оболочке ОС.

Процессы могут посылать друг другу сигналы. В частности, если порождённый процесс заканчивается или приостанавливается, то автоматически генерируется сигнал SIGCHLD. Функция signal() из <signal.h> устанавливает обработчик сигнала с заданным номером, а функция kill() из этого же заголовка используется для генерации заданного сигнала.

#include <cstdio> #include <sys/wait.h>

#include <signal.h> #include <unistd.h> using namespace std;

void signalHandler(int signal) { printf("Cought signal %d!\n", signal); if (signal == SIGCHLD) {

puts("Child ended");

wait(0); //для совместимости

}

}

int main() {

signal(SIGALRM, signalHandler); signal(SIGUSR1, signalHandler); signal(SIGCHLD, signalHandler); signal(SIGINT, signalHandler); //Control-C if (!fork()) {

puts("Child running..."); sleep(2);

puts("Child sending SIGALRM...");

kill(getppid(), SIGALRM); //послать сигнал родителю sleep(10);

puts("Child exitting..."); return 0;

}

printf("Parent running, PID=%d. Press ENTER to exit.\n", getpid()); fgetc(stdin);

puts("Parent exitting..."); return 0;

}

Функции getpid() и getppid() из <unistd.h> возвращают соответственно номера текущего и порождающего процессов. Функция sleep() из <unistd.h> --- это задержка на заданное число секунд. При исполнении заданной программы ей можно посылать сигнал SIGUSR1 для перехвата, например, с командной строки, запуская kill -SIGUSR1 НОМЕР-ПРОЦЕССА.

Средства взаимодействия процессов (IPC --- Inter-process communication) помимо сигналов и трубопроводов (с именем и без) включают семафоры, разделяемую память (shared memory), файлы (обычные и отображаемые в память), сокеты и очереди сообщений.

Отображаемый в память файл (Memory-mapped file) --- весь или частично доступен для прямого доступа по заданным адресам оперативной памяти.

Сложности работы с процессами можно проиллюстрировать следующим примером.

#include<iostream>

#include<unistd.h> main() {

for (int i = 0; i < 2; ++i) {

fork(); std::cout << '.';

//std::cout.flush(); //std::cerr << '.';

}

} //8/6 точек с cout/cerr

Функция clone() в Linux служит для создания сопроцессов или нитей (threads)

--- процессов, разделяющих общую память и другие ресурсы. Управление сопроцессами проводится при помощи средств, похожих на те, что используются для управления процессами: семафоры, мьютексы, .... Библиотека NPTL (New Posix Thread Library) содержит необходимые средства для работы с сопроцессами. Они вводятся заголовком <pthread.h> си/си++. В стандарте си++ 2011 года для этого определяются заголовки <thread>, <mutex>, <condition_variable> и <future>.

Структура файловой системы NTFS

Общая организация данных на диске

BOOT - MFT - FREE SPACE - ADDITIONAL - FREE SPACE - HIDDEN COPY METADATA OF BOOT

Все элементы NTFS считаются файлами. Индексы всех файлов хранятся в

MFT (Master File Table). MFT указывает на BOOT-сектор вначале диска и на себя, а BOOT-сектор указывает на MFT. В дополнительных метаданных хранится наряду с другой информацией частичная копия MFT, что позволяет восстановить MFT

вслучае ее повреждения, а также журнал очереди операций с диском - в случае системного краха этот журнал используется для приведения раздела

врабочее состояние.

Основные характеристики NTFS

-размер кластера от 512 байт до 64 КБ (1, 2, 4, 8, 16, 32) -- назначение кластеров такое же как и в FAT

-все элементы за исключением BOOT-сектора можно перемещать

-поддерживаются именованные потоки данных (независимо от файлов)

-атрибуты файла могут занимать несколько не обязательно последовательных записей MFT

-к атрибутам файла относят и данные этого файла

-журнал операций

-использование Unicode

-поддержка сжатия информации (модификация LZ77)

-поддержка шифрования данных

-жесткие соединители -- получаются созданием нескольких атрибутов-имен для файла

-поддержка управления доступом -- обеспечение безопасности данных

Запись в MFT по каждому файлу состоит атрибутов. Файлы разделяют на нормальные и метаданные. Среди атрибутов есть:

-стандартная информация

-список атрибутов -- требуется если у файла очень много атрибутов -- содержит ссылки на расположения дополнительных атрибутов

-имя файла, состоит из нескольких полей

-идентификаторы файла -- ссылки на место создания, уникальное имя в системе, первоначальное уникальное имя

-дескриптор безопасности -- содержит информацию о владельце и группе пользователей, о правах доступа к файлу для разных категорий пользователей, о типе операций, которые должны документироваться в отчетах системы

-имя раздела диска (до 127 уникодов)

-информация по разделу диска (версия NTFS: 1.2 -- NT, 3.0 -- 2000, 3.1 -- XP; флажки общего состояния)

-данные -- можно иметь несколько таких атрибутов (как в Mac OS) -- каталоги, как правило, не имееют такого атрибута, файл должен иметь в точности один такой неименованный атрибут и сколько угодно именованных

-корневой узел -- это корень B-дерева, реализующего проиндексированные данные, в частности, каталог, идентификаторы объектов, идентификаторы владельцев

-битовая карта (bitmap) -- используется в индексах, например, каталогах и

вMFT, -- каждому элементу данных в этой карте соответствует бит, что позволяет разделять эти элементы на занятые и свободные

-точка переразбора (reparse point) -- символические соединители.

Стандартная информация это:

-время создания файла;

-время последнего изменения файла;

-время последнего изменения MFT;

-время последнего обращения к файлу;

-флажки DOS (DOS permissions): Read Only;

Hidden;

System;

Archive;

Device -- "файл" соответствует устройству как "файлы" в Linux каталоге /dev;

Normal;

Temporary -- временный файл;

Sparse file -- "разбросанный файл" -- файл в котором могут быть "дыры", т.е. участки, для которых на диске не выделяется место, -- считается, что в "дырах" содержатся 0

Reparse point -- символический соединитель с файлом или дисковым разделом; Compressed -- сжатый файл;

Offline -- недоступный файл, получить который можно по специальному запросу;

Encrypted -- зашифрованный файл;

Not content indexed -- содержимое файла не проиндексировано.

-максимальное число версий файла;

-версия файла;

-идентификатор владельца файла;

-идентификатор безопасности.

Время записывается количеством 0.1 microsec с 1 января 1601 года.

Имя файла -- это:

-ссылка на содержащий файл каталог;

-4 временных "штампа", таких же как и в стандартной информации;

-размер файла в кластерах;

-размер файла в байтах;

-флаги -- такие же как и в стандартной информации и еще несколько дополнительных, в частности, флаг каталога;

-длина имени файла;

-имя файла (до 255 уникодов) -- согласно POSIX нельзя использовать код

0 и "слэш", Microsoft Windows вводит дополнительные ограничения на ряд символов, в частности, :\*?.

Права доступа определяются набором из 32 бит. Эти биты определяют доступность для пользователя следующих операций с файлом:

-чтение;

-выполнение;

-добавление данных к концу файла (append);

-удаление файла;

-изменение прав доступа (ACL - Access Control List);

-изменение владельца;

-запись.

Каждый атрибут имеет заголовок, структура которого определяется наличием у атрибута имени и его резидентностью. Резидентные атрибуты нельзя сжимать -- они хранятся в MFT.

Последовательность кластеров, образующая нерезидентный атрибут, записывается в "трассе данных" (data runs), в которой последовательности номеров виртуальных кластеров (VCN -- virtual cluster number) сопоставляются

номера реальных кластеров (LCN -- logical cluster number). Первый виртуальный кластер имеет номер 0. "Трасса данных" может являться частью (полем) атрибута. Такие части атрибутов, в частности, заголовок, а также

способы организации данных, например, компрессии или "разбросанного файла", называют "концепциями". Среди концепций NTFS можно еще назвать: B-деревья, кластеры, правила сопоставления букв (большие--маленькие), каталоги, файлы, файловые записи в MFT, ...

Каждому файлу соответствует i-узел или MFT-запись. Все MFT-записи пронумерованы. Первые 24 записи соответствуют системным файлам (метаданным), в частности, 0 -- это сама MFT, 1 -- ее частичная копия, 2 -- журнал

транзакций, 3 -- информация по разделу (время создания, номер, ...), 4 -- определения поддерживаемых разделом атрибутов, 5 -- корневой каталог,

6 -- битовая карта занятых/свободных кластеров раздела, 7 -- BOOT-сектор, 8 -- список поврежденных секторов, 9 -- дескриптор безопасности раздела,

10 -- таблица заглавных букв. Некоторые метаданные имеют номер, больший 23.

NTFS поддерживает почти все возможности POSIX, все возможности HFS (используется в компьютерах Apple Macintosh) и HPFS (OS/2).

Структура загрузочных записей диска

Каждая файловая система (ext3, ntfs, fat, ...) занимает раздел (volume) на диске.

Первый сектор (512 байт) раздела называют загрузочным (BOOT). BOOT-сектор всегда используется файловыми системами Microsoft (FAT, NTFS), другие файловые системы могут его не использовать.

BOOT-сектор состоит из четырех частей. Вначале находится 2/3-байтная команда машинного кода --- безусловный переход на код загрузчика ОС. Затем приводится описание раздела: размер сектора (практически всегда 512 байт), размер кластера, имя и номер раздела, специфичная для выбранной файловой системы информация. Третья часть BOOT-сектора --- это код загрузчика ОС. Четвертая часть --- это маркер конца сектора (55 AA).

В случае, если размер диска невелик (floppy), то понятия раздела и диска совпадают. В случае большого диска, диск обязательно делится на разделы: раздел может быть единственным, но он не будет совпадать с диском. Деление диска на разделы на IBM PC совместимых компьютерах обычно определяется соглашениями фирмы Microsoft, основанными на понятие MBR --- Master Boot Record. MBR --- это начальный сектор диска, обычно --- 512 байт. Сектор MBR как и BOOT начинается с безусловного перехода, затем следует

код загрузчика BOOT-сектора активного раздела (используется только системами Microsoft) или код мультисистемного загрузчика (GRUB, LILO, ...), а затем по смещению 1BE располагается таблица разделов диска, завершается MBR маркером конца сектора (55 AA).

Таблица разделов содержат 4 записи по 16 байт каждая. Обычно используется не более двух записей. Каждая запись соответствует первичному разделу и содержит 6 полей: флаг активности (только для Microsoft), начало и конец раздела

(номера головки, сектора и цилиндра --- выходят из употребления из-за ограничения на количество цилиндров <1024 --- это дисковая система координат CHS --- Cylinder, Head, Sector), код раздела (1 --- FAT12,

5 --- расширенный раздел, 6/16 --- FAT16, 7/17 --- NTFS, b--f --- FAT32, 4d--4f --- QNX, 82 --- Linux swap, 83 --- Linux, a5 --- Free BSD), номер начального сектора, размер в секторах. Два последних поля фактически дублируют поля начала и конца раздела. Расширенный раздел не содержит

никакой файловой системы --- это ссылка на начало однонаправленного списка разделов, которые размещены в нем. Точнее, это ссылка на дополнительную MBR, в которой принято использовать только две первых записи: первую для ссылки на раздел, а вторую для ссылки на следующий MBR. В последнем дополнительном MBR вторая запись --- пустая.

Для сектора с MBR, как правило, резервируется целый цилиндр, оставшаяся часть которого обычно не используется.

Разметка диска на разделы обычно производится до установки ОС, хотя существуют программы (FIPS, Partition Magic, ...) для изменения размера раздела с установленной ОС. Типичная простейшая программа для разбивки диска --- это FDISK, доступная в разных вариантах для DOS, Microsoft

Windows, Linux. Не следует путать разделение диска с форматированием раздела на нем. Форматирование раздела -- это размещение на нём выбранной файловой системы (fat, ext4, ntfs, btrfs, ...).

Итак все информация о разделах хранится в MBR. К сожалению, Microsoft Windows при установке или переустановке переписывает MBR.

Восстановить такой Microsoft MBR можно, например, командой FDISK /MBR с загрузочной дискеты. Если же нужно восстановить произвольный MBR, например, с мультизагрузчиком, то можно воспользоваться командой dd, которая является стандартной для любой Unix-подобной ОС.

Для сохранения MBR используем команду

ddif=/dev/hdX of=ИМЯ_ФАЙЛА_ДЛЯ_ХРАНЕНИЯ_MBR bs=512 count=1

,где X=a для первого диска, X=b для второго и т.д. Всего может быть до 4-х дисков на стандартном IDE контроллере.

Для восстановления MBR используем команду

dd of=/dev/hdX if=ИМЯ_ФАЙЛА_ДЛЯ_ХРАНЕНИЯ_MBR

.

В среде ОС фирмы Microsoft отсутствуют стандартные средства для выполнения подобных операций. Здесь нужна программа для редактирования секторов физического диска, например, Norton Disk Editor. Кроме того, используя стандартную утилиту DEBUG, такое средство может быть легко создано.

Создание утилиты для ОС Microsoft для записи MBR-сектора для IDE дисков.

DEBUG

 

 

-A100

;address of start of the program

MOV AX,301 ;1 sector to write, function #3

MOV BX,110

;address of MBR

MOV CX,1

;track #0, sector #1

MOV DX,80

;head #0, drive=80 (81 for second)

INT 13

;disk service

INT 20

;exit to OS

;RBX

 

 

; 0

 

 

-RCX

 

 

10

;length of file is 10h bytes

-NMBRHDR

;name of file

-W100

;write from 100h offset

-Q

;exit to OS

COPY /B MBRHDR+MBR MBRWRITE.COM

в Linux вместо последней команды можно использовать cat mbrhdr mbr >mbrwrite.com

Можно используя написанный код получить и сам MBR. Для этого нужно заменить номер функции на 2 (чтение), вызвать программу. Потом вернуть 3.

и записать не 16, а 528 байт, задав CX равным 210h. Такой подход сделает ненужным команду COPY или ее альтернативы.

Файл MBR должен содержать MBR, полученный, например, при помощи dd в Linux. Длина полученного в итоге файла MBRWRITE.COM должна быть 528 байт.

Для восстановления мультисистемного загрузчика в MBR можно использовать загрузку Linux с CD/DVD-ROM в режиме восстановления от ошибок и затем программу chroot. Например, для восстановления GRUB нужно вызвать

chroot ТОЧКА_МОНТИРОВАНИЯ_РАЗДЕЛА_LINUX grub-install [<диск>]

. Точка монтирования --- это, например, /media/hda5 или /mnt/hda7.

Диск надо указывать при наличии нескольких дисков, например, hd0 для 1-го диска.

MBR ограничена поддержкой дисков объемом только до 2ТБ, поэтому происходит переход на работу с GPT (GUID Partition Table). MBR для совместимости с существующим ПО является началом GPT. GPT --- это часть проекта

UEFI (Unified Extensible Firmware Interface), имеющего целью постепенное вытеснение ROM-BIOS и переход на его загрузку с GPT.