Vladislav Shpilevoy PRO
Database C developer at Tarantool. Backend C++ developer at VirtualMinds.
Системное программирование
Лекция 4:
Сигналы. Аппаратные и программные прерывания, их природа. Top and bottom halves. Сигналы и системные вызовы, контекст сигнала.
The presentation is outdated and not maintained anymore. Please, refer to the English version for the most actual slides.
Дедлайны:
.text
.data
.stack
.heap
.stack
.stack
Файловые дескрипторы
Очередь сигналов
IPC
Память
struct task_struct {
/* ... */
struct files_struct *files;
/* ... */
};
struct files_struct {
struct fdtable *fdt;
};
struct fdtable {
unsigned int max_fds;
struct file **fd;
};
struct file {
struct path f_path;
struct inode *f_inode;
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode;
loff_t f_pos;
const struct cred *f_cred;
};
Процесс хранит открытые файлы
В таблице дескрипторов
Таблица - обычный массив
Дескриптор в ядре - это структура. В user space - число. Индекс в массиве дескрипторов.
0x0
0xffffffff
.text
.data
.bss
.heap
.stack
.env
Скорость
Объем
int 0x80
sysenter/sysexit syscall/sysret
Процесс
int 0x80
Адрес обработчика?
0x0...0x80...
IDT - Interrupt Descriptor Table
Процесс
syscall sysenter
Примеры процессорных прерываний?
1 балл
Аппаратные
Программные
Для асинхронного обмена с I/O аппаратурой
Они же software, exceptions, traps
Для асинхронной обработки исключительных ситуаций
Прочитать диск?
syscall(read)
Файловая система
Драйвер
Шина
Как узнать, когда чтение готово?
Как это сделать для всех устройств?
PIC
CPU
Dev 1
Есть, что сказать процессору?
Programmable Interrupt Controller
00000
Dev 2
00001
00001
00001
00010
Генерируются аппаратурой для привлечения внимания процессора
...
push %rbp
mov %rsp,%rbp
sub $0x10,%rsp
lea 0x1c1(%rip),%rax
mov %edi,-0x4(%rbp)
mov %esi,-0x8(%rbp)
mov -0x8(%rbp),%esi
mov -0x4(%rbp),%edx
mov %rax,%rdi
mov $0x0,%al
callq 100001df2 <_recursive+0x82>
mov %eax,-0xc(%rbp)
callq 100001440 <_coro_yield>
cmpl $0x5,-0x4(%rbp)
...
Генерируются процессором для обработки сложных и неожиданных ситуаций
...
mov %eax,-0x18(%rbp)
callq 100001660 <_coro_new>
mov %rax,-0x20(%rbp)
callq 100001440 <_coro_yield>
mov -0xc(%rbp),%eax
add $0x20,%rsp
pop %rbp
retq
...
Ошибки (exceptions)
Ловушки (traps)
Обработчик
Если исправлена, то возврат на начало инструкции
Обработчик
... mov %edi,-0x4(%rbp) mov %esi,-0x8(%rbp) mov -0x8(%rbp),%esi mov -0x4(%rbp),%edx mov %rax,%rdi mov $0x0,%al ...
... mov %edi,-0x4(%rbp) mov %esi,-0x8(%rbp) int 0x03 mov -0x4(%rbp),%edx mov %rax,%rdi mov $0x0,%al ...
Возврат к следующей инструкции
Обработка прерывания
Контекст сохранить в ОП. Функцию найти в таблице функций, где у каждого прерывания задан обработчик.
1 балл
IDT
idtr
0
255
Контекст
Память
.text обработчика
Стек для сохранения контекста
Номер | Назначение |
---|---|
0 | Деление на 0 |
1 | Пошаговый режим выполнения |
2 | NMI - Non-Maskable Interrupt |
3 | Breakpoint |
6 | Некорректная инструкция |
8 | Double page fault |
14 | Page fault |
NA | Triple page fault |
32 - 255 | - Пользовательские прерывания - |
Top Half
Bottom Half
int
request_threaded_irq(unsigned int irq,
irq_handler_t handler,
irq_handler_t thread_fn,
unsigned long irqflags,
const char *devname,
void *dev_id);
Номер линии на PIC
Обработчик каждого прерывания
Флаги блокировки прерываний, флаги разделения линии для нескольких устройств
Произвольные данные. Обычно структура устройства
static irqreturn_t rtc_interrupt(int irq, void *dev_id)
{
spin_lock(&rtc_lock);
rtc_irq_data += 0x100;
rtc_irq_data &= ~0xff;
if (is_hpet_enabled())
rtc_irq_data |= (unsigned long)irq & 0xF0;
else
rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0);
if (rtc_status & RTC_TIMER_ON)
mod_timer(&rtc_irq_timer, jiffies + HZ/rtc_freq + 2*HZ/100);
spin_unlock(&rtc_lock);
wake_up_interruptible(&rtc_wait);
kill_fasync(&rtc_async_queue, SIGIO, POLL_IN);
return IRQ_HANDLED;
}
/* ... */
static int __init rtc_init(void)
{
/* ... */
if (request_irq(rtc_irq, rtc_interrupt, IRQF_SHARED, "rtc",
(void *)&rtc_port)) {
rtc_has_irq = 0;
printk(KERN_ERR "rtc: cannot register IRQ %d\n", rtc_irq);
return -EIO;
}
/* ... */
}
Сигнатура обработчика прерывания - номер линии и те же данные, что сохраняли в request_threaded_irq
Таймер тикает с частотой HZ - используется для обновления счетчика времени
Пробуждение планировщика
Регистрация обработчика - линия делится, имя "rtc", ...
/proc/interrupts - зарегистрированные обработчики прерываний
CPU0 CPU1
0: 30 0 IO-APIC 2-edge timer
1: 7418 0 IO-APIC 1-edge i8042
8: 0 0 IO-APIC 8-edge rtc0
9: 0 0 IO-APIC 9-fasteoi acpi
12: 0 2216 IO-APIC 12-edge i8042
14: 129007 0 IO-APIC 14-edge ata_piix
15: 0 0 IO-APIC 15-edge ata_piix
18: 0 0 IO-APIC 18-fasteoi vboxvideo
19: 92901 588 IO-APIC 19-fasteoi enp0s3
20: 0 69311 IO-APIC 20-fasteoi vboxguest
21: 13110 44644 IO-APIC 21-fasteoi ahci[0000:00:0d.0], snd_intel8x0
22: 29 0 IO-APIC 22-fasteoi ohci_hcd:usb1
NMI: 0 0 Non-maskable interrupts
LOC: 2967493 4647820 Local timer interrupts
SPU: 0 0 Spurious interrupts
PMI: 0 0 Performance monitoring interrupts
IWI: 0 0 IRQ work interrupts
RTR: 0 0 APIC ICR read retries
RES: 340157 261945 Rescheduling interrupts
CAL: 22307 12237 Function call interrupts
TLB: 3463 4360 TLB shootdowns
TRM: 0 0 Thermal event interrupts
THR: 0 0 Threshold APIC interrupts
DFR: 0 0 Deferred Error APIC interrupts
MCE: 0 0 Machine check exceptions
MCP: 418 418 Machine check polls
HYP: 0 0 Hypervisor callback interrupts
CPU0 CPU1
0: 30 0 IO-APIC 2-edge timer
1: 7430 0 IO-APIC 1-edge i8042
8: 0 0 IO-APIC 8-edge rtc0
9: 0 0 IO-APIC 9-fasteoi acpi
12: 0 2240 IO-APIC 12-edge i8042
14: 129069 0 IO-APIC 14-edge ata_piix
15: 0 0 IO-APIC 15-edge ata_piix
18: 0 0 IO-APIC 18-fasteoi vboxvideo
19: 92932 588 IO-APIC 19-fasteoi enp0s3
20: 0 69584 IO-APIC 20-fasteoi vboxguest
21: 13110 44695 IO-APIC 21-fasteoi ahci[0000:00:0d.0], snd_intel8x0
22: 29 0 IO-APIC 22-fasteoi ohci_hcd:usb1
NMI: 0 0 Non-maskable interrupts
LOC: 2970432 4649435 Local timer interrupts
SPU: 0 0 Spurious interrupts
PMI: 0 0 Performance monitoring interrupts
IWI: 0 0 IRQ work interrupts
RTR: 0 0 APIC ICR read retries
RES: 342167 263540 Rescheduling interrupts
CAL: 22316 12237 Function call interrupts
TLB: 3463 4360 TLB shootdowns
TRM: 0 0 Thermal event interrupts
THR: 0 0 Threshold APIC interrupts
DFR: 0 0 Deferred Error APIC interrupts
MCE: 0 0 Machine check exceptions
MCP: 419 419 Machine check polls
HYP: 0 0 Hypervisor callback interrupts
CPU0 CPU1
0: 30 0 IO-APIC 2-edge timer
1: 7442 0 IO-APIC 1-edge i8042
8: 0 0 IO-APIC 8-edge rtc0
9: 0 0 IO-APIC 9-fasteoi acpi
12: 0 2296 IO-APIC 12-edge i8042
14: 129125 0 IO-APIC 14-edge ata_piix
15: 0 0 IO-APIC 15-edge ata_piix
18: 0 0 IO-APIC 18-fasteoi vboxvideo
19: 92960 588 IO-APIC 19-fasteoi enp0s3
20: 0 70033 IO-APIC 20-fasteoi vboxguest
21: 13110 44733 IO-APIC 21-fasteoi ahci[0000:00:0d.0], snd_intel8x0
22: 29 0 IO-APIC 22-fasteoi ohci_hcd:usb1
NMI: 0 0 Non-maskable interrupts
LOC: 2972773 4652064 Local timer interrupts
SPU: 0 0 Spurious interrupts
PMI: 0 0 Performance monitoring interrupts
IWI: 0 0 IRQ work interrupts
RTR: 0 0 APIC ICR read retries
RES: 346096 265997 Rescheduling interrupts
CAL: 22322 12258 Function call interrupts
TLB: 3463 4381 TLB shootdowns
TRM: 0 0 Thermal event interrupts
THR: 0 0 Threshold APIC interrupts
DFR: 0 0 Deferred Error APIC interrupts
MCE: 0 0 Machine check exceptions
MCP: 419 419 Machine check polls
HYP: 0 0 Hypervisor callback interrupts
CPU0 CPU1
0: 30 0 IO-APIC 2-edge timer
1: 7451 0 IO-APIC 1-edge i8042
8: 0 0 IO-APIC 8-edge rtc0
9: 0 0 IO-APIC 9-fasteoi acpi
12: 0 2352 IO-APIC 12-edge i8042
14: 129171 0 IO-APIC 14-edge ata_piix
15: 0 0 IO-APIC 15-edge ata_piix
18: 0 0 IO-APIC 18-fasteoi vboxvideo
19: 92995 588 IO-APIC 19-fasteoi enp0s3
20: 0 70370 IO-APIC 20-fasteoi vboxguest
21: 13110 44769 IO-APIC 21-fasteoi ahci[0000:00:0d.0], snd_intel8x0
22: 29 0 IO-APIC 22-fasteoi ohci_hcd:usb1
NMI: 0 0 Non-maskable interrupts
LOC: 2974684 4654345 Local timer interrupts
SPU: 0 0 Spurious interrupts
PMI: 0 0 Performance monitoring interrupts
IWI: 0 0 IRQ work interrupts
RTR: 0 0 APIC ICR read retries
RES: 349138 268939 Rescheduling interrupts
CAL: 22334 12260 Function call interrupts
TLB: 3463 4383 TLB shootdowns
TRM: 0 0 Thermal event interrupts
THR: 0 0 Threshold APIC interrupts
DFR: 0 0 Deferred Error APIC interrupts
MCE: 0 0 Machine check exceptions
MCP: 419 419 Machine check polls
HYP: 0 0 Hypervisor callback interrupts
Решения для откладывания bottom half:
$ ps aux | grep softirq
root 7 0.0 0.0 S мар20 0:00 [ksoftirqd/0]
root 16 0.0 0.0 S мар20 0:00 [ksoftirqd/1]
Рабочие потоки ядра для выполнения softirq и tasklet-ов
Программные прерывания - основная часть ядра, нет Bottom Half
Аппаратные прерывания - драйвера, есть Bottom Half
Два типа прерываний, и для чего нужны?
Аппаратные и программные. Аппаратные для асинхронной работы с периферией. Программные для обработки исключительных ситуаций.
1 балла
Куда поступают аппаратные прерывания?
PIC или APIC - Programmable Interrupt Controller. Только оттуда они подаются на процессор, по одному.
1 балла
Откуда и как ядро берет обработчик прерывания?
Из IDT - Interrupt Descriptor Table. Она лежит в памяти, и ее начало сохранено ядром в регистр idtr.
1 балл
Прерывания - только в ядре.
Сигналы - аналог прерываний в userspace. Сигнал сохраняет контекст процесса и вызывает обработчик.
Сигнал | Назначение | Действие по умолчанию |
---|---|---|
SIGABRT | Аварийное завершение | Завершить процесс |
SIGALRM | Таймауты | Завершить процесс |
SIGINT | Остановка пользователем | Завершить процесс |
SIGSEGV | Ошибка памяти | Завершить процесс |
SIGUSR1, SIGUSR2 | Пользовательские сигналы | Завершить процесс |
SIGCHLD | Завершился процесс потомок | Игнорирование |
SIGKILL | Принудительное завершение | Завершить процесс |
... | - Еще десятки сигналов - | ... |
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
"Старый, но не бесполезный"
Номер сигнала
Функция-обработчик. Принимает номер сигнала
static void
on_new_signal(int signum)
{
switch(signum) {
case SIGINT:
printf("caught sigint\n");
break;
case SIGUSR1:
printf("caught usr1\n");
break;
case SIGUSR2:
printf("caught usr2\n");
break;
default:
printf("caught unknown signal\n");
exit(-1);
}
}
int
main(void)
{
printf("my pid: %d\n", (int) getpid());
signal(SIGINT, on_new_signal);
signal(SIGUSR1, on_new_signal);
signal(SIGUSR2, on_new_signal);
while(true) pause();
return 0;
}
$> gcc 1_basic_signal.c
$> ./a.out
my pid: 42645
$> kill -s SIGUSR1 42645
^C caught sigint
caught usr1
$> kill -s SIGUSR2 42645
caught usr2
$> kill 42645
Terminated $>
signal - устаревший вызов
static bool is_sig_received = false;
static void
on_new_signal(int signum)
{
printf("Received a signal\n");
is_sig_received = true;
}
static void
interrupt(void)
{
getchar();
}
int
main(void)
{
printf("my pid: %d\n", (int) getpid());
signal(SIGUSR1, on_new_signal);
printf("Installed a handler\n");
interrupt();
while (! is_sig_received) {
interrupt();
printf("Wait for a signal\n");
pause();
}
printf("Finish\n");
return 0;
}
$> gcc 2_pause_hangs.c $>./a.out my pid: 42718 Installed a handler
$> kill -s SIGUSR1 42718
$> kill -s SIGUSR1 42769
$> kill 42769
Received a signal
<press Enter> Finish $>
$> ./a.out
my pid: 42769
Installed a handler
<press Enter>
Received a signal
<press Enter>
Wait for a signal
<press Enter>
<press Enter>
Terminated $>
Общие проблемы сигналов:
ssize_t rc;
while ((rc = read(fd, buf, size) == -1 && errno == EINTR);
Обычно "медленный" системный вызов надо повторять, если ошибка EINTR - вызов был прерван сигналом
static int alarm_cnt = 0;
static void
do_malloc(void)
{
for (int i = 0; i < 10; ++i)
free(malloc(1024 * 1024 * i));
}
static void
on_new_signal(int signum)
{
printf("Interrupted %d times\n", ++alarm_cnt);
do_malloc();
}
int
main(void)
{
printf("my pid: %d\n", (int) getpid());
signal(SIGUSR1, on_new_signal);
while (true)
do_malloc();
return 0;
}
$> gcc 3_intr_malloc.c $>./a.out my pid: 42888
$> while kill -s SIGUSR1 42888; do sleep 0; done
$>
Interrupted 1 times
Interrupted 2 times
Illegal instruction: 4
$>
int kill(pid_t pid, int sig);
int raise(int sig);
int pause(void);
unsigned int alarm(unsigned int seconds);
Послать сигнал процессу
PID получателя
Номер сигнала. Не обязательно SIGKILL
Послать сигнал себе
Остановить поток, пока не будет получен и обработан любой сигнал
Отложенно послать себе SIGALRM
Через сколько секунд
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
Управление маской сигналов потока. Если сигнал есть в маске - он блокируется.
Что сделать с маской в аргументе?
Маска для выполнения действия 'how'
Выходной параметр для получения старой маски
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
Заполнение и проверка маски
int sigpending(sigset_t *set);
Проверка, какие сигналы пришли и ожидают разблокировки
static int cnt = 0;
static void on_new_signal(int signum)
{
printf("Processed a signal %dth time\n",
++cnt);
}
int main(void)
{
printf("Start\n");
signal(SIGINT, on_new_signal);
sigset_t oldm, newm, pendingm;
sigemptyset(&newm);
sigaddset(&newm, SIGINT);
printf("Block SIGINT\n");
sigprocmask(SIG_BLOCK, &newm, &oldm);
getchar();
sigpending(&pendingm);
if (sigismember(&pendingm, SIGINT))
printf("SIGINT is pending\n");
printf("Unblock SIGINT\n");
sigprocmask(SIG_SETMASK, &oldm, NULL);
while(cnt < 4)
pause();
return 0;
}
$> gcc 4_sigprocmask.c $>./a.out Start Block SIGINT
^C ^C ^C ^C
<press Enter>
SIGINT is pending
Unblock SIGINT
Processed a signal 1th time
^C Processed a signal 2th time ^C Processed a signal 3th time ^C Processed a signal 4th time $>
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
};
Номер сигнала
Настраиваемая установка обработчика сигнала
Новый обработчик
Получение старого обработчика
Два варианта обработки сигнала
Очень подробная информация о причине сигнала
ucontext_t * - сохраненный контекст прерванного потока
Маска сигналов для добавление к маске потока на время обработки сигнала. Этот сигнал включен неявно по умолчанию
Управление поведением обработчика:
static void
on_new_signal(int signum, siginfo_t *info,
void *context)
{
printf("Segfault: signum = %d, si_signo = "\
"%d, si_addr = %p, si_code = \n",
signum, info->si_signo, info->si_addr);
switch (info->si_code) {
case SEGV_MAPERR:
printf("not mapped\n");
break;
case SEGV_ACCERR:
printf("no permission\n");
break;
default:
printf("%d\n", info->si_code);
break;
}
exit(-1);
}
int
main(void)
{
struct sigaction act;
act.sa_sigaction = on_new_signal;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO;
sigaction(SIGSEGV, &act, NULL);
char *ptr = (char *) 10;
*ptr = 100;
return 0;
}
$> gcc 5_sigaction_info.c $>./a.out Segfault: signum = 11, si_signo = 11, si_addr = 0xa, si_code = not mapped $>
Для использования расширенного обработчика - SA_SIGINFO
Указатель, очевидно, указывает на невалидную память
Это = SIGSEGV, обработчик которого печатает место и причину
static int finished = 0;
static int id = 0;
static int is_usr1_blocked(void)
{
sigset_t old;
sigprocmask(0, NULL, &old);
return sigismember(&old, SIGUSR1);
}
static void on_new_signal(int signum)
{
int my_id = ++id;
printf("Begin processing %d, "\
"usr1 block = %d\n", my_id,
is_usr1_blocked());
if (my_id == 1)
raise(signum);
printf("End processing %d\n", my_id);
++finished;
}
Проверка, что сигнал SIGUSR1 заблокирован
Обработчик сигнала вызывает свой собственный сигнал еще раз
int main(int argc, char **argv)
{
struct sigaction act;
act.sa_handler = on_new_signal;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
for (int i = 1; i < argc; ++i) {
if (strcmp(argv[1], "nodefer") == 0)
act.sa_flags |= SA_NODEFER;
}
sigaction(SIGUSR1, &act, NULL);
printf("Before raises, usr1 block = %d\n",
is_usr1_blocked());
raise(SIGUSR1);
while (finished != 2)
sched_yield();
printf("After raises, usr1 block = %d\n",
is_usr1_blocked());
return 0;
}
Если в main передан аргумент nodefer, то сигнал ставится с флагом SA_NODEFER - не блокировать сигнал в обработчике
$> gcc 6_sigaction_mask.c $>./a.out Before raises, usr1 block = 0 Begin processing 1, usr1 block = 1 End processing 1 Begin processing 2, usr1 block = 1 End processing 2 After raises, usr1 block = 0 $> $> $> ./a.out nodefer Before raises, usr1 block = 0 Begin processing 1, usr1 block = 0 Begin processing 2, usr1 block = 0 End processing 2 End processing 1 After raises, usr1 block = 0 $>
static int finished = 0;
static jmp_buf buf;
static int is_usr1_blocked(void)
{
sigset_t old;
sigprocmask(0, NULL, &old);
return sigismember(&old, SIGUSR1);
}
static void on_new_signal(int signum)
{
printf("Process signal, usr1 block = %d\n",
is_usr1_blocked());
++finished;
longjmp(buf, 1);
}
Из обработчика выполняется прыжок вместо return
int main(void)
{
struct sigaction act;
act.sa_handler = on_new_signal;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGUSR1, &act, NULL);
printf("Before raise, usr1 block = %d\n",
is_usr1_blocked());
if (setjmp(buf) == 0)
raise(SIGUSR1);
while (finished != 1)
sched_yield();
printf("After raise, usr1 block = %d\n",
is_usr1_blocked());
return 0;
}
Когда прыжок вернется сюда, setjmp выдаст 1. На первый вызов будет 0, и тогда один раз вызывается сигнал
$> gcc 7_sigaction_jmp.c
$>./a.out
Before raise, usr1 block = 0
Process signal, usr1 block = 1
After raise, usr1 block = 1
$>
$> gcc 7_sigaction_jmp.c
$>./a.out
Before raise, usr1 block = 0
Process signal, usr1 block = 1
After raise, usr1 block = 0
$>
Linux
Mac
static int finished = 0;
static sigjmp_buf buf;
static int is_usr1_blocked(void)
{
sigset_t old;
sigprocmask(0, NULL, &old);
return sigismember(&old, SIGUSR1);
}
static void on_new_signal(int signum)
{
printf("Process signal, usr1 block = %d\n",
is_usr1_blocked());
++finished;
siglongjmp(buf, 1);
}
Из обработчика выполняется прыжок вместо return
int
main(void)
{
struct sigaction act;
act.sa_handler = on_new_signal;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGUSR1, &act, NULL);
printf("Before raise, usr1 block = %d\n",
is_usr1_blocked());
if (sigsetjmp(buf, 1) == 0)
raise(SIGUSR1);
while (finished != 1)
sched_yield();
printf("After raise, usr1 block = %d\n",
is_usr1_blocked());
return 0;
}
Когда прыжок вернется сюда, sigsetjmp выдаст 1 и откатит маску на момент вызова sigsetjmp.
$> gcc 8_sigaction_sigjmp.c
$>./a.out
Before raise, usr1 block = 0
Process signal, usr1 block = 1
After raise, usr1 block = 0
$>
$> gcc 8_sigaction_sigjmp.c
$>./a.out
Before raise, usr1 block = 0
Process signal, usr1 block = 1
After raise, usr1 block = 0
$>
Linux
Mac
int sigsuspend(const sigset_t *mask);
Атомарная подмена маски потока, ожидание сигнала, возврат маски
Маска для временного применения
static int finished = 0;
static int is_int_blocked(void)
{
sigset_t old;
sigprocmask(0, NULL, &old);
return sigismember(&old, SIGINT);
}
static void on_new_signal(int signum)
{
printf("Process signal, int block = %d\n",
is_int_blocked());
++finished;
}
int main(void)
{
printf("Before main, int block = %d\n",
is_int_blocked());
sigset_t block, old;
sigemptyset(&block);
sigaddset(&block, SIGINT);
sigprocmask(SIG_BLOCK, &block, &old);
struct sigaction act;
act.sa_handler = on_new_signal;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGINT, &act, NULL);
sigemptyset(&block);
while (finished != 1)
sigsuspend(&block);
sigprocmask(SIG_SETMASK, &old, NULL);
printf("After main, int block = %d\n",
is_int_blocked());
return 0;
}
Ранее SIGINT заблокирован, но каждая итерация sigsuspend разблокирует его в попытке получить нужный сигнал
$> gcc 9_sigsuspend.c
$>./a.out
Before main, int block = 0
^C Process signal, int block = 1
After main, int block = 0
$>
int sigaltstack(const stack_t *ss, stack_t *old_ss);
typedef struct {
void *ss_sp;
int ss_flags;
size_t ss_size;
} stack_t;
Задание альтернативного стека для обработчиков сигналов
Новый стек
Выходной параметр, старый стек
Память стека и ее размер.
Флаги управления стеком:
static int finished = 0;
#define handle_error() ({printf("error = %s\n", \
strerror(errno)); exit(-1); })
static void
on_new_signal(int signum)
{
volatile int local_var;
printf("Process signal, stack = %p\n", &local_var);
++finished;
}
static void
stack_create(int size)
{
int page_size = getpagesize();
size = size + (page_size - size % page_size);
if (size < SIGSTKSZ)
size = SIGSTKSZ;
stack_t s;
s.ss_sp = malloc(size);
printf("Page size = %d, new stack begin = %p, "\
"stack end = %p\n", page_size, s.ss_sp,
s.ss_sp + size);
s.ss_flags = 0;
s.ss_size = size;
if (sigaltstack(&s, NULL) != 0)
handle_error();
}
static void
stack_destroy(void)
{
stack_t s;
if (sigaltstack(NULL, &s) != 0)
handle_error();
s.ss_flags = SS_DISABLE;
if (sigaltstack(&s, NULL) != 0)
handle_error();
printf("Destroy stack %p\n", s.ss_sp);
free(s.ss_sp);
}
Размер стека нужен кратным размеру страницы и не меньше рекомендуемого
Откуда память взять - не важно. Хоть с кучи, хоть с этого же стека
Теперь любой сигнал, sigaction которого позвали с SA_ONSTACK, будет обрабатываться на этом стеке
Выключение стека не простое. Передать NULL первым аргументом не поможет. Нужно достать старый стек
Установить флаг SS_DISABLE - это отвяжет стек от ядра, сигналов, и можно делать с ним, что угодно
static void
wait_signal(void)
{
int need = finished + 1;
raise(SIGUSR1);
while (finished < need)
sched_yield();
}
int
main(int argc, char **argv)
{
struct sigaction act;
printf("Main, stack = %p\n", &act);
act.sa_handler = on_new_signal;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_ONSTACK;
stack_create(1024 * 64);
if (sigaction(SIGUSR1, &act, NULL) != 0)
handle_error();
wait_signal();
stack_destroy();
act.sa_flags = 0;
if (sigaction(SIGUSR1, &act, NULL) != 0)
handle_error();
wait_signal();
return 0;
}
Пробуем вызвать на альтернативном стеке
И на обычном. Адреса должны сильно отличаться
$> gcc 10_sigaltstacks.c
$>./a.out
Main, stack = 0x7ffeea01aa30
Page size = 4096, new stack begin = 0x105bfd000, stack end = 0x105c1d000
Process signal, stack = 0x105c1ca98
Destroy stack 0x105bfd000
Process signal, stack = 0x7ffeea01a438
$>
После установки нового стека видно, что теперь обработчик его использует
После удаления стека снова стал использоваться поточный
На sigaction, sigaltstack, sigsuspend и siglongjmp можно написать библиотеку корутин
Но! Есть проблема - результат setjmp портится после return, согласно стандарту. Почему так? Не помешает ли это библиотеке?
static volatile jmp_buf buf;
static volatile bool stop = false;
static volatile int total = 0;
static void
testf2(void)
{
setjmp(buf);
if (++total > 10) {
printf("Bad exit\n");
exit(-1);
}
}
static void
testf(void)
{
testf2();
}
int
main(void)
{
testf();
if (! stop) {
stop = true;
printf("Аfter first call\n");
longjmp(buf, 1);
}
return 0;
}
$> gcc 11_setjmp_problem.c
$>./a.out
After first call
Segmentation fault
$>
Почему?
Return использует адрес возврата, положенный заранее на стек. После return это место стека может быть перетерто другими вызовами. Прыгнуть обратно можно, но return не сделать.
2 балла
static volatile jmp_buf buf;
static volatile bool stop = false;
static volatile int total = 0;
static void
testf2(void)
{
setjmp(buf);
if (++total > 10) {
printf("Bad exit\n");
exit(-1);
}
}
static void
testf(void)
{
testf2();
}
int
main(void)
{
testf();
if (! stop) {
stop = true;
printf("Аfter first call\n");
longjmp(buf, 1);
}
return 0;
}
Стек
-
<ret addr to main>
<ret addr to testf>
-
-
-
-
Ошибка! На стеке нет адреса возврата!
Возможное решение - никогда не делать return. Все адреса возврата сохранять самому в стек jmp_buf объектов. Это делает coro_jmp.h библиотека из первого ДЗ.
Минусы:
Создание одной корутины:
Сделать return оттуда будет нельзя, так как на этом стеке адреса возврата нет после его открепления. Значит оттуда в конце надо "спрыгивать". Куда? - нужно сделать шедулер. Пользователь будет через него "ловить спрыгивающие" корутины.
#define coro_count 3
static struct coro *coros[coro_count];
static void
recursive(int deep, int id)
{
printf("%d: recursive step %d\n", id, deep);
coro_yield();
if (deep < 2)
recursive(deep + 1, id);
printf("%d: finish recursive step %d\n", id, deep);
}
static int
coro_func(void *ptr)
{
int id = (int) ptr;
printf("%d: coro is started\n", id);
coro_yield();
recursive(0, id);
return id;
}
static int
coro_tree_func(void *ptr)
{
int id = (int) ptr;
printf("%d: coro line is started\n", id);
coro_yield();
if (id > 1) {
printf("%d: coro line end\n", id);
} else {
printf("%d: coro line next\n", id);
coro_new(coro_tree_func,
(void *) (id + 1));
}
coro_yield();
return id;
}
int
main(void)
{
printf("Start main\n");
coro_sched_init();
for (int i = 0; i < coro_count; ++i) {
coros[i] = coro_new(coro_func,
(void *) i);
}
struct coro *c;
while ((c = coro_sched_wait()) != NULL) {
printf("Finished %d\n", coro_status(c));
coro_delete(c);
}
printf("Finished simple\n");
coro_new(coro_tree_func, (void *) 0);
while ((c = coro_sched_wait()) != NULL) {
printf("Finished %d\n", coro_status(c));
coro_delete(c);
}
printf("Finish main\n");
return 0;
}
$> gcc 12_libcoro_example.c 12_libcoro.c $>./a.out Start main 2: coro is started 1: coro is started 0: coro is started 2: recursive step 0 1: recursive step 0 0: recursive step 0 2: recursive step 1 1: recursive step 1 0: recursive step 1 2: recursive step 2 1: recursive step 2 0: recursive step 2 2: finish recursive step 2 2: finish recursive step 1 2: finish recursive step 0 Finished 2 1: finish recursive step 2 1: finish recursive step 1 1: finish recursive step 0 Finished 1 0: finish recursive step 2 0: finish recursive step 1 0: finish recursive step 0 Finished 0 Finished simple
0: coro line is started 0: coro line next 1: coro line is started Finished 0 1: coro line next 2: coro line is started Finished 1 2: coro line end Finished 2 Finish main
Нет. Почему:
Расширенный обработчик в sigaction принимает ucontext_t прерванного потока. Можно ли использовать это для создания корутин?
Можно ли сделать на сигналах вытесняющую многозадачность?
Для корутин - да, но того не стоит. Невозможно аккуратно обойти проблему прерывания посреди функции, использующей глобальные переменные. Всем таким функциям придется блокировать сигналы на время работы, что сводит "вытеснямость" на нет.
Для потоков - да, но тоже не стоит. Планировщик ядра уже это и так делает.
Обратная связь: goo.gl/forms/TAeraYrqJcil7GDt1
и на портале Техносферы
В следующий раз:
Файловая система. Виртуальная ФС в ядре. Файлы, их типы. I/O операции и их планировщики в ядре. Page cache. Режимы работы с файлом.
By Vladislav Shpilevoy
Прерывания. Аппаратные и программные, их природа, назначение. Обработка прерываний. Сигналы - зачем нужни они, как устроены. Обработка сигналов, особенности, контекст выполнения, longjump, top and bottom halves. /proc/interrupts. signal, sigaction.
Database C developer at Tarantool. Backend C++ developer at VirtualMinds.