fsaved = signal (sig, SIG_IGN);
sys_call(...);
signal (sig, fsaved);
или так:
sighold(sig);
sys_call(...);
sigrelse(sig);
Сигналами могут быть прерваны не все системные вызовы и не при всех обстоятельствах.
6.4.3. Напишите функцию sleep(n), задерживающую выполнение программы на n секунд.
Воспользуйтесь системным вызовом alarm(n) (будильник) и вызовом pause(), который
задерживает программу до получения любого сигнала. Предусмотрите рестарт при получе-
нии во время ожидания другого сигнала, нежели SIGALRM. Сохраняйте заказ alarm, сде-
ланный до вызова sleep (alarm выдает число секунд, оставшееся до завершения предыду-
щего заказа). На самом деле есть такая СТАНДАРТНАЯ функция. Ответ:
ненужной строки) и последующего его переименования.
А. Богатырев, 1992-95 - 218 - Си в UNIX
#include
#include
#include
int got; /* пришел ли сигнал */
void onalarm(int sig)
{ printf( "Будильник\n" ); got++; } /* сигнал получен */
void sleep(int n){
time_t time(), start = time(NULL);
void (*save)();
int oldalarm, during = n;
if( n <= 0 ) return;
got = 0;
save = signal(SIGALRM, onalarm);
oldalarm = alarm(3600); /* Узнать старый заказ */
if( oldalarm ){
printf( "Был заказан сигнал, который придет через %d сек.\n",
oldalarm );
if(oldalarm > n) oldalarm -= n;
else { during = n = oldalarm; oldalarm = 1; }
}
printf( "n=%d oldalarm=%d\n", n, oldalarm );
while( n > 0 ){
printf( "alarm(%d)\n", n );
alarm(n); /* заказать SIGALRM через n секунд */
pause();
if(got) break;
/* иначе мы сбиты с pause другим сигналом */
n = during - (time(NULL) - start); /* прошло времени */
}
printf( "alarm(%d) при выходе\n", oldalarm );
alarm(oldalarm); /* alarm(0) - отмена заказа сигнала */
signal(SIGALRM, save); /* восстановить реакцию */
}
void onintr(int nsig){
printf( "Сигнал SIGINT\n"); signal(SIGINT, onintr);
}
void onOldAlarm(int nsig){
printf( "Звонит старый будильник\n");
}
void main(){
int time1 = 0; /* 5, 10, 20 */
setbuf(stdout, NULL);
signal(SIGINT, onintr);
signal(SIGALRM, onOldAlarm); alarm(time1);
sleep(10);
if(time1) pause();
printf("Чао!\n");
}
А. Богатырев, 1992-95 - 219 - Си в UNIX
6.4.4. Напишите "часы", выдающие текущее время каждые 3 секунды.
#include
#include
#include
void tick(nsig){
time_t tim; char *s;
signal (SIGALRM, tick);
alarm(3); time(&tim);
s = ctime(&tim);
s[ strlen(s)-1 ] = '\0'; /* обрубить '\n' */
fprintf(stderr, "\r%s", s);
}
main(){ tick(0);
for(;;) pause();
}
6.5. Жизнь процессов.
6.5.1. Какие классы памяти имеют данные, в каких сегментах программы они располо-
жены?
char x[] = "hello";
int y[25];
char *p;
main(){
int z = 12;
int v;
static int w = 25;
static int q;
char s[20];
char *pp;
...
v = w + z; /* #1 */
}
Ответ:
Переменная Класс памяти Сегмент Начальное значение
x static data/DATA "hello"
y static data/BSS {0, ..., 0}
p static data/BSS NULL
z auto stack 12
v auto stack не определено
w static data/DATA 25
q static data/BSS 0
s auto stack не определено
pp auto stack не определено
main static text/TEXT
Большими буквами обозначены сегменты, хранимые в выполняемом файле:
DATA - это инициализированные статические данные (которым присвоены начальные значе-
ния). Они помещаются компилятором в файл в виде готовых констант, а при запуске
программы (при ее загрузке в память машины), просто копируются в память из
файла.
BSS (Block Started by Symbol)
- неинициализированные статические данные. Они по умолчанию имеют начальное зна-
чение 0 (NULL, "", '\0'). Эта память расписывается нулями при запуске прог-
раммы, а в файле хранится лишь ее размер.
А. Богатырев, 1992-95 - 220 - Си в UNIX
TEXT - сегмент, содержащий машинные команды (код).
Хранящаяся в файле выполняемая программа имеет также заголовок - в нем в частности
содержатся размеры перечисленных сегментов и их местоположение в файле; и еще - в
самом конце файла - таблицу имен. В ней содержатся имена всех функций и переменных,
используемых в программе, и их адреса. Эта таблица используется отладчиками adb и
sdb, а также при сборке программы из нескольких объектных файлов программой ld.
Просмотреть ее можно командой
nm имяФайла
Для экономии дискового пространства эту таблицу часто удаляют, что делается командой
strip имяФайла
Размеры сегментов можно узнать командой
size имяФайла
Программа, загруженная в память компьютера (т.е. процесс), состоит из 3x сегментов,
относящихся непосредственно к программе:
stack
- стек для локальных переменных функций (автоматических переменных). Этот сег-
мент существует только у выполняющейся программы, поскольку отведение памяти в
стеке производится выполнением некоторых машинных команд (поэтому описание авто-
матических переменных в Си - это на самом деле выполняемые операторы, хотя и не
с точки зрения языка). Сегмент стека автоматически растет по мере надобности
(если мы вызываем новые и новые функции, отводящие переменные в стеке). За этим
следит аппаратура диспетчера памяти.
data - сегмент, в который склеены сегменты статических данных DATA и BSS, загруженные
из файла. Этот сегмент также может изменять свой размер, но делать это надо
явно - системными вызовами sbrk или brk. В частности, функция malloc() для раз-
мещения динамически отводимых данных увеличивает размер этого сегмента.
text - это выполняемые команды, копия сегмента TEXT из файла. Так строка с меткой #1
содержится в виде машинных команд именно в этом сегменте.
Кроме того, каждый процесс имеет еще:
proc - это резидентная часть паспорта процесса в таблице процессов в ядре операцион-
ной системы;
user - это 4-ый сегмент процесса - нерезидентная часть паспорта (u-area). К этому
сегменту имеет доступ только ядро, но не сама программа.
Паспорт процесса был поделен на 2 части только из соображений экономии памяти в ядре:
контекст процесса (таблица открытых файлов, ссылка на I-узел текущего каталога, таб-
лица реакций на сигналы, ссылка на I-узел управляющего терминала, и.т.п.) нужен ядру
только при обслуживании текущего активного процесса. Когда активен другой процесс -
эта информация в памяти ядра не нужна. Более того, если процесс из-за нехватки места
в памяти машины был откачан на диск, эта информация также может быть откачана на диск
и подкачана назад лишь вместе с процессом. Поэтому контекст был выделен в отдельный
сегмент, и сегмент этот подключается к адресному пространству ядра лишь при выполне-
нии процессом какого-либо системного вызова (это подключение называется "переключение
контекста" - context switch). Четыре сегмента процесса могут располагаться в памяти
машины не обязательно подряд - между ними могут лежать сегменты других процессов.
Схема составных частей процесса:
П Р О Ц Е С С
таблица процессов:
паспорт в ядре сегменты в памяти
struct proc[]
####---------------> stack 1
#### data 2
text 3
контекст: struct user 4
А. Богатырев, 1992-95 - 221 - Си в UNIX
Каждый процесс имеет уникальный номер, хранящийся в поле p_pid в структуре proc[*]. В
ней также хранятся: адреса сегментов процесса в памяти машины (или на диске, если
процесс откачан); p_uid - номер владельца процесса; p_ppid - номер процесса-родителя;
p_pri, p_nice - приоритеты процесса; p_pgrp - группа процесса; p_wchan - ожидаемое
процессом событие; p_flag и p_stat - состояние процесса; и многое другое. Структура
proc определена в include-файле , а структура user - в .
6.5.2. Системный вызов fork() (вилка) создает новый процесс: копию процесса, издав-
шего вызов. Отличие этих процессов состоит только в возвращаемом fork-ом значении:
0 - в новом процессе.
pid нового процесса - в исходном.
Вызов fork может завершиться неудачей если таблица процессов переполнена. Простейший
способ сделать это:
main(){
while(1)
if( ! fork()) pause();
}
Одно гнездо таблицы процессов зарезервировано - его может использовать только супер-
пользователь (в целях жизнеспособности системы: хотя бы для того, чтобы запустить
программу, убивающую все эти процессы-варвары).
Вызов fork создает копию всех 4х сегментов процесса и выделяет порожденному про-
цессу новый паспорт и номер. Иногда сегмент text не копируется, а используется про-
цессами совместно ("разделяемый сегмент") в целях экономии памяти. При копировании
сегмента user контекст порождающего процесса наследуется порожденным процессом (см.
ниже).
Проведите опыт, доказывающий что порожденный системным вызовом fork() процесс и
породивший его - равноправны. Повторите несколько раз программу:
#include
int pid, i, fd; char c;
main(){
fd = creat( "TEST", 0644);
if( !(pid = fork())){ /* сын: порожденный процесс */
c = 'a';
for(i=0; i < 5; i++){
write(fd, &c, 1); c++; sleep(1);
}
printf("Сын %d окончен\n", getpid());
exit(0);
}
/* else процесс-отец */
c = 'A';
for(i=0; i < 5; i++){
write(fd, &c, 1); c++; sleep(1);
}
printf("Родитель %d процесса %d окончен\n",
getpid(), pid );
}
В файле TEST мы будем от случая к случаю получать строки вида
aABbCcDdEe или AaBbcdCDEe
что говорит о том, что первым "проснуться" после fork() может любой из двух процес-
сов. Если же опыт дает устойчиво строки, начинающиеся с одной и той же буквы - значит
____________________
[*] Процесс может узнать его вызовом pid=getpid();
А. Богатырев, 1992-95 - 222 - Си в UNIX
в данной реализации системы один из процессов все же запускается раньше. Но не стоит
использовать этот эффект - при переносе на другую систему его может не быть!
Данный опыт основан на следующем свойстве системы UNIX: при системном вызове
fork() порожденный процесс получает все открытые порождающим процессом файлы "в нас-
ледство" - это соответствует тому, что таблица открытых процессом файлов копируется в
процесс-потомок. Именно так, в частности, передаются от отца к сыну стандартные
каналы 0, 1, 2: порожденному процессу не нужно открывать стандартные ввод, вывод и
вывод ошибок явно. Изначально же они открываются специальной программой при вашем