* если существуют сигналы, которые нужно маскировать
* при переключении VC. */
sact.flags = 0;
sact.sa_handler = relsig;
sigaction(SIGUSR1, &sact, NULL);
sact.sa_handler = acqsig;
sigaction(SIGUSR2, &sact, NULL);
}
После этого потребуется изменить стандартный режим VC (mode
) с VT_AUTO
на VT_PROGRESS
, пока консоль уведомляется об обработчиках сигналов путем установки relsig
и acqsig
.
void control_vc_switching(int fd) {
struct vt_mode vtmode;
vtmode.mode = VT_PROCESS;
vtmode.waitv = 1;
vtmode.relsig = SIGUSR1;
vtmode.acqsig = SIGUSR2;
vtmode.frsig = 0;
ioctl(fd, VT_SETMODE, &vtmode);
}
Обработчики сигналов, которые вызываются тогда, когда консоль находится в режиме VT_PROCESS
, не должны соглашаться на переключение. Говоря более точно, обработчик relsig
может отклонить разрешение на переключение VC. Обработчик acqsig
, как правило, управляет процессом передачи консоли, но существует вероятность того, что он инициирует переключение на другую консоль. Будьте внимательны при кодировании обработчиков сигналов, чтобы избежать вызова любых нереентерабельных библиотечных функций. POSIX.1 устанавливает, что функции, перечисленные в табл. 12.2, являются реентерабельными. Значит, вы должны считать все остальные функции нереентерабельными, особенно, если хотите написать переносимую программу. Обратите внимание, в частности, на то, что malloc()
и printf()
являются нереентерабельными.
Ниже приведены примеры функций relsig()
и acqsig()
, выполняющих полезную работу. Особо следует отметить, что для функции relsig()
вызов VT_RELDISP
является обязательным, тогда как для acqsig()
вызов VT_RELDISP
рекомендуется только ради переносимости.
void relsig (int signo) {
if (change_vc_ok()) {
/* Разрешено переключение VC */
save_state();
ioctl(fd, VT_RELDISP, 1);
} else {
/* Запрещено переключение VC */
ioctl(fd, VT_RELDISP, 0);
}
}
void acqsig (int signo) {
restore_state();
ioctl(fd, VT_RELDISP, VT_ACKACQ);
}
Теперь вы в состоянии реализовать код функций change_vc_ok()
, save_state()
и restore_state()
.
Динамическое выделение памяти под виртуальные консоли происходит при их открытии, однако, они не удаляются автоматически, когда закрываются. Для того чтобы освободить память ядра, в которой сохранялось состояние VC, нужно вызвать ioctl()
.
ioctl(fd, VT_DISALLOCATE, vtnum);
Вы можете заблокировать и повторно открыть переключение VC с помощью нескольких простых команд управления вводом-выводом:
void disallow_vc_switch(int fd) {
ioctl(fd, VT_LOCKSWITCH, 0);
}
void allow_vc_switch(int fd) {
ioctl(fd, VT_UNLOCKSWITCH, 0);
}
20.6. Пример команды open
Ниже показан пример кода, выполняющий следующие задачи: поиск неиспользуемой VC, запуск на ней оболочки, ожидание завершения оболочки, переключение обратно, а также освобождение памяти, выделенной под VC, по завершении программы. Программа open
, входящая в состав дистрибутива Linux, выполняет те же самые действия, но при этом она содержит больше опций и лучший контроль ошибок, таким образом, является более надежной. Хотя приведенная ниже версия тоже будет работать, ее трудно назвать удобной, надежной или безопасной.
1: /* minopen.c */
2:
3: #include <stdio.h>
4: #include <unistd.h>
5: #include <stdlib.h>
6: #include <signal.h>
7: #include <fcntl.h>
8: #include <sys/ioctl.h>
9: #include <sys/vt.h>
10: #include <sys/stat.h>
11: #include <sys/types.h>
12: #include <sys/wait.h>
13:
14: int main (int argc, const char ** argv) {
15: int vtnum;
16: int vtfd;
17: struct vt_stat vtstat;
18: char device[32];
19: int child;
20:
21: vtfd = open("/dev/tty", O_RDWR, 0);
22: if (vtfd < 0) {
23: perror("minopen: не удается открыть /dev/tty");
24: exit(1);
25: }
26: if (ioctl(vtfd, VT_GETSTATE, &vtstat) < 0) {
27: perror("minopen: tty не является виртуальной консолью");
28: exit(1);
29: }
30: if (ioctl(vtfd, VT_OPENQRY, &vtnum) < 0) {
31: perror("minopen: нет свободных виртуальных консолей");
32: exit(1);
33: }
34: sprintf(device, "/dev/tty%d", vtnum);
35: if (access(device, (W_OK|R_OK)) < 0) {
36: perror("minopen: недостаточные полномочия на tty");
37: exit(1);
38: }
39: child = fork();
40: if (child == 0) {
41: ioctl(vtfd, VT_ACTIVATE, vtnum);
42: ioctl(vtfd, VT_WAITACTIVE, vtnum);
43: setsid();
44: close(0); close(1); close(2);
45: close(vtfd);
46: vtfd = open(device, O_RDWR, 0); dup(vtfd); dup(vtfd);
47: execlp("/bin/bash", "bash", NULL);
48: }
49: wait(&child);
50: ioctl(vtfd, VT_ACTIVATE, vtstat.v_active);
51: ioctl(vtfd, VT_WAITACTIVE, vtstat.v_active);
52: ioctl(vtfd, VT_DISALLOCATE, vtnum);
53: exit(0);
54: }
Глава 21
Консоль Linux
Консоль Linux, как правило, имитирует последовательный терминал. Выводя специальные последовательности символов в компонент консоли, можно управлять всеми аспектами воспроизведения на экране. Для вывода информации на экран обычно применяются S-Lang, curses или ряд других библиотек рисования на экране; они используют упомянутые управляющие последовательности. Консоль можно также читать и модифицировать через альтернативный полноэкранный интерфейс, который особенно полезен для некоторых специализированных программ.
Программисты для DOS, впервые сталкиваясь с программированием для Linux, обнаруживают пугающий их факт. Оказывается, вывод символов на экран — это не просто инициализация указателя на адрес экрана в памяти и последующая запись прямо через него. Некоторые даже жалуются в полный голос на эту "отсталую" систему, которая вынуждает их делать вид, что они выводят информацию в последовательный терминал, вписывая между символами управляющие последовательности. Последние выводятся на экран для управления перемещением курсора, цветом, очисткой экрана и так далее.