Главная страница
Навигация по странице:

  • Таблица 1.1.

  • Межпроцессное взаимодействие

  • Обработка ошибок

  • Таблица 1.2.

  • Добро пожаловать в системное программирование

  • Открытие файлов

  • Системный вызов open()

  • Системное программирование Линукс. Linux. Системное программирование. Вступление


    Скачать 0.65 Mb.
    НазваниеLinux. Системное программирование. Вступление
    АнкорСистемное программирование Линукс
    Дата23.01.2023
    Размер0.65 Mb.
    Формат файлаpdf
    Имя файлаLinuxSystemProgramming.pdf
    ТипКраткое содержание
    #900372
    страница7 из 14
    1   2   3   4   5   6   7   8   9   10   ...   14
    49
    что пользователь может открыть каталог и увидеть список его содержимого. Право записи позволяет добавлять в каталог новые ссылки, а право исполнения разрешает открыть каталог и ввести его название в имя пути. В табл. 1.1 перечислены все девять бит доступа, их восьмеричные значения (распространенный способ представления девяти бит), их текстовые значения (отображаемые командой ls
    ) и их права.
    Таблица 1.1. Биты доступа и их значения
    Бит
    Восьмеричное значение
    Текстовое значение Соответствующие права
    8 400
    r--------
    Владелец может читать
    7 200
    -w-------
    Владелец может записывать
    6 100
    --x------
    Владелец может исполнять
    5 040
    ---r-----
    Члены группы могут читать
    4 020
    ----w----
    Члены группы могут записывать
    3 010
    -----x---
    Члены группы могут исполнять
    2 004
    ------r--
    Любой может читать
    1 002
    -------w-
    Любой может записывать
    0 001
    --------x
    Любой может исполнять
    Кроме исторически сложившихся в UNIX прав доступа, Linux также поддержи­
    вает списки контроля доступа (ACL). Они позволяют предоставлять гораздо более детализированные и точные права, соответственно, более полный контроль над безопасностью. Эти преимущества приобретаются за счет общего усложнения прав доступа и увеличения требуемого для этой информации дискового пространства.
    Сигналы
    Сигналы — это механизм, обеспечивающий односторонние асинхронные уведом­
    ления. Сигнал может быть отправлен от ядра к процессу, от процесса к другому процессу либо от процесса к самому себе. Обычно сигнал сообщает процессу, что произошло какое­либо событие, например возникла ошибка сегментации или пользователь нажал Ctrl+C.
    Ядро Linux реализует около 30 разновидностей сигналов (точное количество зависит от конкретной архитектуры). Каждый сигнал представлен числовой кон­
    стантой и текстовым названием. Например, сигнал
    SIGHUP
    используется, чтобы сооб­
    щить о зависании терминала. В архитектуре x86­64 этот сигнал имеет значение
    1
    Сигналы прерывают исполнение работающего процесса. В результате процесс откладывает любую текущую задачу и немедленно выполняет заранее определен­
    ное действие. Все сигналы, за исключением
    SIGKILL
    (всегда завершает процесс) и
    SIGSTOP
    (всегда останавливает процесс), оставляют процессам возможность вы­
    бора того, что должно произойти после получения конкретного сигнала. Так, про­
    цесс может совершить действие, заданное по умолчанию (в частности, завершение процесса, завершение процесса с созданием дампа, остановка процесса или отсут­
    ствие действия), — в зависимости от полученного сигнала. Кроме того, процессы могут явно выбирать, будут они обрабатывать сигнал или проигнорируют его.
    Концепции программирования в Linux

    50
    Проигнорированные сигналы бесшумно удаляются. Если сигнал решено обрабо­
    тать, выполняется предоставляемая пользователем функция, которая называется
    обработчиком сигнала. Программа переходит к выполнению этой функции, как только получит сигнал. Когда обработчик сигнала возвращается, контроль над программой передается обратно инструкции, работа которой была прервана. Сиг­
    налы являются асинхронными, поэтому обработчики сигналов не должны срывать выполнение прерванного кода. Таким образом, речь идет о выполнении только функций, которые безопасны для выполнения в асинхронной среде, они также на­
    зываются сигналобезопасными.
    Межпроцессное взаимодействие
    Самые важные задачи операционной системы — это обеспечение обмена информаци­
    ей между процессами и предоставление процессам возможности уведомлять друг друга о событиях. Ядро Linux реализует большинство из исторически сложившихся в UNIX механизмов межпроцессного взаимодействия. В частности, речь идет о меха­
    низмах, определенных и стандартизированных как в SystemV, так и в POSIX. Кроме того, в ядре Linux имеется пара собственных механизмов подобного взаимодействия.
    К механизмам межпроцессного взаимодействия, поддерживаемым в Linux, от­
    носятся именованные каналы, семафоры, очереди сообщений, разделяемая память и фьютексы.
    Заголовки
    Системное программирование в Linux всецело зависит от нескольких заголовков.
    И само ядро, и glibc предоставляют заголовки, применяемые при системном про­
    граммировании. К ним относятся стандартный джентльменский набор C (например,

    ) и обычные заголовки UNIX (к примеру,

    ).
    Обработка ошибок
    Само собой разумеется, что проверка наличия ошибок и их обработка — задача пер­
    востепенной важности. В системном программировании ошибка характеризуется возвращаемым значением функции и описывается с помощью специальной пере­
    менной errno glibc явно предоставляет поддержку errno как для библиотечных, так и для системных вызовов. Абсолютное большинство интерфейсов, рассмотренных в данной книге, используют для сообщения об ошибках именно этот механизм.
    Функции уведомляют вызывающую сторону об ошибках посредством специаль­
    ного возвращаемого значения, которое обычно равно
    –1
    (точное значение, использу­
    емое в данном случае, зависит от конкретной функции). Значение ошибки предупре­
    ждает вызывающую сторону, что возникла ошибка, но никак не объясняет, почему она произошла. Переменная errno используется для выяснения причины ошибки.
    Эта переменная объявляется в

    следующим образом:
    extern int errno;
    Глава 1. Введение и основополагающие концепции

    51
    Ее значение является валидным лишь непосредственно после того, как функция, задающая errno
    , указывает на ошибку (обычно это делается путем возвращения
    -1
    ).
    Дело в том, что после успешного выполнения функции значение этой переменной вполне может быть изменено.
    Переменная errno доступна для чтения или записи напрямую; это модифициру­
    емое именуемое выражение. Значение errno соответствует текстовому описанию конкретной ошибки. Препроцессор
    #define также ассоциирует переменную errno с числовым значением. Например, препроцессор определяет
    EACCES
    равным
    1
    и озна чает «доступ запрещен». В табл. 1.2 перечислены стандартные определения и соответствующие им описания ошибок.
    Таблица 1.2. Ошибки и их описание
    Обозначение препроцессора
    Описание
    E2BIG
    Список аргументов слишком велик
    EACCES
    Доступ запрещен
    EAGAIN
    Повторить попытку (ресурс временно недоступен)
    EBADF
    Недопустимый номер файла
    EBUSY
    Заняты ресурс или устройство
    ECHILD
    Процессы-потомки отсутствуют
    EDOM
    Математический аргумент вне области функции
    EEXIST
    Файл уже существует
    EFAULT
    Недопустимый адрес
    EFBIG
    Файл слишком велик
    EINTR
    Системный вызов был прерван
    EINVAL
    Недействительный аргумент
    EIO
    Ошибка ввода-вывода
    EISDIR
    Это каталог
    EMFILE
    Слишком много файлов открыто
    EMLINK
    Слишком много ссылок
    ENFILE
    Переполнение таблицы файлов
    ENODEV
    Такое устройство отсутствует
    ENOENT
    Такой файл или каталог отсутствует
    ENOEXEC
    Ошибка формата исполняемого файла
    ENOMEM
    Недостаточно памяти
    ENOSPC
    Израсходовано все пространство на устройстве
    ENOTDIR
    Это не каталог
    ENOTTY
    Недопустимая операция управления вводом-выводом
    ENXIO
    Такое устройство или адрес не существует
    EPERM
    Операция недопустима
    EPIPE
    Поврежденный конвейер
    ERANGE
    Результат слишком велик
    EROFS
    Файловая система доступна только для чтения
    ESPIPE
    Недействительная операция позиционирования
    ESRCH
    Такой процесс отсутствует
    ETXTBSY
    Текстовый файл занят
    EXDEV
    Неверная ссылка
    Концепции программирования в Linux

    52
    Библиотека C предоставляет набор функций, позволяющих представлять кон­
    кретное значение errno в виде соответствующего ему текстового представления.
    Эта возможность необходима только для отчетности об ошибках и т. п. Проверка наличия ошибок и их обработка может выполняться на основе одних лишь опре­
    делений препроцессора, а также напрямую через errno
    Первая подобная функция такова:
    #include
    void perror (const char *str);
    Эта функция печатает на устройстве stderr
    (standard error — «стандартная ошибка») строковое представление текущей ошибки, взятое из errno
    , добавляя в качестве префикса строку, на которую указывает параметр str
    . Далее следует двоеточие. Для большей информативности имя отказавшей функции следует включать в строку. Например:
    if (close (fd) == –1)
    perror ("close");
    В библиотеке C также предоставляются функции strerror()
    и strerror_r()
    , прототипированные как:
    #include
    char * strerror (int errnum);
    и
    #include
    int strerror_r (int errnum, char *buf, size_t len);
    Первая функция возвращает указатель на строку, описывающую ошибку, вы­
    данную errnum
    . Эта строка не может быть изменена приложением, однако это мож­
    но сделать с помощью последующих вызовов perror()
    и strerror()
    . Соответственно, такая функция не является потокобезопасной.
    Функция strerror_r()
    , напротив, является потокобезопасной. Она заполняет буфер, имеющий длину len
    , на который указывает buf
    . При успешном вызове strerror_r()
    возвращается
    0
    , при сбое —
    –1
    . Забавно, но при ошибке она выдает errno
    Для некоторых функций к допустимым возвращаемым значениям относится вся область возвращаемого типа. В таких случаях перед вызовом функции errno нужно присвоить значение
    0
    , а по завершении проверить ее новое значение (такие функции могут возвратить ненулевое значение errno
    , только когда действительно возникла ошибка). Например:
    errno = 0;
    arg = strtoul (buf, NULL, 0);
    if (errno)
    perror ("strtoul");
    Глава 1. Введение и основополагающие концепции

    53
    При проверке errno мы зачастую забываем, что это значение может быть изме­
    нено любым библиотечным или системным вызовом. Например, в следующем коде есть ошибка:
    if (fsync (fd) == –1) {
    fprintf (stderr, "fsyncfailed!\n");
    if (errno == EIO)
    fprintf (stderr, "I/O error on %d!\n", fd);
    }
    Если необходимо сохранить значение errno между вызовами нескольких функ­
    ций, делаем это:
    if (fsync (fd) == –1) {
    const int err = errno;
    fprintf (stderr, "fsync failed: %s\n", strerror (errno));
    if (err == EIO) {
    /* если ошибка связана с вводом-выводом — уходим */
    fprintf (stderr, "I/O error on %d!\n", fd);
    exit (EXIT_FAILURE);
    }
    }
    В однопоточных программах errno является глобальной переменной, как было показано выше в этом разделе. Однако в многопоточных программах errno сохра­
    няется отдельно в каждом потоке для обеспечения потокобезопасности.
    Добро пожаловать в системное
    программирование
    В этой главе мы рассмотрели основы системного программирования в Linux и сде­
    лали обзор операционной системы Linux с точки зрения программиста. В следу­
    ющей главе мы обсудим основы файлового ввода­вывода, в частности поговорим о чтении файлов и записи информации в них. Многие интерфейсы в Linux реали­
    зованы как файлы, поэтому файловый ввод­вывод имеет большое значение для решения разных задач, а не только для работы с обычными файлами.
    Итак, все общие моменты изучены, настало время приступить к настоящему системному программированию. В путь!
    Добро пожаловать в системное программирование

    55
    Каждый процесс традиционно имеет не менее трех открытых файловых дес­
    крипторов: 0, 1 и 2, если, конечно, процесс явно не закрывает один из них. Файловый дескриптор 0 соответствует стандартному вводу (stdin), дескриптор 1 — стандарт-
    ному выводу (stdout), дескриптор 2 — стандартной ошибке (stderr). Библиоте­
    ка С не ссылается непосредственно на эти целые числа, а предоставляет препроцес­
    сорные определения
    STDIN_FILENO
    ,
    STDOUT_FILENO
    и
    STDERR_FILENO
    для каждого из вышеописанных вариантов соответственно. Как правило, stdin подключен к тер­
    минальному устройству ввода (обычно это пользовательская клавиатура), а stdout и stderr — к дисплею терминала. Пользователи могут переназначать эти стандарт­
    ные файловые дескрипторы и даже направлять по конвейеру вывод одной програм­
    мы во ввод другой. Именно так оболочка реализует переназначения и конвейеры.
    Дескрипторы могут ссылаться не только на обычные файлы, но и на файлы устройств и конвейеры, каталоги и фьютексы, FIFO и сокеты. Это соответствует парадигме «все есть файл». Практически любая информация, которую можно читать или записывать, доступна по файловому дескриптору.
    По умолчанию процесс­потомок получает копию таблицы файлов своего про­
    цесса­предка. Список открытых файлов и режимы доступа к ним, актуальные файловые позиции и другие метаданные не меняются. Однако изменение, связан­
    ное с одним процессом, например закрытие файла процессом­потомком, не затра­
    гивает таблиц файлов других процессов. В гл. 5 будет показано, что такое поведение является типичным, но предок и потомок могут совместно использовать таблицу файлов предка (как потоки обычно и делают).
    Открытие файлов
    Наиболее простой способ доступа к файлу связан с использованием системных вызовов read()
    и write()
    . Однако прежде, чем к файлу можно будет получить доступ, его требуется открыть с помощью системного вызова open()
    или creat()
    . Когда работа с файлом закончена, его нужно закрыть посредством системного вызова close()
    Системный вызов open()
    Открытие файла и получение файлового дескриптора осуществляются с помощью системного вызова open()
    :
    #include
    #include
    #include
    int open (const char *name, int flags);
    int open (const char *name, int flags, mode_t mode);
    Системный вызов open()
    ассоциирует файл, на который указывает имя пути name с файловым дескриптором, возвращаемым в случае успеха. В качестве файловой
    Открытие файлов

    56
    позиции указывается его начало (нуль), и файл открывается для доступа в соответ­
    ствии с заданными флагами (параметр flags
    ).
    Флаги для open(). Аргумент flags
    — это поразрядное ИЛИ, состоящее из од­
    ного или нескольких флагов. Он должен указывать режим доступа, который может иметь одно из следующих значений:
    O_RDONLY
    ,
    O_WRONLY
    или
    O_RDWR
    . Эти аргументы соответственно означают, что файл может быть открыт только для чтения, только для записи или одновременно для того и другого.
    Например, следующий код открывает каталог /home/kidd/madagascar для чтения:
    int fd;
    fd = open ("/home/kidd/madagascar", O_RDONLY);
    if (fd == –1)
    /* ошибка */
    Если файл открыт только для чтения, в него невозможно что­либо записать, и на­
    оборот. Процесс, осуществляющий системный вызов open()
    , должен иметь довольно широкие права, чтобы получить запрашиваемый доступ. Например, если файл до­
    ступен определенному пользователю только для чтения, то процесс, запущенный этим пользователем, может открыть этот файл как
    O_RDONLY
    , но не как
    O_WRONLY
    или
    O_RDWR
    Перед указанием режима доступа задается вариант побитового «ИЛИ» для аргумента flags
    . Вариант описывается одним или несколькими из следующих значений, изменяющих поведение запроса на открытие файла.
    
    O_APPEND
    . Файл будет открыт в режиме дозаписи. Это означает, что перед каждым актом записи файловая позиция будет обновляться и устанавливаться в теку­
    щий конец файла. Это происходит, даже когда другой процесс что­то записал в файл уже после последнего акта записи от процесса, выполнившего вызов
    (таким образом, процесс, осуществивший вызов, уже изменил позицию конца файла).
    
    O_ASYNC
    . Когда указанный файл станет доступным для чтения или записи, гене­
    рируется специальный сигнал (по умолчанию
    SIGIO
    ). Этот флаг может исполь­
    зоваться только при работе с FIFO, каналами, сокетами и терминалами, но не с обычными файлами.
    
    O_CLOEXEC
    . Задает флаг close-on-exec для открытого файла. Как только начнется выполнение нового процесса, этот файл будет автоматически закрыт. Таким образом, очевидна необходимость вызова fcntl()
    для задания флага и исклю­
    чается возможность возникновения условий гонки. Этот флаг доступен лишь в версии ядра Linux 2.6.23 и выше.
    
    O_CREAT
    . Если файл, обозначаемый именем name
    , не существует, то ядро создаст его. Если же файл уже есть, то этот флаг не дает никакого эффекта (кроме слу­
    чаев, в которых также задан
    O_EXCL
    ).
    
    O_DIRECT
    . Файл будет открыт для непосредственного ввода­вывода.
    
    O_DIRECTORY
    . Если файл name не является каталогом, то вызов open()
    не удастся.
    Этот флаг используется внутрисистемно библиотечным вызовом opendir()
    Глава 2. Файловый ввод-вывод

    57
    
    O_EXCL
    . При указании вместе с
    O_CREAT
    этот флаг предотвратит срабатывание вызова open()
    , если файл с именем name уже существует. Данный флаг применя­
    ется для предотвращения условий гонки при создании файлов. Если
    O_CREAT
    не указан, то данный флаг не имеет значения.
    
    O_LARGEFILE
    . Указанный файл будет открыт с использованием 64­битных сме­
    щений. Таким образом, становится возможно манипулировать файлами крупнее
    2 Гбайт. Для таких операций требуется 64­битная архитектура.
    
    O_NOATIME+
    . Последнее значение времени доступа к файлу не обновляется при его чтении. Этот флаг удобно применять при индексировании, резервном копи­
    ровании и использовании других подобных программ, считывающих все файлы в системе. Так предотвращается учет значительной записывающей активности, возникающей при обновлении индексных дескрипторов каждого считываемого файла. Этот флаг доступен лишь в версии ядра Linux 2.6.8 и выше.
    
    O_NOCTTY
    . Если файл с именем name указывает на терминальное устройство (на­
    пример, /dev/tty), это устройство не получает контроля над процессом, даже если в настоящий момент этот процесс не имеет контролирующего устройства.
    Этот флаг используется редко.
    
    O_NOFOLLOW
    . Если name
    — это символьная ссылка, то вызов open()
    окончится ошибкой. Как правило, происходит разрешение ссылки, после чего открывает­
    ся целевой файл. Если остальные компоненты заданного пути также являются ссылками, то вызов все равно удастся. Например, при name
    , равном
    /etc/ship/
    plank.txt
    , вызов не удастся в случае, если plank.txt является символьной ссыл­
    кой. При этом если plank.txt не является символьной ссылкой, а etc или ship являются, то вызов будет выполнен успешно.
    
    O_NONBLOCK
    . Если это возможно, файл будет открыт в неблокирующем режиме.
    Ни вызов open()
    , ни какая­либо иная операция не вызовут блокирования про­
    цесса (перехода в спящий режим) при вводе­выводе. Такое поведение может быть определено лишь для FIFO.
    
    O_SYNC
    . Файл будет открыт для синхронного ввода­вывода. Никакие операции записи не завершатся, пока данные физически не окажутся на диске. Обычные действия по считыванию уже протекают синхронно, поэтому данный флаг никак не влияет на чтение. Стандарт POSIX дополнительно определяет
    O_DSYNC
    и
    O_RSYNC
    , но в Linux эти флаги эквивалентны
    O_SYNC
    
    O_TRUNC
    . Если файл уже существует, является обычным файлом и заданные для него флаги допускают запись, то файл будет усечен до нулевой длины. Флаг
    O_TRUNC
    для FIFO или терминального устройства игнорируется. Использование с файлами других типов не определено. Указание
    O_TRUNC
    с
    O_RDONLY
    также явля­
    ется неопределенным, так как для усечения файла вам требуется разрешение на доступ к нему для записи.
    Например, следующий код открывает для записи файл
    /home/teach/pearl
    . Если файл уже существует, то он будет усечен до нулевой длины. Флаг
    O_CREAT
    не ука­
    зывается, когда файл еще не существует, поэтому вызов не состоится:
    Открытие файлов

    1   2   3   4   5   6   7   8   9   10   ...   14


    написать администратору сайта