При настройке атрибутов терминала через tcsetattr(fd) может ли fd быть либо стандартным выводом, либо стандартным вводом?

Я искал man 3 tcgetattr (так как я хочу изменить настройки терминала в программа) и нашел это.

int tcgetattr(int fd, struct termios *termios_p);

int tcsetattr(int fd, int optional_actions,
              const struct termios *termios_p);

Вопрос:

Я хотел бы знать, что означает fd? (вроде бы stdin, пока не понимаю почему)?

Фон

Насколько я понимаю, терминал вводит и выводит вместе, так как я понимаю, что /dev/tty или /dev/pty дает stdin, stdout и stderr вместе.


person humanityANDpeace    schedule 08.03.2016    source источник
comment
fd означает файловый дескриптор   -  person HoKy22    schedule 08.03.2016
comment
@ HoKy22 спасибо, и обе упомянутые опции для fd , а именно stdin и stdout являются файловыми дескрипторами, поэтому это неясно, и мне нужно спросить   -  person humanityANDpeace    schedule 08.03.2016


Ответы (4)


fd обозначает файловый дескриптор, который является ссылкой на файловый объект ОС. Поскольку это ссылка, несколько разных файловых дескрипторов могут ссылаться на один и тот же файловый объект.

stdin, stdout и stderr являются объектами FILE * -- действительными указателями на структуры данных FILE stdio. Вы можете получить дескриптор файла, который ссылается на базовый объект ОС, с помощью функции fileno.

Таким образом, здесь происходит два уровня косвенности. Все FILE * могут относиться к одному и тому же FILE, но это не так; есть 3 отдельных объекта FILE для stdin, stdout и stderr. Каждый из этих объектов FILE содержит файловый дескриптор, обычно 0, 1 и 2 (я говорю обычно - ОС/библиотека устанавливает их таким образом, и они изменятся только в том случае, если вы явно измените их в своей программе). В этом случае 3 файловых дескриптора обычно ссылаются на один и тот же базовый объект ОС, который является одним терминальным объектом.

Так как (обычно) есть только один терминал, и все эти файловые дескрипторы (обычно) ссылаются на него, не имеет значения, какой fd (0, 1 или 2) вы используете в качестве первого аргумента для tcsetaddr.

Обратите внимание, что эти fd могут ссылаться на разные объекты — если вы запустите программу с перенаправлениями (< или > в оболочке), то один или несколько из них будут ссылаться на какой-то другой файловый объект. а не терминал.

person Chris Dodd    schedule 08.03.2016

Чтобы упростить Thomas Dickey's и Chris Ответы Додда, типичный код для выбора того, какой дескриптор используется для ссылки на терминал:

int ttyfd;

/* Check standard error, output, and input, in that order. */
if (isatty(fileno(stderr)))
    ttyfd = fileno(stderr);
else
if (isatty(fileno(stdout)))
    ttyfd = fileno(stdout);
else
if (isatty(fileno(stdin)))
    ttyfd = fileno(stdin);
else
    ttyfd = -1; /* No terminal; redirecting to/from files. */

Если ваше приложение настаивает на доступе к управляющему терминалу (терминал, который пользователь использовал для выполнения этого процесса), если он есть, вы можете использовать следующую функцию new_terminal_descriptor(). Для простоты я встрою его в пример программы:

#define  _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>

int new_terminal_descriptor(void)
{
    /* Technically, the size of this buffer should be
     *  MAX( L_ctermid + 1, sysconf(_SC_TTY_NAME_MAX) )
     * but 256 is a safe size in practice. */
    char buffer[256], *path;
    int  fd;

    if (isatty(fileno(stderr)))
        if (!ttyname_r(fileno(stderr), buffer, sizeof buffer)) {
            do {
                fd = open(path, O_RDWR | O_NOCTTY);
            } while (fd == -1 && errno == EINTR);
            if (fd != -1)
                return fd;
        }

    if (isatty(fileno(stdout)))
        if (!ttyname_r(fileno(stdout), buffer, sizeof buffer)) {
            do {
                fd = open(path, O_RDWR | O_NOCTTY);
            } while (fd == -1 && errno == EINTR);
            if (fd != -1)
                return fd;
        }

    if (isatty(fileno(stdin)))
        if (!ttyname_r(fileno(stdin), buffer, sizeof buffer)) {
            do {
                fd = open(path, O_RDWR | O_NOCTTY);
            } while (fd == -1 && errno == EINTR);
            if (fd != -1)
                return fd;
        }

    buffer[0] = '\0';
    path = ctermid(buffer);
    if (path && *path) {
        do {
            fd = open(path, O_RDWR | O_NOCTTY);
        } while (fd == -1 && errno == EINTR);
        if (fd != -1)
            return fd;
    }

    /* No terminal. */
    errno = ENOTTY;
    return -1;
}

static void wrstr(const int fd, const char *const msg)
{
    const char       *p = msg;
    const char *const q = msg + ((msg) ? strlen(msg) : 0);    
    while (p < q) {
        ssize_t n = write(fd, p, (size_t)(q - p));
        if (n > (ssize_t)0)
            p += n;
        else
        if (n != (ssize_t)-1)
            return;
        else
        if (errno != EINTR)
            return;
    }
}

