Системные вызовы из C, errno и файловый ввод-вывод
Системный вызов и библиотечная функция
Системный вызов — обращение программы к ядру: процессор переключается в привилегированный режим, и выполняется код ядра. Библиотечная функция — обычный код в пространстве пользователя (в libc), который может вызвать системный вызов, а может и нет.
Посмотреть системные вызовы программы: 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
Что из этого — системный вызов (обращение к ядру), а что — библиотечная функция libc, которая сама по себе в ядро не входит?
| Системный вызов | Функция библиотеки | |
|---|---|---|
| fork | ||
| printf | ||
| write | ||
| malloc | ||
| mmap | ||
| strlen | ||
| kill | ||
| exit | ||
| _exit |
Откройте файл "data.txt" только для чтения. Если open вернул -1, напечатайте ошибку через perror("open")
и завершите программу exit(1).
int fd = open("data.txt", O_RDONLY);
if (fd == -1) {
perror("open");
exit(1);
}
Программа выводит в файл (stdout перенаправлен: ./prog > out.txt):
printf("A\n");
fork();
printf("B\n");
return 0;
Сколько строк A окажется в файле?
Почему в потомке после fork при ошибке часто вызывают _exit(1), а не exit(1)?
Что верно про errno?
Войдите, чтобы отмечать пройденные уроки.