Как glibc обрабатывает клон()?
Через арку-специфические монтажные обертки. Для i386 см. sysdeps/unix/sysv/linux/i386/clone.S в источниках glibc; для x86-64, см. sysdeps/unix/sysv/linux/x86-64/clone.S и т. д.
Обычные обертки для системных вызовов недостаточны, так как в пользовательском пространстве можно переключать стеки. В приведенных выше сборных файлах есть довольно информативные комментарии относительно того, что на самом деле нужно делать в пользовательском пространстве в дополнение к syscall.
Все параметры системного вызова в ядре не точно так же, как клон в glibc, так как обрабатываются эти вариации?
Функции библиотеки C, которые отображаются в системном режиме, являются функциями оболочки.
Рассмотрим, например, POSIX.1 write()
C библиотека низкоуровневой функции ввода-вывода и системный стол Linux write()
. Параметры в основном такие же, как и условия ошибки, но значения возврата ошибки различаются. Функция библиотеки C возвращает -1
с errno
при возникновении ошибки, тогда как в syscall Linux возвращаются отрицательные коды ошибок (которые в основном соответствуют значениям errno
).
Если вы посмотрите на, например, sysdeps/unix/sysv/linux/x86_64/sysdep.h, вы можете увидеть, что основной системный вызов оболочка для Linux на x86-64 сводится к
# define INLINE_SYSCALL(name, nr, args...) \
({ \
unsigned long int resultvar = INTERNAL_SYSCALL (name, , nr, args); \
if (__glibc_unlikely (INTERNAL_SYSCALL_ERROR_P (resultvar,))) \
{ \
__set_errno (INTERNAL_SYSCALL_ERRNO (resultvar,)); \
resultvar = (unsigned long int) -1; \
} \
(long int) resultvar; })
который только называет фактический системный вызов, а затем проверяет, является ли возвращение системного вызова значение, указанное сообщение об ошибке; и если это так, то изменяется результат на -1
и устанавливает errno
соответственно. Это просто забавно, потому что он полагается на расширения GCC, чтобы заставить его вести себя как одно утверждение.
Допустим, вы добавили новый системный вызов в Linux, скажем
SYSCALL_DEFINE2(splork, unsigned long, arg1, void *, arg2);
и, по каким-либо причинам, вы хотите, чтобы выставить его в пользовательское приложение как
int splork(void *arg2, unsigned long arg1);
Нет проблем! Все, что вам нужно, чтобы обеспечить минимальный файл заголовка,
#ifndef _SPLORK_H
#define _SPLORK_H
#define _GNU_SOURCE
#include <sys/syscall.h>
#include <errno.h>
#ifndef __NR_splork
#if defined(__x86_64__)
#define __NR_splork /* syscall number on x86-64 */
#else
#if defined(__i386)
#define __NR_splork /* syscall number on i386 */
#endif
#endif
#ifdef __NR_splork
#ifndef SYS_splork
#define SYS_splork __NR_splork
#endif
int splork(void *arg2, unsigned long arg1)
{
long retval;
retval = syscall(__NR_splork, (long)arg1, (void *)arg2);
if (retval < 0) {
/* Note: For backward compatibility, we might wish to use
*(__errno_location()) = -retval;
here. */
errno = -retval;
return -1;
} else
return (int)retval;
}
#else
#undef SYS_splork
int splork(void *arg2, unsigned long arg1)
{
/* Note: For backward compatibility, we might wish to use
*(__errno_location()) = ENOTSUP;
here. */
errno = ENOTSUP;
return -1;
}
#endif
#endif /* _SPLORK_H */
SYS_splork
и __NR_splork
препроцессора макросы, определяющие количество системных вызовов для нового системного вызова. Так как номер syscall, вероятно, не включен (но?) В официальные источники и заголовки ядра, указанный выше заголовочный файл явно объявляет его для каждой поддерживаемой архитектуры. Для архитектур, где он не поддерживается, функция splork()
всегда будет возвращать -1
с errno == ENOTSUP
.
Обратите внимание, что системные вызовы Linux ограничены 6 параметрами. Если вашей функции ядра требуется больше, вам нужно упаковать параметры в структуру, передать адрес этой структуры в ядро и использовать copy_from_user()
для копирования значений в одну и ту же структуру в ядре.
Во всех Linux архитектур, указателей и long
одного и того же размера (int
может быть меньше, чем указатель), поэтому я рекомендую вам использовать либо long
или фиксированного размера типа в таких структурах, чтобы передать данные в/из ядра.