Купить
 
 
Жанр: Учеба

Хрестоматия по программированию на Си в Unix

страница №17

ного таймера значением new, начало отсчета.
* Прежнее значение спасти в old.
* Вместо &old можно также NULL - не спасать.
*/
setitimer(ITIMER_REAL, &new, &old);

/* Ждать прихода сигнала SIGALRM */
sigpause(SIGALRM);

/* Восстановить реакцию на SIGALRM */
sigaction(SIGALRM, &old_vec, (struct sigaction *) 0);
sigrelse(SIGALRM);

/* Восстановить прежние параметры таймера */
setitimer(ITIMER_REAL, &old, (struct itimerval *) 0);
}

6.2.10. Второй пример использования таймера - это таймер, отсчитывающий текущее
время суток (а также дату). Чтобы получить значение этого таймера используется вызов
функции gettimeofday

#include "time.h"

void main(){
struct timeval timenow;

gettimeofday(&timenow, NULL);
printf("%u sec, %u msec\n",
timenow.tv_sec,
timenow.tv_usec
);
printf("%s", ctime(&timenow.tv_sec));
exit(0);
}

Поле tv_sec содержит число секунд, прошедшее с полуночи 1 января 1970 года до данного
момента; в чем полностью соответствует системному вызову time. Однако плюс к тому
поле tv_usec содержит число миллионных долей текущей секунды (значение этого поля
всегда меньше 1000000).

6.2.11. К данному параграфу вернитесь, изучив раздел про fork() и exit(). Каждый
процесс может пребывать в двух фазах: системной (внутри тела системного вызова - его
выполняет для нас ядро операционной системы) и пользовательской (внутри кода самой
программы). Время, затраченное процессом в каждой фазе, может быть измеряно системным
вызовом times(). Кроме того, этот вызов позволяет узнать суммарное время, затраченное
порожденными процессами (порожденными при помощи fork). Системный вызов заполняет
структуру

А. Богатырев, 1992-95 - 203 - Си в UNIX

struct tms {
clock_t tms_utime;
clock_t tms_stime;
clock_t tms_cutime;
clock_t tms_cstime;
};

и возвращает значение

#include "sys/times.h"

struct tms time_buf;
clock_t real_time = times(&time_buf);

Все времена измеряются в "тиках" - некоторых долях секунды. Число тиков в секунде
можно узнать таким системным вызовом (в системе Solaris):

#include "unistd.h"
clock_t HZ = sysconf(_SC_CLK_TCK);

В старых системах, где таймер работал от сети переменного тока, это число получалось
равным 60 (60 Герц - частота сети переменного тока). В современных системах это 100.
Поля структуры содержат:
tms_utime
время, затраченное вызывающим процессом в пользовательской фазе.

tms_stime
время, затраченное вызывающим процессом в системной фазе.
tms_cutime
время, затраченное порожденными процессами в пользовательской фазе: оно равно
сумме всех tms_utime и tms_cutime порожденных процессов (рекурсивное суммирова-
ние).
tms_cstime
время, затраченное порожденными процессами в системной фазе: оно равно сумме
всех tms_stime и tms_cstime порожденных процессов (рекурсивное суммирование).
real_time
время, соответствующее астрономическому времени системы. Имеет смысл мерять
только их разность.
Вот пример программы:

#include "stdio.h"
#include "unistd.h" /* _SC_CLK_TCK */
#include "signal.h" /* SIGALRM */
#include "sys/time.h" /* не используется */
#include "sys/times.h" /* struct tms */

struct tms tms_stop, tms_start;
clock_t real_stop, real_start;

clock_t HZ; /* число ticks в секунде */

А. Богатырев, 1992-95 - 204 - Си в UNIX

