English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
В процессе разработки программ мы время от времени используем некоторые таймеры, обычно если точность времени не требуется высокой, можно использовать функции sleep, usleep, чтобы процесс спал в течение определенного времени для реализации таймера;
У前者 единица измерения - секунды (s), у второго - микросекунды (us); но иногда мы не хотим, чтобы процесс замирал, и нам нужно, чтобы процесс выполнялся normalmente, а затем выполнял соответствующие действия по достижении установленного времени;
В Linux мы обычно используем функции alarm и setitimer для реализации функции таймера;
Ниже мы проведем детальный анализ этих двух функций:
(1) функция alarm
alarm также известен как функция будильника, которая может устанавливать таймер в процессе, и когда время таймера достигает указанного значения, он отправляет сигнал SIGALRM процессу;
Прототип функции alarm() выглядит следующим образом:
unsigned int alarm(unsigned int seconds); //seconds - это указанное количество секунд
Необходимые заголовочные файлы
#include<unistd.h>
Прототип функции
unsigned int alarm(unsigned int seconds)
Функциональные параметры
seconds: specify seconds
Возврат значений функции
Успех: если перед вызовом этой alarm() процесс уже установил время будильника, то возвращается оставшееся время до предыдущего времени будильника,否则 возвращается 0.
Ошибка: -1
Вот простой пример функции alarm():
void sigalrm_fn(int sig) { printf("alarm!\n"); alarm(2); return; {} int main(void) { signal(SIGALRM, sigalrm_fn); //后面的函数必须是带int参数的 alarm(1); while(1) pause(); {}
(2) функция setitimer()
В Linux, если требования к точности таймера не слишком высоки, можно использовать alarm() и signal(), но для реализации высокоточного定时ного функционала необходимо использовать функцию setitimer().
setitimer() - это API Linux, а не стандартная библиотека C, setitimer() имеет две функции: определить время, по истечении которого выполняется функция, и выполнять функцию через равные интервалы времени;
Linux для каждой задачи планирует три внутренних таймера:
ITIMER_REAL: реальный таймер, который всегда учитывается, независимо от того, в каком режиме работает процесс (даже если процесс приостановлен). По достижении времени к процессу отправляется сигнал SIGALRM.
ITIMER_VIRTUAL: это не реальный таймер, когда процесс в режиме пользователя (т.е. при выполнении программы) рассчитывает время выполнения процесса. По достижении времени к этому процессу отправляется сигнал SIGVTALRM.
ITIMER_PROF: процесс в режиме пользователя (т.е. при выполнении программы) и режиме ядра (т.е. при планировании процесса) учитывается. По достижении времени генерируется сигнал SIGPROF. Время, записанное ITIMER_PROF, больше времени планирования процесса по сравнению с ITIMER_VIRTUAL.
Таймер в момент инициализации получает начальное значение, которое с течением времени уменьшается, и после уменьшения до 0 выдает сигнал, а затем восстанавливает начальное значение. В задаче мы можем использовать один или все три таймера, но в один момент времени можно использовать только один таймер одного типа.
Прототип функции setitimer:}
#include <sys/time.h> int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value); Значения таймера определяются следующими структурами: struct itimerval { struct timeval it_interval; /* следующее значение */ struct timeval it_value; /* текущее значение */ }; struct timeval { time_t tv_sec; /* секунды */ suseconds_t tv_usec; /* микросекунды */ };
it_interval используется для указания интервала выполнения задачи, а it_value сохраняет время до выполнения задачи. Например, если указать it_interval в 2 секунды (микросекунды 0), то в начале мы также устанавливаем it_value в 2 секунды (микросекунды 0). Через одну секунду it_value уменьшается до 1, а через ещё одну секунду до 0. В этот момент выдается сигнал (информирующий пользователя о том, что время пришло и можно выполнять задачу), и система автоматически resets it_value до значения it_interval, то есть 2 секунды, и начинает новый счёт
Вот пример простого использования setitimer:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <signal.h> #include <sys/time.h> void test_func(); { static count = 0; printf("count is %d\n", count++); {} void init_sigaction(); { struct sigaction act; act.sa_handler = test_func; // устанавливаем функцию обработки сигнала act.sa_flags = 0; sigemptyset(&act.sa_mask); sigaction(SIGPROF, &act, NULL); // время до отправки SIGPROF сигнала {} void init_time()} { struct itimerval val; val.it_value.tv_sec = 1; // таймер будет активирован через 1 секунду val.it_value.tv_usec = 0; val.it_interval = val.it_value; // интервал таймера 1с setitimer(ITIMER_PROF, &val, NULL); {} int main(int argc, char **argv) { init_sigaction(); init_time(); while(1); return 0; {}
可以看出, каждый секунд выводится значение count:
Вот результат выполнения:
[root@localhost 5th]# ./test
count is 0
count is 1
count is 2
count is 3
count is 4
count is 5
count is 6
count is 7
count is 8
count is 9
Приложение:
signal
1. Заголовочный файл
#include <signal.h>
2. Функциональность
Установка действия для определенного сигнала
3. Прототип функции
void (*signal(int signum,void(* handler)(int)))(int);
Разберем это подробнее:
typedef void (*sig_t) (int);
sig_t signal(int sig, sig_t func);
Первый параметр - целевой сигнал. Параметр func - это указатель на функцию, которая обрабатывает сигнал. Эта функция обработки сигнала имеет параметр типа int и должна возвращать void.
Параметр func также можно установить в следующие значения:
SIG_IGN: Если параметр func установлен в SIG_IGN, сигнал будет проигнорирован.
SIG_DFL: Если параметр func установлен в SIG_DFL, сигнал будет обрабатываться по определенному поведению.
4. Возможные типы сигналов sig
1) #define SIGHUP 1 /* hangup */
SIGHUP является одним из часто используемых сигналов системным администратором Unix. Многие фоновые сервисные процессы после получения этого сигнала будут заново читать свои конфигурационные файлы. Однако, фактическая функция этого сигнала - уведомить процесс о том, что его управляющий терминал был разорван. По умолчанию поведение - завершение процесса.
2) #define SIGINT 2 /* прерывание */
Для пользователей Unix SIGINT — это еще один часто используемый сигнал. Многие комбинации CTRL-C в shellах известны этому сигналу. Официальное имя этого сигнала — сигнал прерывания. По умолчанию поведение — завершение процесса.
3) #define SIGQUIT 3 /* quit */
Сигнал SIGQUIT используется для приема комбинации CTRL-/ от оболочки. Кроме того, он также используется для уведомления процесса о выходе. Это обычный сигнал, который используется для уведомления приложения о спокойном завершении (примечание переводчика: то есть выполнения некоторых действий выхода перед завершением). По умолчанию поведение — завершение процесса и создание core dump.
4) #define SIGILL 4 /* незаконное指令 (не сбрасывается при захвате) */
Если выполняемый процесс содержит незаконное指令, операционная система отправляет процессу сигнал SIGILL. Если ваша программа использует потоки или функции pointers, то, если возможно, можно попытаться захватить этот сигнал для协助 отладки. (Обратите внимание: в оригинале这句话 звучит так: “If your program makes use of use of threads, or pointer functions, try to catch this signal if possible for aid in debugging.”. Два “use of use of” могут быть ошибкой в публикации или я действительно не понял их значение; также я часто слышу “functions pointer”, для “pointer functions” это, возможно, что-то из Fortran, в любом случае, я не знаю точного значения, пожалуйста, исправьте меня, если знаете. По умолчанию поведение — завершение процесса и создание core dump.
5) #define SIGTRAP 5 /* отладочный прерывание (не сбрасывается при захвате) */
Сигнал SIGTRAP определен стандартом POSIX и используется для целей отладки. Когда отладаемый процесс получает этот сигнал, это означает, что он достиг某一个 точки прерывания отладки. Как только этот сигнал передается, отладаемый процесс останавливается, и его родительский процесс получает уведомление. По умолчанию поведение — завершение процесса и создание 核心 dump.
6) #define SIGABRT 6 /* abort() */
SIGABRT предоставляет способ для завершения процесса с созданием ядра дампа. Однако, если сигнал перехватывается, и обработчик сигнала не возвращает управление, процесс не завершается. По умолчанию поведение - это终止 процесс и создание ядра дампа.
7) #define SIGFPE 8 /* исключение плавающей точки */
Когда процесс возникает ошибка плавающей точки, сигнал SIGFPE отправляется процессу. Для программ, выполняющих сложные математические вычисления, рекомендуется перехватывать этот сигнал. По умолчанию这种行为 приводит к终止у процесса и созданию ядра дампа.
8) #define SIGKILL 9 /* kill (не может быть перехвачен или проигнорирован) */
SIGKILL - это один из самых сложных для борьбы сигналов. Как вы видите в комментарии рядом с ним, этот сигнал не может быть перехвачен или проигнорирован. Как только сигнал передается процессу, процесс заканчивается. Однако, в некоторых редких случаях SIGKILL не завершает процесс. Эти редкие случаи происходят при обработке «несрывных операций» (например, ввод-вывод на диск). 虽然 такие случаи редки, но если они происходят, это может привести к блокировке процесса. Единственный способ прекратить процесс - это перезагрузка. По умолчанию поведение - это终止 процесс.
9) #define SIGBUS 10 /* ошибка шины */
Как暗示ывает его имя, сигнал SIGBUS возникает, когда CPU обнаруживает ошибку на шине данных. Этот сигнал возникает, когда программа пытается получить доступ к памяти, адрес которой не правильно выравнен. По умолчанию这种行为 приводит к终止у процесса и созданию ядра дампа.
10) #define SIGSEGV 11 /* нарушение сегментации */
SIGSEGV - это сигнал, с которым хорошо знакомы программисты на C/C++. Этот сигнал возникает, когда программа пытается получить доступ к защищенной памяти или к无效ному виртуальному адресу памяти (грязные указатели, dirty pointers, примечание: это происходит из-за несинхронизации с содержимым резервного хранилища. О «диких указателях» можно узнать по адресу http://en.wikipedia.org/wiki/Wild_pointer.). По умолчанию这种行为 приводит к终止у процесса и созданию ядра дампа.
11) #define SIGSYS 12 /* non-existent system call invoked */
Сигнал SIGSYS будет передан, если процесс выполнит не существующий системный вызов. Операционная система отправит этот сигнал, и процесс будет завершен. По умолчанию行为 - завершение процесса и создание ядра дампа.
12) #define SIGPIPE 13 /* write on a pipe with no one to read it */
Роль потока аналогична телефону, позволяя процессам обмениваться сообщениями. Если процесс пытается выполнить запись в поток, а на другой стороне потока нет получателя, операционная система отправит сигнал SIGPIPE этому злосчастному процессу (в данном случае это процесс, который пытается写入). По умолчанию行为 - завершение процесса.
13) #define SIGALRM 14 /* alarm clock */
Когда процессу наступает время его таймера, сигнал SIGALRM будет передан (delivered) этому процессу. Эти таймеры будут рассмотрены в следующей главе.
установки с помощью вызовов setitimer и alarm. По умолчанию行为 - завершение процесса.
14) #define SIGTERM 15 /* software termination signal from kill */
Сигнал SIGTERM отправляется процессу, чтобы уведомить его о том, что время для завершения процесса пришло, и до этого нужно выполнить некоторые действия по очистке. Сигнал SIGTERM - это стандартный сигнал, отправляемый командой kill в Unix, а также это стандартный сигнал, отправляемый операционной системой процессу при关闭.
15) #define SIGURG 16 /* urgent condition on IO channel */
При возникновении некоторых ситуаций в уже открытых сокетах процесса, сигнал SIGURG будет отправлен этому процессу. Если процесс не перехватывает этот сигнал, то он будет丢弃ен. По умолчанию行为 -丢弃 сигнал.
16) #define SIGSTOP 17 /* sendable stop signal not from tty */
Этот сигнал не может быть перехвачен или пропущен. Как только процесс получает сигнал SIGSTOP, он сразу же останавливается (stop) до получения другого сигнала SIGCONT
Сигнал остановки. По умолчанию行为 - остановка процесса до получения сигнала SIGCONT.
17) #define SIGTSTP 18 /* stop signal from tty */
SIGSTP与SIGSTOP类似,它们的区别在于SIGSTP信号可以被捕获或忽略。当shell从键盘接收到CTRL-Z的时候就会交付(deliver)这个信号给进程。默认行为是停止进程,直到接收到一个SIGCONT信号为止。
18) #define SIGCONT 19 /* continue a stopped process */
SIGCONT也是一个有意思的信号。如前所述,当进程停止的时候,这个信号用来告诉进程恢复运行。该信号的有趣的地方在于:它不能被忽略或阻塞,但可以被捕获。这样做很有意义:因为进程大概不愿意忽略或阻塞SIGCONT信号,否则,如果进程接收到SIGSTOP或SIGSTP的时候该怎么办?默认行为是丢弃该信号。
19) #define SIGCHLD 20 /* to parent on child stop or exit */
SIGCHLD是由Berkeley Unix引入的,并且比SRV 4 Unix上的实现有更好的接口。(如果信号是一个没有追溯能力的过程(not a retroactive process),那么BSD的SIGCHID信号实现会比较好。在system V Unix的实现中,如果进程要求捕获该信号,操作系统会检查是否存在有任何未完成的子进程(这些子进程是已经退出exit)的子进程,并且在等待调用wait的父进程收集它们的状态)。如果子进程退出的时候附带有一些终止信息(terminating information),那么信号处理句柄就会被调用。所以,仅仅要求捕获这个信号会导致信号处理句柄被调用(译注:即是上面说的“信号的追溯能力”),而这是却一种相当混乱的状况。)一旦一个进程的子进程状态发生改变,SIGCHLD信号就会被发送给该进程。就像我在前面章节提到的,父进程虽然可以fork出子进程,但没有必要等待子进程退出。一般来说这是不太好的,因为这样的话,一旦进程退出就可能会变成一个僵尸进程。可是如果父进程捕获SIGCHLD信号的话,它就可以使用wait系列调用中的某一个去收集子进程状态,或者判断发生了什么事情。当发送SIGSTOP、SIGSTP或SIGCONF信号给子进程时,SIGCHLD信号也会被发送给父进程。默认行为是丢弃该信号。
20) #define SIGTTIN 21 /* к читателям pgrp при чтении фонового tty */
Когда фоновый процесс пытается выполнить чтение, сигналу SIGTTIN отправляется к этому процессу. Процесс будет блокироваться до получения сигнала SIGCONT. По умолчанию поведение - остановить процесс до получения сигнала SIGCONT.
21) #define SIGTTOU 22 /* как TTIN, если (tp->t_local<OSTOP) */
Сигнал SIGTTOU очень похож на SIGTTIN, разница в том, что сигнал SIGTTOU генерируется только когда фоновый процесс пытается выполнить запись на tty с установленной атрибутом TOSTOP. Однако, если этот атрибут не установлен для tty, сигнал SIGTTOU не отправляется. По умолчанию поведение - остановить процесс до получения сигнала SIGCONT.
22) #define SIGIO 23 /* возможный сигнал ввода/вывода */
Если процесс выполняет I/O операции с файловым дескриптором, сигналу SIGIO отправляется к этому процессу. Процесс может установить это через вызов fcntl. По умолчанию поведение - игнорировать сигнал.
23) #define SIGXCPU 24 /* превысил ограничение времени CPU */
Если процесс выходит за пределы ограничений использования CPU (ограничение CPU), сигналу SIGXCPU отправляется к нему. Это ограничение можно настроить с помощью later discussed setrlimit. По умолчанию поведение - завершение процесса.
24) #define SIGXFSZ 25 /* превышение размера файла */
Если процесс exceeds его размер файла, который он может использовать, сигнал SIGXFSZ отправляется процессу. Мы продолжим обсуждение этого сигнала позже. По умолчанию поведение -терминировать процесс.
25) #define SIGVTALRM 26 /* сигнал виртуального времени */
Если процесс exceeds его установленный счетчик виртуального таймера, сигнал SIGVTALRM отправляется процессу. По умолчанию поведение -终止 процесса.
26) #define SIGPROF 27 /* сигнал времени профилирования */
Когда установлены таймеры, сигнал SIGPROF является еще одним сигналом, который будет отправлен процессу. По умолчанию поведение -终止 процесса.
27) #define SIGWINCH 28 /* изменения размера окна */
Когда процесс изменяет строку или столбец терминала (например, увеличиваете размер вашего xterm), сигнал SIGWINCH отправляется процессу. По умолчанию поведение - выброс сигнала.
28) #define SIGUSR1 29 /* пользовательский сигнал 1 */
29) #define SIGUSR2 30 /* пользовательский сигнал 2 */
SIGUSR1 и SIGUSR2 эти сигналы спроектированы для пользователя. Их можно настроить для выполнения ваших потребностей. Иначе говоря, операционная система не имеет никакого поведения, связанного с этими сигналами. По умолчанию поведение -终止进程. (Примечание переводчика: По тексту оригинала эти два предложения кажутся несколько противоречащими.)
5. Пример
5.1. Реализация Ctrl+C в Linux для Windows1.
Обычно做法 в Linux:
signal(SIGINT, sigfunc); // настройка сигнала void sigfunc(int signo) { ... // операции, связанные с сигналами {}
Вот реализация Ctrl+C в Linux для Windows
#include <stdio.h> #include <windows.h> static is_loop = 1; // Функция для захвата события Ctrl+C консоли BOOL CtrlHandler( DWORD fdwCtrlType ) { switch (fdwCtrlType) { /* Обработка сигнала CTRL-C. */ case CTRL_C_EVENT: printf("CTRL_C_EVENT \n"); break; case CTRL_CLOSE_EVENT: printf("CTRL_CLOSE_EVENT \n"); break; case CTRL_BREAK_EVENT: printf("CTRL_BREAK_EVENT \n"); break; case CTRL_LOGOFF_EVENT: printf("CTRL_LOGOFF_EVENT \n"); break; case CTRL_SHUTDOWN_EVENT: printf("CTRL_SHUTDOWN_EVENT \n"); break; default: return FALSE; {} is_loop = 0; return (TRUE); {} int main(int argc, char *argv[]) { printf("Set Console Ctrl Handler\n"); SetConsoleCtrlHandler((PHANDLER_ROUTINE)CtrlHandler, TRUE); while (is_loop); return 0; {}
5.2. Реализация Ctrl+C в Linux под Windows
#include <stdio.h> #include <windows.h> #define CONTRL_C_HANDLE() signal(3, exit) int main(int argc, char *argv[]) { printf("Set Console Ctrl Handler\n"); CONTRL_C_HANDLE(); while (1); system("PAUSE"); return 0; {}
Вот и подошел к концу краткий обзор различных定时 функций в Linux, который я предоставил вам. Надеюсь, вам понравилось и вы поддержите呐喊 учебник~