strncpy(op->body, s, n);
return op;
}
А. Богатырев, 1992-95 - 184 - Си в UNIX
void printobj( struct obj *p )
{
register i;
printf( "OBJECT(cls=%d,size=%d)\n", p->hdr.cls, p->hdr.size);
for(i=0; i < p->hdr.size; i++ )
putchar( p->body[i] );
putchar( '\n' );
}
char *strs[] = { "a tree", "a maple", "an oak", "the birch", "the fir" };
int main(int ac, char *av[]){
int i;
printf("sizeof(struct header)=%d sizeof(struct obj)=%d\n",
sizeof(struct header), sizeof(struct obj));
{
struct obj *sample;
printf("offset(cls)=%d\n", OFFSET(hdr.cls, sample));
printf("offset(size)=%d\n", OFFSET(hdr.size, sample));
printf("offset(body)=%d\n", body_offset = OFFSET(body, sample));
}
for( i=0; i < SZ; i++ )
items[i] = newObj( i, strs[i] );
for( i=0; i < SZ; i++ ){
printobj( items[i] ); free( items[i] ); items[i] = NULL;
}
return 0;
}
5.17. Напишите программу, реализующую список со "старением". Элемент списка, к
которому обращались последним, находится в голове списка. Самый старый элемент
вытесняется к хвосту списка и в конечном счете из списка удаляется. Такой алгоритм
использует ядро UNIX для кэширования блоков файла в оперативной памяти: блоки, к
которым часто бывают обращения оседают в памяти (а не на диске).
/* Список строк, упорядоченных по времени их добавления в список,
* т.е. самая "свежая" строка - в начале, самая "древняя" - в конце.
* Строки при поступлении могут и повторяться! По подобному принципу
* можно организовать буферизацию блоков при обмене с диском.
*/
#include
extern char *malloc(), *gets();
#define MAX 3 /* максимальная длина списка */
int nelems = 0; /* текущая длина списка */
struct elem { /* СТРУКТУРА ЭЛЕМЕНТА СПИСКА */
char *key; /* Для блоков - это целое - номер блока */
struct elem *next; /* следующий элемент списка */
/* ... и может что-то еще ... */
} *head; /* голова списка */
void printList(), addList(char *), forget();
А. Богатырев, 1992-95 - 185 - Си в UNIX
void main(){ /* Введите a b c d b a c */
char buf[128];
while(gets(buf)) addList(buf), printList();
}
/* Распечатка списка */
void printList(){ register struct elem *ptr;
printf( "В списке %d элементов\n", nelems );
for(ptr = head; ptr != NULL; ptr = ptr->next )
printf( "\t\"%s\"\n", ptr->key );
}
/* Добавление в начало списка */
void addList(char *s)
{ register struct elem *p, *new;
/* Анализ - нет ли уже в списке */
for(p = head; p != NULL; p = p->next )
if( !strcmp(s, p->key)){ /* Есть. Перенести в начало списка */
if( head == p ) return; /* Уже в начале */
/* Удаляем из середины списка */
new = p; /* Удаляемый элемент */
for(p = head; p->next != new; p = p->next );
/* p указывает на предшественника new */
p->next = new->next; goto Insert;
}
/* Нет в списке */
if( nelems >= MAX ) forget(); /* Забыть старейший */
if((new = (struct elem *) malloc(sizeof(struct elem)))==NULL) goto bad;
if((new->key = malloc(strlen(s) + 1)) == NULL) goto bad;
strcpy(new->key, s); nelems++;
Insert: new->next = head; head = new; return;
bad: printf( "Нет памяти\n" ); exit(13);
}
/* Забыть хвост списка */
void forget(){ struct elem *prev = head, *tail;
if( head == NULL ) return; /* Список пуст */
/* Единственный элемент ? */
if((tail = head->next) == NULL){ tail=head; head=NULL; goto Del; }
for( ; tail->next != NULL; prev = tail, tail = tail->next );
prev->next = NULL;
Del: free(tail->key); free(tail); nelems--;
}
А. Богатырев, 1992-95 - 186 - Си в UNIX
6. Системные вызовы и взаимодействие с UNIX.
В этой главе речь пойдет о процессах. Скомпилированная программа хранится на
диске как обычный нетекстовый файл. Когда она будет загружена в память компьютера и
начнет выполняться - она станет процессом.
UNIX - многозадачная система (мультипрограммная). Это означает, что одновре-
менно может быть запущено много процессов. Процессор выполняет их в режиме разделения
времени - выделяя по очереди квант времени одному процессу, затем другому,
третьему... В результате создается впечатление параллельного выполнения всех процес-
сов (на многопроцессорных машинах параллельность истинная). Процессам, ожидающим
некоторого события, время процессора не выделяется. Более того, "спящий" процесс
может быть временно откачан (т.е. скопирован из памяти машины) на диск, чтобы освобо-
дить память для других процессов. Когда "спящий" процесс дождется события, он будет
"разбужен" системой, переведен в ранг "готовых к выполнению" и, если был откачан -
будет возвращен с диска в память (но, может быть, на другое место в памяти!). Эта
процедура носит название "своппинг" (swapping).
Можно запустить несколько процессов, выполняющих программу из одного и того же
файла; при этом все они будут (если только специально не было предусмотрено иначе)
независимыми друг от друга. Так, у каждого пользователя, работающего в системе, име-
ется свой собственный процесс-интерпретатор команд (своя копия), выполняющий прог-
рамму из файла /bin/csh (или /bin/sh).
Процесс представляет собой изолированный "мир", общающийся с другими "мирами" во
Вселенной при помощи:
a) Аргументов функции main:
void main(int argc, char *argv[], char *envp[]);
Если мы наберем команду
$ a.out a1 a2 a3
то функция main программы из файла a.out вызовется с
argc = 4 /* количество аргументов */
argv[0] = "a.out" argv[1] = "a1"
argv[2] = "a2" argv[3] = "a3"
argv[4] = NULL
По соглашению argv[0] содержит имя выполняемого файла из которого загружена эта
программа[*].
b) Так называемого "окружения" (или "среды") char *envp[], продублированного также
в предопределенной переменной
extern char **environ;
Окружение состоит из строк вида
"ИМЯПЕРЕМЕННОЙ=значение"
Массив этих строк завершается NULL (как и argv). Для получения значения пере-
менной с именем ИМЯ существует стандартная функция
char *getenv( char *ИМЯ );
Она выдает либо значение, либо NULL если переменной с таким именем нет.
c) Открытых файлов. По умолчанию (неявно) всегда открыты 3 канала:
ВВОД В Ы В О Д
FILE * stdin stdout stderr
соответствует fd 0 1 2
связан с клавиатурой дисплеем
____________________
[*] Именно это имя показывает команда ps -ef
#include
main(ac, av) char **av; {
execl("/bin/sleep", "Take it easy", "1000", NULL);
}
А. Богатырев, 1992-95 - 187 - Си в UNIX
Эти каналы достаются процессу "в наследство" от запускающего процесса и связаны
с дисплеем и клавиатурой, если только не были перенаправлены. Кроме того, прог-
рамма может сама явно открывать файлы (при помощи open, creat, pipe, fopen).
Всего программа может одновременно открыть до 20 файлов (считая стандартные
каналы), а в некоторых системах и больше (например, 64). В MS DOS есть еще 2
предопределенных канала вывода: stdaux - в последовательный коммуникационный
порт, stdprn - на принтер.
d) Процесс имеет уникальный номер, который он может узнать вызовом
int pid = getpid();
а также узнать номер "родителя" вызовом
int ppid = getppid();
Процессы могут по этому номеру посылать друг другу сигналы:
kill(pid /* кому */, sig /* номер сигнала */);
и реагировать на них
signal (sig /*по сигналу*/, f /*вызывать f(sig)*/);
e) Существуют и другие средства коммуникации процессов: семафоры, сообщения, общая
память, сетевые коммуникации.
f) Существуют некоторые другие параметры (контекст) процесса: например, его текущий
каталог, который достается в наследство от процесса-"родителя", и может быть
затем изменен системным вызовом
chdir(char *имя_нового_каталога);
У каждого процесса есть свой собственный текущий рабочий каталог (в отличие от
MS DOS, где текущий каталог одинаков для всех задач). К "прочим" характеристи-
кам отнесем также: управляющий терминал; группу процессов (pgrp); идентификатор
(номер) владельца процесса (uid), идентификатор группы владельца (gid), реакции
и маски, заданные на различные сигналы; и.т.п.
g) Издания других запросов (системных вызовов) к операционной системе ("богу") для
выполнения различных "внешних" операций.
h) Все остальные действия происходят внутри процесса и никак не влияют на другие
процессы и устройства ("миры"). В частности, один процесс НИКАК не может полу-
чить доступ к памяти другого процесса, если тот не позволил ему это явно (меха-
низм shared memory); адресные пространства процессов независимы и изолированы
(равно и пространство ядра изолировано от памяти процессов).
Операционная система выступает в качестве коммуникационной среды, связывающей
"миры"-процессы, "миры"-внешние устройства (включая терминал пользователя); а также в
качестве распорядителя ресурсов "Вселенной", в частности - времени (по очереди выде-
ляемого активным процессам) и пространства (в памяти компьютера и на дисках).
Мы уже неоднократно упоминали "системные вызовы". Что же это такое? С точки
зрения Си-программиста - это обычные функции. В них передают аргументы, они возвра-
щают значения. Внешне они ничем не отличаются от написанных нами или библиотечных
функций и вызываются из программ одинаковым с ними способом.
С точки же зрения реализации - есть глубокое различие. Тело функции-сисвызова
расположено не в нашей программе, а в резидентной (т.е. постоянно находящейся в
памяти компьютера) управляющей программе, называемой ядром операционной системы[*].
____________________
[*] Собственно, операционная система характеризуется набором предоставляемых ею сис-
темных вызовов, поскольку все концепции, заложенные в системе, доступны нам только
через них. Если мы имеем две реализации системы с разным внутренним устройством
ядер, но предоставляющие одинаковый интерфейс системных вызовов (их набор, смысл и
поведение), то это все-таки одна и та же система! Ядра могут не просто отличаться,
но и быть построенными на совершенно различных принципах: так обстоит дело с UNIX-ами
на однопроцессорных и многопроцессорных машинах. Но для нас ядро - это "черный
ящик", полностью определяемый его поведением, т.е. своим интерфейсом с программами,
но не внутренним устройством. Вторым параметром, характеризующим ОС, являются фор-
маты данных, используемые системой: форматы данных для сисвызовов и формат информации
в различных файлах, в том числе формат оформления выполняемых файлов (формат данных в
физической памяти машины в этот список не входит - он зависим от реализации и от про-
цессора). Как правило, программа пишется так, чтобы использовать соглашения, приня-