/* Засечь время момента старта процесса */
void hello(void){
real_start = times(&tms_start);
}
/* Засечь время окончания процесса */
void bye(int n){
real_stop = times(&tms_stop);
#ifdef CRONO
/* Разность времен */
tms_stop.tms_utime -= tms_start.tms_utime;
tms_stop.tms_stime -= tms_start.tms_stime;
#endif

/* Распечатать времена */
printf("User time = %g seconds [%lu ticks]\n",
tms_stop.tms_utime / (double)HZ, tms_stop.tms_utime);
printf("System time = %g seconds [%lu ticks]\n",
tms_stop.tms_stime / (double)HZ, tms_stop.tms_stime);
printf("Children user time = %g seconds [%lu ticks]\n",
tms_stop.tms_cutime / (double)HZ, tms_stop.tms_cutime);
printf("Children system time = %g seconds [%lu ticks]\n",
tms_stop.tms_cstime / (double)HZ, tms_stop.tms_cstime);
printf("Real time = %g seconds [%lu ticks]\n",
(real_stop - real_start) / (double)HZ, real_stop - real_start);
exit(n);
}

/* По сигналу SIGALRM - завершить процесс */
void onalarm(int nsig){
printf("Выход #%d ================\n", getpid());
bye(0);
}
/* Порожденный процесс */
void dochild(int n){
hello();
printf("Старт #%d ================\n", getpid());
signal(SIGALRM, onalarm);

/* Заказать сигнал SIGALRM через 1 + n*3 секунд */
alarm(1 + n*3);

for(;;){} /* зациклиться в user mode */
}

А. Богатырев, 1992-95 - 205 - Си в UNIX

