Про системные вызовы уже много было сказано, например здесь или здесь. Наверняка вам уже известно, что системный вызов — это способ вызова функции ядра ОС. Мне же захотелось копнуть глубже и узнать, что особенного в этом системном вызове, какие существуют реализации и какова их производительность на примере архитектуры x86-64. Если вам также интересны ответы на данные вопросы, добро пожаловать под кат.
System call
Каждый раз, когда мы хотим что-то отобразить на мониторе, записать в устройство, считать с файла, нам приходится обращаться к ядру ОС. Именно ядро ОС отвечает за любое общение с железом, именно там происходит работа с прерываниями, режимами процессора, переключениями задач… Чтобы пользователь программой не смог завалить работу всей операционной системы, было решено разделить пространство памяти на пространство пользователя (область памяти, предназначенная для выполнения пользовательских программ) и пространство ядра, а также запретить пользователю доступ к памяти ядра ОС. Реализовано это разделение в x86-семействе аппаратно при помощи сегментной защиты памяти. Но пользовательской программе нужно каким-то образом общаться с ядром, для этого и была придумана концепция системных вызовов.
Системный вызов — способ обращения программы пользовательского пространства к пространству ядра. Со стороны это может выглядеть как вызов обычной функции со своим собственным calling convention, но на самом деле процессором выполняется чуть больше действий, чем при вызове функции инструкцией call. Например, в архитектуре x86 во время системного вызова как минимум происходит увеличение уровня привилегий, замена пользовательских сегментов на сегменты ядра и установка регистра IP на обработчик системного вызова.
Программист обычно не работает с системными вызовами напрямую, так как системные вызовы обернуты в функции и скрыты в различных библиотеках, например libc.so в Linux или же ntdll.dll в Windows, с которыми и взаимодействует прикладной разработчик.
Теоретически, реализовать системный вызов можно при помощи любого исключения, хоть при помощи деления на 0. Главное — это передача управления ядру. Рассмотрим реальные примеры реализаций исключений.
Способы реализации системных вызовов
Выполнение неверной инструкции.
Ранее, ещё на 80386 это был самый быстрый способ сделать системный вызов. Для этого обычно применялась бессмысленная и неверная инструкция LOCK NOP, после исполнения которой процессором вызывался обработчик неверной инструкции. Это было больше 20 лет назад и, говорят, этим приёмом обрабатывались системные вызовы в корпорации Microsoft. Обработчик неверной инструкции в наши дни используется по назначению.
Call gates
Для того, чтобы иметь доступ к сегментам кода с различным уровнем привилегий, в Intel был разработан специальный набор дескрипторов, называемый gate descriptors. Существует 4 вида таких дескрипторов:
- Call gates
- Trap gates (для исключений, вроде int 3, требующих выполнения участка кода)
- Interrupt gates (аналогичен trap gates, но с некоторыми отличиями)
- Task gates (полагалось, что будут использоваться для переключения задач)
Нам интересны только call gates, так как именно через них планировалось реализовывать системные вызовы в x86.
Call gate реализован при помощи инструкции call far или jmp far и принимает в качестве параметра call gate-дескриптор, который настраивается ядром ОС. Является достаточно гибким механизмом, так как возможен переход и на любой уровень защитного кольца, и на 16-битный код. Считается, что call gates производительней прерываний. Этот способ использовался в OS/2 и Windows 95. Из-за неудобства использования в Linux механизм так и не был реализован. Со временем совсем перестал использоваться, так как появились более производительные и простые в обращении реализации системных вызовов (sysenter/sysexit).
Системные вызовы, реализованные в Linux
В архитектуре x86-64 ОС Linux существует несколько различных способов системных вызовов:
- int 80h
- sysenter/sysexit
- syscall/sysret
- vsyscall
- vDSO
В реализации каждого системного вызова есть свои особенности, но в общем, обработчик в Linux имеет примерно одинаковую структуру:
- Включается защита от чтения/записи/исполнения кода пользовательского пространства.
- Заменяется пользовательский стек на стек ядра, сохраняются callee-saved регистры.
- Выполняется обработка системного вызова
- Восстановление стека, регистров
- Отключение защиты
- Выход из системного вызова
Рассмотрим немного подробнее каждый системный вызов.
int 80h
Изначально, в архитектуре x86, Linux использовал программное прерывание 128 для совершения системного вызова. Для указания номера системного вызова, пользователь задаёт в eax номер системного вызова, а его параметры располагает по порядку в регистрах ebx, ecx, edx, esi, edi, ebp. Далее вызывается инструкция int 80h, которая программно вызывает прерывание. Процессором вызывается обработчик прерывания, установленный ядром Linux ещё во время инициализации ядра. В x86-64 вызов прерывания используется только во время эмуляции режима x32 для обратной совместимости.
В принципе, никто не запрещает пользоваться инструкцией в расширенном режиме. Но вы должны понимать, что используется 32-битная таблица вызовов и все используемые адреса должны помещаться в 32-битное адресное пространство. Согласно SYSTEM V ABI [4] §3.5.1, для программ, виртуальный адрес которых известен на этапе линковки и помещается в 2гб, по умолчанию используется малая модель памяти и все известные символы находятся в 32-битном адресном пространстве. Под это определение подходят статически скомпилированные программы, где и возможно использовать int 80h. Пошаговая работа прерывания подробно описана на stackoverflow.
В ядре обработчиком этого прерывания является функция entry_INT80_compat и находится в arch/x86/entry/entry_64_compat.S
section .text
global _start
_start:
mov edx,len
mov ecx,msg
mov ebx,1 ; file descriptor (stdout)
mov eax,4 ; system call number (sys_write)
int 0x80 ; call kernel
mov eax,1 ; system call number (sys_exit)
int 0x80 ; call kernel
section .data
msg db 'Hello, world!',0xa
len equ $ - msg
Компиляция:
nasm -f elf main.s -o main32.o
ld -melf_i386 main32.o -o a32.out
Или в расширенном режиме (программа работает так как компилируется статически)
nasm -f elf64 main.s -o main.o
ld main.o -o a.out
sysenter/sysexit
Спустя некоторое время, ещё когда не было x86-64, в Intel поняли, что можно ускорить системные вызовы, если создать специальную инструкцию системного вызова, тем самым минуя некоторые издержки прерывания. Так появилась пара инструкций sysenter/sysexit. Ускорение достигается за счёт того, что на аппаратном уровне при выполнении инструкции sysenter опускается множество проверок на валидность дескрипторов, а так же проверок, зависящих от уровня привилегий [3] §6.1. Также инструкция опирается на то, что вызывающая её программа использует плоскую модель памяти. В архитектуре Intel, инструкция валидна как для режима совместимости, так и для расширенного режима, но у AMD данная инструкция в расширенном режиме приводит к исключению неизвестного опкода [3]. Поэтому в настоящее время пара sysenter/sysexit используется только в режиме совместимости.
В ядре обработчиком этой инструкции является функция entry_SYSENTER_compat и находится в arch/x86/entry/entry_64_compat.S
section .text
global _start
_start:
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
push continue_l
push ecx
push edx
push ebp
mov ebp,esp
sysenter
hlt ; dumb instructions that is going to be skipped
continue_l:
mov eax,1 ;system call number (sys_exit)
mov ebx,0
push ecx
push edx
push ebp
mov ebp,esp
sysenter
section .data
msg db 'Hello, world!',0xa
len equ $ - msg
Компилирование:
nasm -f elf main.s -o main.o
ld main.o -melf_i386 -o a.out
Несмотря на то, что в реализации архитектуры от Intel инструкция валидна, в расширенном режиме скорее всего такой системный вызов никак не получится использовать. Это из-за того, что в регистре ebp сохраняется текущее значение стека, а адрес верхушки независимо от модели памяти находится вне 32-битного адресного пространства. Это всё потому, что Linux отображает стек на конец нижней половины каноничного адреса пространства.
Разработчики ядра Linux предостерегают пользователей от жесткого программирования sysenter из-за того, что ABI системного вызова может измениться. Из-за того, что Android не последовал этому совету, Linux пришлось откатить свой патч для сохранения обратной совместимости. Правильно реализовывать системный вызов нужно используя vDSO, речь о которой будет идти далее.
syscall/sysret
Так как именно AMD разработали x86-64 архитектуру, которая и называется AMD64, то они решили создать свой собственный системный вызов. Инструкция разрабатывалась AMD, как аналог sysenter/sysexit для архитектуры IA-32. В AMD позаботились о том, чтобы инструкция была реализована как в расширенном режиме, так и в режиме совместимости, но в Intel решили не поддерживать данную инструкцию в режиме совместимости. Несмотря на всё это, Linux имеет 2 обработчика для каждого из режимов: для x32 и x64. Обработчиками этой инструкции является функции entry_SYSCALL_64 для x64 и entry_SYSCALL_compat для x32 и находится в arch/x86/entry/entry_64.S и arch/x86/entry/entry_64_compat.S соответственно.
Кому интересно более подробно ознакомиться с инструкциями системных вызовов, в мануале Intel [0] (§4.3) приведён их псевдокод.
section .text
global _start
_start:
mov rdx,len ;message length
mov rsi,msg ;message to write
mov rdi,1 ;file descriptor (stdout)
mov rax,1 ;system call number (sys_write)
syscall
mov rax,60 ;system call number (sys_exit)
syscall
section .data
msg db 'Hello, world!',0xa
len equ $ - msg
Компилирование
nasm -f elf64 main.s -o main.o
ld main.o -o a.out
Для тестирования следующего примера потребуется ядро с конфигурацией CONFIG_IA32_EMULATION=y и компьютер AMD. Если же у вас компьютер фирмы Intel, то можно запустить пример на виртуалке. Linux может без предупреждения изменить ABI и этого системного вызова, поэтому в очередной раз напомню: системные вызовы в режиме совместимости правильнее исполнять через vDSO.
section .text
global _start
_start:
mov edx,len ;message length
mov ebp,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
push continue_l
push ecx
push edx
push ebp
syscall
hlt
continue_l:
mov eax,1 ;system call number (sys_exit)
mov ebx,0
push ecx
push edx
push ebp
syscall
section .data
msg db 'Hello, world!',0xa
len equ $ - msg
Компиляция:
nasm -f elf main.s -o main.o
ld main.o -melf_i386 -o a.out
Непонятна причина, по которой AMD решили разработать свою инструкцию вместо того, чтобы расширить инструкцию Intel sysenter на архитектуру x86-64.
vsyscall
При переходе из пространства пользователя в пространство ядра происходит переключение контекста, что является не самой дешёвой операцией. Поэтому, для улучшения производительности системных вызовов, было решено их обрабатывать в пространстве пользователя. Для этого было зарезервировано 8 мб памяти для отображения пространства ядра в пространство пользователя. В эту память для архитектуры x86 поместили 3 реализации часто используемых read-only вызова: gettimeofday, time, getcpu.
Со временем стало понятно, что vsyscall имеет существенные недостатки. Фиксированное размещение в адресном пространстве является уязвимым местом с точки зрения безопасности, а отсутствие гибкости в размере выделяемой памяти может негативно сказаться на расширении отображаемой области ядра.
Для того, чтобы пример работал, необходимо, чтобы в ядре была включена поддержка vsyscall: CONFIG_X86_VSYSCALL_EMULATION=y
#include <sys/time.h>
#include <stdio.h>
#define VSYSCALL_ADDR 0xffffffffff600000UL
int main()
{
// Offsets in x86-64
// 0: gettimeofday
// 1024: time
// 2048: getcpu
int (*f)(struct timeval *, struct timezone *);
struct timeval tm;
unsigned long addrOffset = 0;
f = (void*)VSYSCALL_ADDR + addrOffset;
f(&tm, NULL);
printf("%d:%d\n", tm.tv_sec, tm.tv_usec);
}
Компиляция:
gcc main.c
Linux не отображает vsyscall в режиме совместимости.
На данный момент, для сохранения обратной совместимости, ядро Linux предоставляет эмуляцию vsyscall. Эмуляция сделана для того, чтобы залатать дыры безопасности в ущерб производительности.
Эмуляция может быть реализована двумя способами.
Первый способ — при помощи замены адреса функции на системный вызов syscall. В таком случае виртуальный системный вызов функции gettimeofday на x86-64 выглядит следующим образом:
movq $0x60, %rax
syscall
ret
Где 0x60 — код системного вызова функции gettimeofday.
Второй же способ немного интереснее. При вызове функции vsyscall генерируется исключение Page fault, которое обрабатывается Linux. ОС видит, что ошибка произошла из-за исполнения инструкции по адресу vsyscall и передаёт управление обработчику виртуальных системных вызовов emulate_vsyscall (arch/x86/entry/vsyscall/vsyscall_64.c).
Реализацией vsyscall можно управлять при помощи параметра ядра vsyscall. Можно как отключить виртуальный системный вызов при помощи параметра vsyscall=none
, задать реализацию как при помощи инструкции syscall syscall=native
, так и через Page fault vsyscall=emulate
.
vDSO (Virtual Dynamic Shared Object)
Чтобы исправить основной недостаток vsyscall, было предложено реализовать системные вызовы в виде отображения динамически подключаемой библиотеки, к которой применяется технология ASLR. В "длинном" режиме библиотека называется linux-vdso.so.1, а в режиме совместимости — linux-gate.so.1. Библиотека автоматически подгружается для каждого процесса, даже статически скомпилированного. Увидеть зависимости приложения от неё можно при помощи утилиты ldd
в случае динамической компоновки библиотеки libc.
Также vDSO используется в качестве выбора наиболее производительного способа системного вызова, например в режиме совместимости.
Список разделяемых функций можно посмотреть в руководстве.
#include <sys/time.h>
#include <dlfcn.h>
#include <stdio.h>
#include <assert.h>
#if defined __x86_64__
#define VDSO_NAME "linux-vdso.so.1"
#else
#define VDSO_NAME "linux-gate.so.1"
#endif
int main()
{
int (*f)(struct timeval *, struct timezone *);
struct timeval tm = {0};
void *vdso = dlopen(VDSO_NAME,
RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
assert(vdso && "vdso not found");
f = dlsym(vdso, "__vdso_gettimeofday");
assert(f);
f(&tm, NULL);
printf("%d:%d\n", tm.tv_sec, tm.tv_usec);
}
Компиляция:
gcc -ldl main.c
Для режима совместимости:
gcc -ldl -m32 main.c -o a32.elf
Правильнее всего искать функции vDSO при помощи извлечения адреса библиотеки из вспомогательного вектора AT_SYSINFO_EHDR и последующего парсинга разделяемого объекта. Пример парсинга vDSO из вспомогательного вектора можно найти в исходном коде ядра: tools/testing/selftests/vDSO/parse_vdso.c
Или если интересно, то можно покопаться и посмотреть, как парсится vDSO в glibc:
- Парсинг вспомогательных векторов: elf/dl-sysdep.c
- Парсинг разделяемой библиотеки: elf/setup-vdso.h
- Установка значений функций: sysdeps/unix/sysv/linux/x86_64/init-first.c, sysdeps/unix/sysv/linux/x86/gettimeofday.c, sysdeps/unix/sysv/linux/x86/time.c
Согласно System V ABI AMD64 [4] вызовы должны происходить при помощи инструкции syscall. На практике же к этой инструкции добавляются вызовы через vDSO. Поддержка системных вызовов в виде int 80h и vsyscall остались для обратной совместимости.
Сравнение производительности системных вызовов
С тестированием скорости системных вызовов всё неоднозначно. В архитектуре x86 на выполнение одной инструкции влияет множество факторов таких как наличие инструкции в кэше, загруженность конвейера, даже существует таблица задержек для данной архитектуры [2]. Поэтому достаточно сложно определить скорость выполнения участка кода. У Intel есть даже специальный гайд по замеру времени для участка кода [1]. Но проблема в том, что мы не можем замерить время согласно документу из-за того, что нам нужно вызывать объекты ядра из пользовательского пространства.
Поэтому было решено замерить время при помощи clock_gettime и тестировать производительность вызова gettimeofday, так как он есть во всех реализациях системных вызовов. На разных процессорах время может отличаться, но в целом, относительные результаты должны быть схожи.
Программа запускалась несколько раз и в итоге бралось минимальное время исполнения.
Тестирование int 80h, sysenter и vDSO-32 производилось в режиме совместимости.
#include <sys/time.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <syscall.h>
#include <dlfcn.h>
#include <limits.h>
#define min(a,b) ((a) < (b)) ? (a) : (b)
#define GIGA 1000000000
#define difftime(start, end) (end.tv_sec - start.tv_sec) * GIGA + end.tv_nsec - start.tv_nsec
static struct timeval g_timespec;
#if defined __x86_64__
static inline int test_syscall() {
register long int result asm ("rax");
asm volatile (
"lea %[p0], %%rdi \n\t"
"mov $0, %%rsi \n\t"
"mov %[sysnum], %%rax \n\t"
"syscall \n\t"
: "=r"(result)
: [sysnum] "i" (SYS_gettimeofday), [p0] "m" (g_timespec)
: "rcx", "rsi");
return result;
}
#endif
static inline int test_int80h() {
register int result asm ("eax");
asm volatile (
"lea %[p0], %%ebx \n\t"
"mov $0, %%ecx \n\t"
"mov %[sysnum], %%eax \n\t"
"int $0x80 \n\t"
: "=r"(result)
: [sysnum] "i" (SYS_gettimeofday), [p0] "m" (g_timespec)
: "ebx", "ecx");
return result;
}
int (*g_f)(struct timeval *, struct timezone *);
static void prepare_vdso() {
void *vdso = dlopen("linux-vdso.so.1",
RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
if (!vdso) {
vdso = dlopen("linux-gate.so.1",
RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
}
assert(vdso && "vdso not found");
g_f = dlsym(vdso, "__vdso_gettimeofday");
}
static int test_g_f() {
return g_f(&g_timespec, 0);
}
#define VSYSCALL_ADDR 0xffffffffff600000UL
static void prepare_vsyscall() {
g_f = (void*)VSYSCALL_ADDR;
}
static inline int test_sysenter() {
register int result asm ("eax");
asm volatile (
"lea %[p0], %%ebx \n\t"
"mov $0, %%ecx \n\t"
"mov %[sysnum], %%eax \n\t"
"push $cont_label%=\n\t"
"push %%ecx \n\t"
"push %%edx \n\t"
"push %%ebp \n\t"
"mov %%esp, %%ebp \n\t"
"sysenter \n\t"
"cont_label%=: \n\t"
: "=r"(result)
: [sysnum] "i" (SYS_gettimeofday), [p0] "m" (g_timespec)
: "ebx", "esp");
return result;
}
#ifdef TEST_SYSCALL
#define TEST_PREPARE()
#define TEST_PROC_CALL() test_syscall()
#elif defined TEST_VDSO
#define TEST_PREPARE() prepare_vdso()
#define TEST_PROC_CALL() test_g_f()
#elif defined TEST_VSYSCALL
#define TEST_PREPARE() prepare_vsyscall()
#define TEST_PROC_CALL() test_g_f()
#elif defined TEST_INT80H
#define TEST_PREPARE()
#define TEST_PROC_CALL() test_int80h()
#elif defined TEST_SYSENTER
#define TEST_PREPARE()
#define TEST_PROC_CALL() test_sysenter()
#else
#error Choose test
#endif
static inline unsigned long test() {
unsigned long result = ULONG_MAX;
struct timespec start = {0}, end = {0};
int rt, rt2, rt3;
for (int i = 0; i < 1000; ++i) {
rt = clock_gettime(CLOCK_MONOTONIC, &start);
rt3 = TEST_PROC_CALL();
rt2 = clock_gettime(CLOCK_MONOTONIC, &end);
assert(rt == 0);
assert(rt2 == 0);
assert(rt3 == 0);
result = min(difftime(start, end), result);
}
return result;
}
int main() {
TEST_PREPARE();
// prepare calls
int a = TEST_PROC_CALL();
assert(a == 0);
a = TEST_PROC_CALL();
assert(a == 0);
a = TEST_PROC_CALL();
assert(a == 0);
unsigned long result = test();
printf("%lu\n", result);
}
Компиляция:
gcc -O2 -DTEST_SYSCALL time_test.c -o test_syscall
gcc -O2 -DTEST_VDSO -ldl time_test.c -o test_vdso
gcc -O2 -DTEST_VSYSCALL time_test.c -o test_vsyscall
#m32
gcc -O2 -DTEST_VDSO -ldl -m32 time_test.c -o test_vdso_32
gcc -O2 -DTEST_INT80H -m32 time_test.c -o test_int80
gcc -O2 -DTEST_SYSENTER -m32 time_test.c -o test_sysenter
О системе
cat /proc/cpuinfo | grep "model name" -m 1
— Intel® Core(TM) i7-5500U CPU @ 2.40GHz
uname -r
— 4.14.13-1-ARCH
Таблица Результатов
Реализация | время (нс) |
---|---|
int 80h | 498 |
sysenter | 338 |
syscall | 278 |
vsyscall emulate | 692 |
vsyscall native | 278 |
vDSO | 37 |
vDSO-32 | 51 |
Как можно увидеть, каждая новая реализация системного вызова является производительней предыдущей, не считая vsysvall, так как это эмуляция. Как вы наверное уже догадались, если бы vsyscall был таким, каким его задумывали, время вызова было бы аналогично vDSO.
Все текущие сравнения производительности были произведены с патчем KPTI, исправляющим уязвимость meltdown.
Бонус: Производительность системных вызовов без KPTI
Патч KPTI был разработан специально для исправления уязвимости meltdown. Как известно, данный патч замедляет производительность ОС. Проверим производительность с выключенным KPTI (pti=off).
Таблица результатов с выключенным патчем
Реализация | Время (нс) | Увеличение времени исполнения после патча (нс) | Ухудшение производительности после патча (t1 - t0) / t0 * 100% |
---|---|---|---|
int 80h | 317 | 181 | 57% |
sysenter | 150 | 188 | 125% |
syscall | 103 | 175 | 170% |
vsyscall emulate | 496 | 196 | 40% |
vsyscall native | 103 | 175 | 170% |
vDSO | 37 | 0 | 0% |
vDSO-32 | 51 | 0 | 0% |
Переход в режим ядра и обратно в среднем после патча стал занимать примерно на 180 нс. больше времени, видимо это и есть цена сброса TLB-кэша.
Производительность системного вызова через vDSO не ухудшилась по причине того, то в данном типе вызова нет перехода в режим ядра, и, следовательно, нет причин сбрасывать TLB-кэш.
Для дальнейшего чтения
Реализация виртуальных системных вызовов в ядре Linux (очень хорошая книга, советую): https://0xax.gitbooks.io/linux-insides/content/SysCall/syscall-3.html
История развития Linux: https://www.win.tue.nl/~aeb/linux/lk/lk-4.html
Анатомия системных вызовов, часть 1: https://lwn.net/Articles/604287/
Анатомия системных вызовов, часть 2: https://lwn.net/Articles/604515/
Ссылки
[0] Intel 64 and IA-32 Architectures Developer's Manual: Vol. 2B
[1] How to benchmark code execution times ...
[2] Instruction latencies and throughput for AMD and Intel x86 processors
[3] AMD64 Architecture Programmer’s Manual Volume 2: System Programming
[4] System V ABI AMD64
Комментарии (23)
masterspline
27.01.2018 15:43Отличнейшая статья. Заодно по таблице замеров времени понятно, что современные системные вызовы (переключение контекста) далеко не такие тяжелые, как про них любят говорить.
powerman
28.01.2018 04:16Они не тяжёлые ровно до того момента, пока не возникает ситуация что нужно держать миллион сокетов и активно делать в них I/O.
firk
27.01.2018 15:56+1vdso — это не системные вызовы, а просто некоторая стандартная библиотека, в которую, судя по вашему описанию, вынесено то из системных вызовов, что не требует быть системным вызовом для работы (т.е. не требует ядерных привилегий). Отличие системного вызова от системно-бибилиотечной функции (того же glibc) в первую очередь как раз в том, что первый выполняется с повышенными правами.
atrosinenko
27.01.2018 21:23не требует быть системным вызовом для работы (т.е. не требует ядерных привилегий)
Но тогда, насколько я понял, (стабильный?) интерфейс программа-ядро начнёт состоять не только из "вызовов функций", но и из read-only страниц памяти. vDSO, случайно, не лежит внутри конкретного скомпилированного ядра? У меня сложилось ощущение, что это такой способ не выдумывать стабильного интерфейса на содержимое этих страниц (о совместимости которого потом придётся задумываться), а выдать более понятные "геттеры" с интерфейсом обычной функции, которые хоть формально и не требуют ядерных привилегий, но жутко implementation-specific.
MacIn
27.01.2018 22:13Получается, этот то же принцип, что используется в NT — слои kernel32, ntdll, которые отрабатывают то, что можно в user space и идут в ядро только когда иначе никак + доступные TEB, PEB.
robert_ayrapetyan
27.01.2018 18:47+1А сам clock_gettime до и после теста у вас какой метод переключения использует?
atrosinenko
27.01.2018 21:39Реализовано это разделение в x86-семействе аппаратно при помощи сегментной защиты памяти.
Говорят, вместо неё уже давно обычно используются страничная защита. Например, в Википедии про AMD64 написано:
Segmented addressing has long been considered an obsolete mode of operation, and all current PC operating systems in effect bypass it, setting all segments to a base address of zero and (in their 32 bit implementation) a size of 4 GB. AMD was the first x86-family vendor to implement no-execute in linear addressing mode. The feature is also available in legacy mode on AMD64 processors, and recent Intel x86 processors, when PAE is used.
MacIn
27.01.2018 22:15Все верно, но в «старом» режиме, чистом IA32 вы можете использовать сегментацию без страничной адресации. Т.е. началось все с защиты сегментов, а уже потом…
kalininmr
28.01.2018 12:41> Каждый раз, когда мы хотим что-то отобразить на мониторе, записать в устройство, считать с файла, нам приходится обращаться к ядру ОС.
начинаю ностальгировать аж :)
вот в наше время — записал в память — вывел на экран. с устройством — с портами пообщался или тоже с памятью. ну прерывания там ещё…
старое доброе время :)
понятно, конечно, что такое это неподходит для многозадачности, не защищает память процессов и т.д.geher
28.01.2018 15:00+1начинаю ностальгировать аж :)
вот в наше время — записал в память — вывел на экран. с устройством — с портами пообщался или тоже с памятью. ну прерывания там ещё…
старое доброе время :)< :) >
А чего ностальгировать-то? Добро пожаловать в программирование простых микроконтроллеров.
Там до сих пор так. Никаких ОС. Прямой доступ к оборудованию. Только с дисплеем не так просто, приходится подавать на дисплей команды, формируя на его ногах нужные комбинации сигналов.
< /:) >
maisvendoo
Плюсище за статью. Когда-то сам этим интереслвался, проводя дьявольские эксперименты phantomexos.blogspot.co.ke/2013/08/phantomex_8411.html?m=1
vire
Ваш учебник по созданию ОСи — лучшее что я видел на русском языке, да и на инглише такого не найти. Самостоятельно въехать в тему osdev — очень не просто, затер ваш сайт до дыр — теперь у меня есть MyOS. Огромнейшее спасибо!
LexXL
с нескучными обоями?
vire
вот зря вас минусят( вы попали в самую точку — gui я начал писать именно с нескучных обоев.
habrastorage.org/webt/8-/xa/d_/8-xad_ut-tmrtuwuxwonmsgkvck.png
Если что, то подложка из macos — это для антуража и самообмана. Картиночки на скрине — тест отрисовки с прозрачностью, понятия не имею зачем я это делал)
maisvendoo
Только вот там в коде много глюков, в основном из-за кривой реализации менеджера памяти. Поэтому в релизе это ядро одно сплошное неопределенное поведение
vire
Это уже для самостоятельной работы) Тут главное — начать.
Просто замечательное хобби для души без дедлайнов и релизов на ближайшие лет 30.
MacIn
Это точно. У меня аналогичный проект тянется с 2001 примерно года. Сам себе архитектор и творец, очень интересно.
sergling
а можно адрес сайта?
vire
Самое начало: phantomexos.blogspot.ru/2013/07/phantomex-multiboot.html?m=0
Рекомендация одна: пишите код. Пока достаточно его просто копипастить из блога(весь код рабочий) — работающее ядрышко в эмуляторе очень мотивирует.