int main(void)
{
    int ttyfd;

    ttyfd = new_terminal_descriptor();
    if (ttyfd == -1)
        return EXIT_FAILURE;

    /* Let's close the standard streams,
     * just to show we're not using them
     * for anything anymore. */
    fclose(stdin);
    fclose(stdout);
    fclose(stderr);

    /* Print a hello message directly to the terminal. */
    wrstr(ttyfd, "\033[1;32mHello!\033[0m\n");

    return EXIT_SUCCESS;
}

Функция wrstr() — это просто вспомогательная функция, которая сразу же записывает указанную строку в указанный файловый дескриптор без буферизации. Строка содержит цветовые коды ANSI, так что в случае успеха она выведет на терминал светло-зеленый Hello!, даже если стандартные потоки были закрыты.

Если вы сохраните вышеуказанное как example.c, вы можете скомпилировать его, используя, например.

gcc -Wall -Wextra -O2 example.c -o example

и запустить с помощью

./example

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

./example </dev/null >/dev/null 2>/dev/null

Наконец, если вам интересно, ничто из этого не является «особенным». Я не говорю о консольном терминале, который представляет собой текстовый консольный интерфейс, который многие дистрибутивы Linux предоставляют в качестве альтернативы графической среде, и единственный локальный интерфейс, предоставляемый большинством серверов Linux. Все вышеперечисленное использует только обычные псевдотерминальные интерфейсы POSIX и будет отлично работать, например. xterm или любого другого обычного эмулятора терминала (или консоли Linux) во всех системах POSIXy — Linux, Mac OS X и вариантах BSD.

person Nominal Animal    schedule 10.03.2016
comment
понравилось читать ваш информативный ответ и его код, спасибо. Я не понимаю, почему wrstr() и его write() не вызывают буферизацию системы. Я считаю, что это действительно полностью зависит от настроек этого терминального устройства, то есть установка флага ICANON в struct termios.c_lflag должна сделать ввод доступным построчно.. Что мне не хватает, какая часть вашего хорошего кода установила это? - person humanityANDpeace; 11.03.2016
comment
@humanityANDpeace: Вы ничего не упустили, я просто плохо выразился. Я имел в виду буферизацию, которую, например, стандартный ввод-вывод C выполняет для stdout по умолчанию. По сути, wrstr() просто немедленно передает строку ядру — так же, как fputs(str, stdout); fflush(stdout);. - person Nominal Animal; 11.03.2016

Соглашаясь с @chris-dodd, что файловые дескрипторы, соответствующие потокам stdin, stdout и stderr, обычно относятся к одному и тому же терминалу, некоторые моменты нужно для исходного вопроса:

  • параметр fd (дескриптор файла) для tcgetattr и tcsetattr должны быть для терминала< /эм>.
  • вы можете получить это для потока, используя fileno, например, fileno(stdin).
  • POSIX определяет константы для назначения файловых дескрипторов по умолчанию stdin, stdout и stderr как STDIN_FILENO, STDOUT_FILENO и STDERR_FILENO. Однако можно повторно открыть любой из потоков (или использовать dup или dup2) и измените фактический дескриптор файла.
  • в то время как вы можете получить дескриптор файла для потока, если вы делаете что-то интересное с атрибутами терминала, что может помешать буферизации, используемой для потоков. Если вам нужно смешать эти два параметра (дескриптор файла и поток), внесите изменения в атрибуты терминала перед чтением или записью потока.
  • вы также можете получить дескриптор файла, используя open на терминальном устройстве. Это полезно, если потоки перенаправляются, а ваше приложение должно работать с терминалом. Запросы пароля делают это.
  • терминальное устройство можно прочитать из программы tty (даже если < em>stdin и т. д. перенаправляются).
  • программы могут проверять дескриптор файла, используя isatty чтобы убедиться, что это терминал. Если поток перенаправляется в файл или канал, это не терминал.

Дальнейшее чтение:

person Thomas Dickey    schedule 09.03.2016

Путем эксперимента я нашел для себя следующий ответ:

Каждую из троек stderr,stdout,stdin можно было использовать для изменения настроек терминала с помощью функции tcsetattr(fd....). После внесения изменения чтение tcgsetattr(stdin....), tcgsetattr(stdout....), а также tcgsetattr(sterr....) вернуло то же содержимое в struct termios.h, которое можно было проверить через memcmp(&termios_stdin,&termios_stdout,sizeof(struct termios)) == 0

также, возможно, на странице руководства несколько косвенно указано это

tcgetattr() получает параметры, связанные с объектом, на который ссылается fd, и сохраняет их в структуре termios, на которую ссылается termios_p. Эта функция может быть вызвана из фонового процесса; однако атрибуты терминала могут быть впоследствии изменены приоритетным процессом.

около fd, следовательно, объект, на который ссылается fd, всегда является одним и тем же терминалом

person humanityANDpeace    schedule 08.03.2016