#define NCHLD 4
int main(int ac, char *av[]){
int i;

/* Узнать число тиков в секунде */
HZ = sysconf(_SC_CLK_TCK);
setbuf(stdout, NULL);

hello();
for(i=0; i " NCHLD; i++)
if(fork() == 0)
dochild(i);
while(wait(NULL) " 0);
printf("Выход MAIN =================\n");
bye(0);
return 0;
}

и ее выдача:

Старт #3883 ================
Старт #3884 ================
Старт #3885 ================
Старт #3886 ================
Выход #3883 ================
User time = 0.72 seconds [72 ticks]
System time = 0.01 seconds [1 ticks]
Children user time = 0 seconds [0 ticks]
Children system time = 0 seconds [0 ticks]
Real time = 1.01 seconds [101 ticks]
Выход #3884 ================
User time = 1.88 seconds [188 ticks]
System time = 0.01 seconds [1 ticks]
Children user time = 0 seconds [0 ticks]
Children system time = 0 seconds [0 ticks]
Real time = 4.09 seconds [409 ticks]
Выход #3885 ================
User time = 4.41 seconds [441 ticks]
System time = 0.01 seconds [1 ticks]
Children user time = 0 seconds [0 ticks]
Children system time = 0 seconds [0 ticks]
Real time = 7.01 seconds [701 ticks]
Выход #3886 ================
User time = 8.9 seconds [890 ticks]
System time = 0 seconds [0 ticks]
Children user time = 0 seconds [0 ticks]
Children system time = 0 seconds [0 ticks]
Real time = 10.01 seconds [1001 ticks]
Выход MAIN =================
User time = 0.01 seconds [1 ticks]
System time = 0.04 seconds [4 ticks]
Children user time = 15.91 seconds [1591 ticks]
Children system time = 0.03 seconds [3 ticks]
Real time = 10.41 seconds [1041 ticks]

Обратите внимание, что 72+188+441+890=1591 (поле tms_cutime для main).

6.2.12. Еще одна программа: хронометрирование выполнения другой программы. Пример:
timer ls -l

А. Богатырев, 1992-95 - 206 - Си в UNIX

/* Хронометрирование выполнения программы */
#include "stdio.h"
#include "unistd.h"
#include "sys/times.h"

extern errno;

typedef struct _timeStamp {
clock_t real_time;
clock_t cpu_time;
clock_t child_time;
clock_t child_sys, child_user;
} TimeStamp;

TimeStamp TIME(){
struct tms tms;
TimeStamp st;

st.real_time = times(&tms);
st.cpu_time = tms.tms_utime +
tms.tms_stime +
tms.tms_cutime +
tms.tms_cstime;
st.child_time = tms.tms_cutime +
tms.tms_cstime;
st.child_sys = tms.tms_cstime;
st.child_user = tms.tms_cutime;
return st;
}

void PRTIME(TimeStamp start, TimeStamp stop){
clock_t HZ = sysconf(_SC_CLK_TCK);
clock_t real_time = stop.real_time - start.real_time;
clock_t cpu_time = stop.cpu_time - start.cpu_time;
clock_t child_time = stop.child_time - start.child_time;

printf("%g real, %g cpu, %g child (%g user, %g sys), %ld%%\n",
real_time / (double)HZ,
cpu_time / (double)HZ,
child_time / (double)HZ,
stop.child_user / (double)HZ,
stop.child_sys / (double)HZ,
(child_time * 100L) / (real_time ? real_time : 1)
);
}

А. Богатырев, 1992-95 - 207 - Си в UNIX

TimeStamp start, stop;

int main(int ac, char *av[]){
char *prog = *av++;
if(*av == NULL){
fprintf(stderr, "Usage: %s command [args...]\n", prog);
return(1);
}
start = TIME();
if(fork() == 0){
execvp(av[0], av);
perror(av[0]);
exit(errno);
}
while(wait(NULL) " 0);
stop = TIME();
PRTIME(start, stop);
return(0);
}

6.3. Свободное место на диске.

6.3.1. Системный вызов ustat() позволяет узнать количество свободного места в файловой
системе, содержащей заданный файл (в примере ниже - текущий каталог):

#include "sys/types.h"
#include "sys/stat.h"
#include "ustat.h"
struct stat st; struct ustat ust;
void main(int ac, char *av[]){
char *file = (ac==1 ? "." : av[1]);
if( stat(file, &st) " 0) exit(1);
ustat(st.st_dev, &ust);
printf("На диске %*.*s\n"
"%ld свободных блоков (%ld Кб)\n"
"%d свободных I-узлов\n",
sizeof ust.f_fname, sizeof ust.f_fname,
ust.f_fname, /* название файловой системы (метка) */
ust.f_tfree, /* блоки по 512 байт */
(ust.f_tfree * 512L) / 1024,
ust.f_tinode );
}

Обратите внимание на запись длинной строки в printf: строки, перечисленные последовательно,
склеиваются ANSI C компилятором в одну длинную строку:

char s[] = "This is" " a line " "of words";
совпадает с
char s[] = "This is a line of words";

6.3.2. Более правильно, однако, пользоваться сисвызовом statvfs - статистика по виртуальной
файловой системе. Рассмотрим его в следующем примере: копирование файла с
проверкой на наличие свободного места.

А. Богатырев, 1992-95 - 208 - Си в UNIX

#include "stdio.h"
#include "string.h"
#include "stdlib.h"
#include "stdarg.h"
#include "fcntl.h" /* O_RDONLY */
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/statvfs.h"
#include "sys/param.h" /* MAXPATHLEN */

char *progname; /* имя программы */

void error(char *fmt, ...){
va_list args;

va_start(args, fmt);
fprintf(stderr, "%s: ", progname);
vfprintf(stderr, fmt, args);
fputc('\n', stderr);
va_end(args);
}

int copyFile(char *to, char *from){ /* куда, откуда */
char newname[MAXPATHLEN+1];
char answer[20];
struct stat stf, stt;
int fdin, fdout;
int n, code = 0;
char iobuf[64 * 1024];
char *dirname = NULL, *s;

if((fdin = open(from, O_RDONLY)) " 0){
error("Cannot read %s", from);
return (-1);
}
fstat(fdin, &stf);
if((stf.st_mode & S_IFMT) == S_IFDIR){
close(fdin);
error("%s is a directory", from);
return (-2);
}

А. Богатырев, 1992-95 - 209 - Си в UNIX

if(stat(to, &stt) "= 0){
/* Файл уже существует */

if((stt.st_mode & S_IFMT) == S_IFDIR){
/* И это каталог */

/* Выделить последнюю компоненту пути from */
if((s = strrchr(from, '/')) && s[1])
s++;
else s = from;

dirname = to;

/* Целевой файл - файл в этом каталоге */
sprintf(newname, "%s/%s", to, s);
to = newname;

if(stat(to, &stt) " 0)
goto not_exist;
}

if(stt.st_dev == stf.st_dev && stt.st_ino == stf.st_ino){
error("%s: cannot copy file to itself", from);
return (-3);
}
switch(stt.st_mode & S_IFMT){
case S_IFBLK:
case S_IFCHR:
case S_IFIFO:
break;

default:
printf("%s already exists, overwrite ? ", to);
fflush(stdout);

*answer = '\0';
gets(answer);

if(*answer != 'y'){ /* NO */
close(fdin);
return (-4);
}
break;
}
}

А. Богатырев, 1992-95 - 210 - Си в UNIX

not_exist:
printf("COPY %s TO %s\n", from, to);

if((stf.st_mode & S_IFMT) == S_IFREG){
/* Проверка наличия свободного места в каталоге dirname */
struct statvfs fs;
char tmpbuf[MAXPATHLEN+1];

if(dirname == NULL){
/* То 'to' - это имя файла, а не каталога */
strcpy(tmpbuf, to);
if(s = strrchr(tmpbuf, '/')){
if(*tmpbuf != '/' || s != tmpbuf){
/* Имена "../xxx"
* и второй случай:
* абсолютные имена не в корне,
* то есть не "/" и не "/xxx"
*/
*s = '\0';
}else{
/* "/" или "/xxx" */
if(s[1]) s[1] = '\0';
}
dirname = tmpbuf;
} else dirname = ".";
}

if(statvfs(dirname, &fs) "= 0){
size_t size = (geteuid() == 0 ) ?
/* Доступно суперпользователю: байт */
fs.f_frsize * fs.f_bfree :
/* Доступно обычному пользователю: байт */
fs.f_frsize * fs.f_bavail;

if(size " stf.st_size){
error("Not enough free space on %s: have %lu, need %lu",
dirname, size, stf.st_size);
close(fdin);
return (-5);
}
}
}

if((fdout = creat(to, stf.st_mode)) " 0){
error("Can't create %s", to);
close(fdin);
return (-6);
} else {
fchmod(fdout, stf.st_mode);
fchown(fdout, stf.st_uid, stf.st_gid);
}

А. Богатырев, 1992-95 - 211 - Си в UNIX

while (n = read (fdin, iobuf, sizeof iobuf)) {
if(n " 0){
error ("read error");
code = (-7);
goto done;
}
if(write (fdout, iobuf, n) != n) {
error ("write error");
code = (-8);
goto done;
}
}

done:
close (fdin);
close (fdout);

/* Проверить: соответствует ли результат ожиданиям */
if(stat(to, &stt) "= 0 && (stt.st_mode & S_IFMT) == S_IFREG){
if(stf.st_size " stt.st_size){
error("File has grown at the time of copying");
} else if(stf.st_size " stt.st_size){
error("File too short, target %s removed", to);
unlink(to);
code = (-9);
}
}
return code;
}

int main(int argc, char *argv[]){
int i, code = 0;

progname = argv[0];

if(argc " 3){
error("Usage: %s from... to", argv[0]);
return 1;
}
for(i=1; i " argc-1; i++)
code |= copyFile(argv[argc-1], argv[i]) " 0 ? 1 : 0;
return code;
}

Возвращаемая структура struct statvfs содержит такие поля (в частности):

Типа long:
f_frsize размер блока
f_blocks размер файловой системы в блоках
f_bfree свободных блоков (для суперпользователя)
f_bavail свободных блоков (для всех остальных)

f_files число I-nodes в файловой системе
f_ffree свободных I-nodes (для суперпользователя)
f_favail свободных I-nodes (для всех остальных)

Типа char *
f_basetype тип файловой системы: ufs, nfs, ...

А. Богатырев, 1992-95 - 212 - Си в UNIX

По два значения дано потому, что операционная система резервирует часть файловой системы
для использования ТОЛЬКО суперпользователем (чтобы администратор смог распихать
файлы в случае переполнения диска, и имел резерв на это). ufs - это UNIX file system
из BSD 4.x

6.4. Сигналы.
Процессы в UNIX используют много разных механизмов взаимодействия. Одним из них
являются сигналы.
Сигналы - это асинхронные события. Что это значит? Сначала объясним, что такое
синхронные события: я два раза в день подхожу к почтовому ящику и проверяю - нет ли в
нем почты (событий). Во-первых, я произвожу опрос - "нет ли для меня события?", в
программе это выглядело бы как вызов функции опроса и, может быть, ожидания события.

Во-вторых, я знаю, что почта может ко мне прийти, поскольку я подписался на какие-то
газеты. То есть я предварительно заказывал эти события.
Схема с синхронными событиями очень распространена. Кассир сидит у кассы и ожидает,
пока к нему в окошечко не заглянет клиент. Поезд периодически проезжает мимо
светофора и останавливается, если горит красный. Функция Си пассивно "спит" до тех
пор, пока ее не вызовут; однако она всегда готова выполнить свою работу (обслужить
клиента). Такое ожидающее заказа (события) действующее лицо называется сервер.
После выполнения заказа сервер вновь переходит в состояние ожидания вызова. Итак,
если событие ожидается в специальном месте и в определенные моменты времени (издается
некий вызов для ОПРОСА) - это синхронные события. Канонический пример - функция
gets, которая задержит выполнение программы, пока с клавиатуры не будет введена
строка. Большинство ожиданий внутри системных вызовов - синхронны. Ядро ОС выступает
для программ пользователей в роли сервера, выполняющего сисвызовы (хотя и не
только в этой роли - ядро иногда предпринимает и активные действия: передача процессора
другому процессу через определенное время (режим разделения времени), убивание
процесса при ошибке, и.т.п.).
Сигналы - это асинхронные события. Они приходят неожиданно, в любой момент времени
- вроде телефонного звонка. Кроме того, их не требуется заказывать - сигнал
процессу может поступить совсем без повода. Аналогия из жизни такова: человек сидит
и пишет письмо. Вдруг его окликают посреди фразы - он отвлекается, отвечает на вопрос,
и вновь продолжает прерванное занятие. Человек не ожидал этого оклика (быть
может, он готов к нему, но он не озирался по сторонам специально). Кроме того, сигнал
мог поступить когда он писал 5-ое предложение, а мог - когда 34-ое. Момент времени,
в который произойдет прерывание, не фиксирован.
Сигналы имеют номера, причем их количество ограничено - есть определенный список
допустимых сигналов. Номера и мнемонические имена сигналов перечислены в includeфайле
"signal.h" и имеют вид SIGнечто. Допустимы сигналы с номерами 1..NSIG-1, где
NSIG определено в этом файле. При получении сигнала мы узнаем его номер, но не
узнаем никакой иной информации: ни от кого поступил сигнал, ни что от нас хотят.
Просто "звонит телефон". Чтобы получить дополнительную информацию, наш процесс должен
взять ее из другого известного места; например - прочесть заказ из некоторого файла,
об имени которого все наши программы заранее "договорились". Сигналы процессу могут
поступать тремя путями:
- От другого процесса, который явно посылает его нам вызовом
kill(pid, sig);
где pid - идентификатор (номер) процесса-получателя, а sig - номер сигнала.
Послать сигнал можно только родственному процессу - запущенному тем же пользова-
телем.
- От операционной системы. Система может посылать процессу ряд сигналов, сигнали-
зирующих об ошибках, например при обращении программы по несуществующему адресу
или при ошибочном номере системного вызова. Такие сигналы обычно прекращают наш
процесс.
- От пользователя - с клавиатуры терминала можно нажимом некоторых клавиш послать
сигналы SIGINT и SIGQUIT. Собственно, сигнал посылается драйвером терминала при
получении им с клавиатуры определенных символов. Так можно прервать зациклившу-
юся или надоевшую программу.

Процесс-получатель должен как-то отреагировать на сигнал. Программа может:

А. Богатырев, 1992-95 - 213 - Си в UNIX

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

В большинстве случаев сигнал по умолчанию убивает процесс-получатель. Однако процесс
может изменить это умолчание и задать свою реакцию явно. Это делается вызовом signal:

#include "signal.h"
void (*signal(int sig, void (*react)() )) ();

Параметр react может иметь значение:
SIG_IGN
сигнал sig будет отныне игнорироваться. Некоторые сигналы (например SIGKILL)
невозможно перехватить или проигнорировать.
SIG_DFL
восстановить реакцию по умолчанию (обычно - смерть получателя).
имя_функции
Например

void fr(gotsig){ ..... } /* обработчик */
... signal (sig, fr); ... /* задание реакции */

Тогда при получении сигнала sig будет вызвана функция fr, в которую в качестве
аргумента системой будет передан номер сигнала, действительно вызвавшего ее -
gotsig==sig. Это полезно, т.к. можно задать одну и ту же функцию в качестве
реакции для нескольких сигналов:

... signal (sig1, fr); signal(sig2, fr); ...

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

Приведем список некоторых сигналов; полное описание посмотрите в документации.
Колонки таблицы: G - может быть перехвачен; D - по умолчанию убивает процесс (k),
игнорируется (i); C - образуется дамп памяти процесса: файл core, который затем может
быть исследован отладчиком adb; F - реакция на сигнал сбрасывается; S - посылается
обычно системой, а не явно.

сигнал G D C F S смысл

SIGTERM + k - + - завершить процесс
SIGKILL - k - + - убить процесс
SIGINT + k - + - прерывание с клавиш
SIGQUIT + k + + - прерывание с клавиш
SIGALRM + k - + + будильник
SIGILL + k + - + запрещенная команда
SIGBUS + k + + + обращение по неверному
SIGSEGV + k + + + адресу
SIGUSR1, USR2 + i - + - пользовательские
SIGCLD + i - + + смерть потомка

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

А. Богатырев, 1992-95 - 214 - Си в UNIX

реакция на этот сигнал не сбрасывается.
- SIGALRM посылается в результате его заказа вызовом alarm() (см. ниже).
- Сигнал SIGCLD посылается процессу-родителю при выполнении процессом-потомком
сисвызова exit (или при смерти вследствие получения сигнала). Обычно процесс-
родитель при получении такого сигнала (если он его заказывал) реагирует, выпол-
няя в обработчике сигнала вызов wait (см. ниже). По-умолчанию этот сигнал игно-
рируется.
- Реакция SIG_IGN не сбрасывается в SIG_DFL при приходе сигнала, т.е. сигнал игно-
рируется постоянно.
- Вызов signal возвращает старое значение реакции, которое может быть запомнено в
переменную вида void (*f)(); а потом восстановлено.
- Синхронное ожидание (сисвызов) может иногда быть прервано асинхронным событием
(сигналом), но об этом ниже.

Некоторые версии UNIX предоставляют более развитые средства работы с сигналами.
Опишем некоторые из средств, имеющихся в BSD (в других системах они могут быть смоделированы
другими способами).
Пусть у нас в программе есть "критическая секция", во время выполнения которой
приход сигналов нежелателен. Мы можем "заморозить" (заблокировать) сигнал, отложив
момент его поступления до "разморозки":

|
sighold(sig); заблокировать сигнал
| :
КРИТИЧЕСКАЯ :"---процессу послан сигнал sig,
СЕКЦИЯ : но он не вызывает реакцию немедленно,
|

Список страниц

Закладка в соц.сетях

Купить

☏ Заказ рекламы: +3805044-68-872

© Ассоциация электронных библиотек Украины

☝ Все материалы сайта (включая статьи, изображения, рекламные объявления и пр.) предназначены только для предварительного ознакомления. Все права на публикации, представленные на сайте принадлежат их законным владельцам. Просим Вас не сохранять копии информации.