Системные вызовы из C, errno и файловый ввод-вывод

⏱ 30 мин · 🧩 заданий: 5

Системный вызов и библиотечная функция

Системный вызов — обращение программы к ядру: процессор переключается в привилегированный режим, и выполняется код ядра. Библиотечная функция — обычный код в пространстве пользователя (в libc), который может вызвать системный вызов, а может и нет.

программа: printf("x=%d\n", x); strlen(s) │ │ libc: форматирование в буфер просто цикл │ (буфер полон / \n) по байтам, ▼ без ядра write(1, buf, n) ── обёртка: номер вызова в rax, │ аргументы в регистрах, инструкция syscall ───────────────── │ ─────────────── граница режимов ─────────────── ядро: sys_write → VFS → драйвер терминала

Посмотреть системные вызовы программы: strace ./prog.

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

Соглашение POSIX: при ошибке вызов возвращает -1 (или NULL, или MAP_FAILED), а причину кладёт в глобальную (точнее, своя у каждого потока) переменную errno.

#include <errno.h>
#include <stdio.h>
#include <string.h>

pid_t pid = fork();
if (pid == -1) {
    perror("fork");                          /* "fork: Resource temporarily unavailable" */
    fprintf(stderr, "%s\n", strerror(errno));
    if (errno == EAGAIN) { /* слишком много процессов */ }
}

Частые коды: ENOENT (нет файла), EACCES (нет прав), EAGAIN (временно недоступно), EINTR (вызов прерван сигналом), ENOMEM, EXDEV (операция между разными файловыми системами — вспомним в уроке про жёсткие ссылки).

Файловый ввод-вывод низкого уровня

#include <fcntl.h>
#include <unistd.h>

int fd = open("out.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) { perror("open"); exit(1); }

const char msg[] = "hello\n";
ssize_t n = write(fd, msg, sizeof(msg) - 1);   /* может записать меньше, чем просили! */

close(fd);
Вызов Назначение
open(path, flags, mode) открыть файл, вернуть файловый дескриптор (маленькое целое)
read(fd, buf, n) прочитать до n байт; 0 — конец файла
write(fd, buf, n) записать до n байт
lseek(fd, off, whence) переместить позицию
close(fd) закрыть
dup2(old, new) сделать new копией old (перенаправления)

Флаги open: O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_TRUNC, O_APPEND, O_EXCL.

Буферизация stdio

printf, fputs, fwrite не вызывают write сразу — они копят данные в буфере внутри процесса:

Куда выводит stdout Режим буфера Когда сбрасывается
терминал построчный на \n
файл или канал полный когда буфер (обычно 4–8 КиБ) заполнен
stderr без буфера сразу

Буфер сбрасывается также при fflush(stdout) и при нормальном завершении через exit() или return из main.

Буфер и fork

fork копирует всю память процесса, включая неотправленный буфер stdio. Если перед fork в буфере что-то лежало, оба процесса это выведут. Поэтому перед fork делают fflush(stdout), а в потомке при аварийном выходе вызывают _exit.

exit и _exit

exit(0);    /* libc: обработчики atexit → сброс буферов stdio → системный вызов _exit */
_exit(0);   /* сразу системный вызов: ядро закрывает дескрипторы, освобождает память */

В задаче 8 потомки выходят через exit(0), а при ошибках используется _exit(1). Потомки ничего не печатают через stdio, поэтому дублирования вывода там нет.

Итог

  • Системный вызов переключает процессор в режим ядра; функции libc — обычный код, часто обёртки над вызовами.
  • Ошибка: результат −1 и код в errno; perror печатает понятный текст.
  • open/read/write/close работают с файловыми дескрипторами; write может записать меньше.
  • stdio буферизует вывод; буфер копируется при fork; exit сбрасывает буферы, _exit — нет.

Практика решено 0 из 5

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Заполнить таблицу

Что из этого — системный вызов (обращение к ядру), а что — библиотечная функция libc, которая сама по себе в ядро не входит?

Системный вызовФункция библиотеки
fork
printf
write
malloc
mmap
strlen
kill
exit
_exit
Отметьте ячейку — «+», пустая — «−».
Задание 2 Написать код

Откройте файл "data.txt" только для чтения. Если open вернул -1, напечатайте ошибку через perror("open") и завершите программу exit(1).

Задание 3 Ввод ответа

Программа выводит в файл (stdout перенаправлен: ./prog > out.txt):

printf("A\n");
fork();
printf("B\n");
return 0;

Сколько строк A окажется в файле?

Задание 4 Один вариант ответа

Почему в потомке после fork при ошибке часто вызывают _exit(1), а не exit(1)?

Задание 5 Несколько вариантов

Что верно про errno?

Войдите, чтобы отмечать пройденные уроки.