Ваше понимание довольно близко; хитрость заключается в том, что большинство компиляторов никогда не будут писать системные вызовы, потому что функции, которые вызывают программы (например, getpid(2)
, chdir(2)
и т. д.), фактически предоставляются стандартной библиотекой C. Стандартная библиотека C содержит код для системного вызова, независимо от того, вызывается ли он через INT 0x80
или SYSENTER
. Это была бы странная программа, которая делает системные вызовы без работы библиотеки. (Хотя perl
предоставляет функцию syscall()
, которая может напрямую выполнять системные вызовы! Сумасшедший, верно?)
Далее память. Ядро операционной системы иногда имеет легкий адресный доступ к памяти пользовательского процесса. Конечно, режимы защиты различны, и предоставленные пользователем данные должны быть скопированы в защищенное адресное пространство ядра, чтобы предотвратить изменение предоставленных пользователем данных , пока системный вызов находится в полете :
static int do_getname(const char __user *filename, char *page)
{
int retval;
unsigned long len = PATH_MAX;
if (!segment_eq(get_fs(), KERNEL_DS)) {
if ((unsigned long) filename >= TASK_SIZE)
return -EFAULT;
if (TASK_SIZE - (unsigned long) filename < PATH_MAX)
len = TASK_SIZE - (unsigned long) filename;
}
retval = strncpy_from_user(page, filename, len);
if (retval > 0) {
if (retval < len)
return 0;
return -ENAMETOOLONG;
} else if (!retval)
retval = -ENOENT;
return retval;
}
Хотя это и не системный вызов, это вспомогательная функция , вызываемая функциями системного вызова, которая копирует имена файлов в адресное пространство ядра. Он проверяет, находится ли полное имя файла в диапазоне данных пользователя, вызывает функцию, которая копирует строку из пространства пользователя, и выполняет некоторые проверки работоспособности перед возвратом.
get_fs()
и подобные функции являются остатками от x86-корней Linux. Функции имеют рабочие реализации для всех архитектур, но имена остаются архаичными.
Вся дополнительная работа с сегментами происходит потому, что ядро и пользовательское пространство могут совместно использовать некоторую часть доступного адресного пространства. На 32-битной платформе (где цифры легко понять) ядро обычно имеет один гигабайт виртуального адресного пространства, а пользовательские процессы обычно имеют три гигабайта виртуального адресного пространства.
Когда процесс вызывает ядро, ядро «исправит» разрешения таблицы страниц, чтобы предоставить ему доступ ко всему диапазону, и получит преимущество предварительно заполненных записей TLB для пользователя. предоставленная память. Огромный успех. Но когда ядро должно переключить контекст обратно в пользовательское пространство, оно должно сбросить TLB, чтобы удалить кэшированные привилегии на страницах адресного пространства ядра.
Но дело в том, что одного гигабайта виртуального адресного пространства не достаточно для всех структур данных ядра на огромных машинах. Поддержка метаданных кэшированных файловых систем и драйверов блочных устройств, сетевых стеков и отображений памяти для всех процессов в системе может занимать огромное количество данных.
Доступны разные «сплиты»: два гигабайта для пользователя, два гигабайта для ядра, один гигабайт для пользователя, три гигабайта для ядра и т. Д. По мере увеличения пространства для ядра пространство для пользовательских процессов уменьшается. Таким образом, существует разделение памяти 4:4
, которое дает четыре гигабайта пользовательскому процессу, четыре гигабайта ядру, и ядро должно манипулировать дескрипторами сегментов, чтобы иметь доступ к пользовательской памяти. TLB сбрасывается при входе и выходе из системных вызовов, что является довольно значительным снижением скорости. Но это позволяет ядру поддерживать значительно большие структуры данных.
Гораздо большие таблицы страниц и диапазоны адресов 64-битных платформ, вероятно, делают весь предыдущий вид странным Во всяком случае, я очень на это надеюсь.