В современных операционных системах, поскольку системные ресурсы могут быть доступны нескольким приложениям одновременно, если они не защищены, между приложениями могут возникать конфликты, которые могут привести к сбоям системы для вредоносных приложений. Упомянутые здесь системные ресурсы включают файлы, сети и различные аппаратные устройства. Например, для работы с файлами вы должны использовать API, предоставляемый операционной системой (например, fopen в Linux).
Системные вызовы находятся в постоянном контакте с нами в нашей работе.Какой принцип работы системных вызовов? Что вы делали в процессе?
В этой статье будет объяснен принцип системных вызовов, чтобы каждый имел четкое представление о системных вызовах.
Больше статей смотрите в личном блоге:GitHub.com/farmer Джон Брат…
Обзор
Современные процессоры обычно имеют различные уровни привилегий.В целом существует 4 уровня привилегий, пронумерованных от Ring 0 (самые высокие привилегии) до Ring 3 (наименьшие привилегии).В Linux используются Ring 0 и RIng 3, а пользовательский режим соответствует кольцу 3, состояние ядра соответствует кольцу 0.
Обычные приложения работают в пользовательском режиме, и многие их операции ограничены, например, изменение уровней привилегий, доступ к оборудованию и т. д. Код с высокими привилегиями может опускаться до более низких уровней, но не наоборот. И системные вызовы выполняются в режиме ядра, так как же приложения, работающие в пользовательском режиме, запускают код режима ядра? Операционная система, как правило,прерыватьдля переключения из пользовательского режима в режим ядра. Студенты, изучавшие курсы по операционным системам, должны быть знакомы со словом «прерывание».
Прерывания обычно имеют два атрибута: один — это номер прерывания, а другой — обработчик прерывания. Разные прерывания имеют разные номера прерываний, и каждый номер прерывания соответствует обработчику прерывания. В ядре есть массив, называемый таблицей векторов прерываний, для отображения этой взаимосвязи. Когда поступает прерывание, ЦП приостанавливает выполнение кода, переходит к таблице векторов прерываний в соответствии с номером прерывания, чтобы найти соответствующий обработчик прерывания и вызвать его. После завершения обработчика прерывания предыдущий код продолжает выполняться.
Прерывания делятся на аппаратные прерывания и программные прерывания. Здесь мы говорим о программных прерываниях. Программные прерывания обычно представляют собой инструкцию. Используя эту инструкцию, пользователи могут вручную инициировать прерывание. Например, в i386 соответствующей инструкцией является int, а соответствующий номер прерывания указывается после инструкции int.Например, int 0x80 означает, что вы вызываете обработчик прерывания с номером 0x80.
Количество прерываний ограничено, поэтому одно прерывание не будет соответствовать системному вызову (системных вызовов много). Используйте int 0x80 для запуска всех системных вызовов в Linux, так как же различать разные вызовы? Для каждого системного вызова существует номер системного вызова. Перед запуском прерывания номер системного вызова будет помещен в фиксированный регистр. Обработчик прерывания, соответствующий 0x80, прочитает значение регистра, а затем решит, какой системный вызов выполнить. код.
Версии до Linux 2.5 (конкретная версия не очень точна) использовали int 0x80 для реализации системных вызовов, но на самом деле производительность инструкции int не очень хорошая по следующим причинам (из этой статьистатья):
在 x86 保护模式中,处理 INT 中断指令时,CPU 首先从中断描述表 IDT 取出对应的门描述符,判断门描述符的种类,然后检查门描述符的级别 DPL 和 INT 指令调用者的级别 CPL,当 CPL<=DPL 也就是说 INT 调用者级别高于描述符指定级别时,才能成功调用,最后再根据描述符的内容,进行压栈、跳转、权限级别提升。内核代码执行完毕之后,调用 IRET 指令返回,IRET 指令恢复用户栈,并跳转会低级别的代码。
其实,在发生系统调用,由 Ring3 进入 Ring0 的这个过程浪费了不少的 CPU 周期,例如,系统调用必然需要由 Ring3 进入 Ring0(由内核调用 INT 指令的方式除外,这多半属于 Hacker 的内核模块所为),权限提升之前和之后的级别是固定的,CPL 肯定是 3,而 INT 80 的 DPL 肯定也是 3,这样 CPU 检查门描述符的 DPL 和调用者的 CPL 就是完全没必要。
Именно из-за этого в linux 2.5 поддерживается новый системный вызов, основанный на наборе инструкций специально для системных вызовов, которые начал поддерживать процессор Intel Pentium 2 поколения.sysenter
/sysexit
.sysenter
Команда используется для входа в Ring0 из Ring3,sysexit
Инструкция используется для возврата Ring3 из Ring0. Поскольку нет обработки проверки уровня привилегий и операции стекирования, скорость выполнения намного выше, чем у INT n/IRET.
В этой статье анализируется инструкция int, новый механизм системных вызовов можно найти в следующих статьях:
woo woo Краткое описание.com/afraid/fa 4 от 04 до еды 8 ой...
системный вызов на основе int
триггерное прерывание
Мы называем системуfork
Например, функция fork определена в glibc (версия 2.17).unistd.h
/* Clone the calling process, creating an exact copy.
Return -1 for errors, 0 to the new process,
and the process ID of the new process to the old process. */
extern __pid_t fork (void) __THROWNL;
fork
Код реализации функции найти сложнее, т.к.nptl\sysdeps\unix\sysv\linux\fork.c
Есть такой кусок кода
weak_alias (__libc_fork, __fork)
libc_hidden_def (__fork)
weak_alias (__libc_fork, fork)
Проще говоря, его функция состоит в том, чтобы__libc_fork
в виде__fork
, поэтому реализация функции fork находится в__libc_fork
, основной код выглядит следующим образом
#ifdef ARCH_FORK
pid = ARCH_FORK ();
#else
# error "ARCH_FORK must be defined so that the CLONE_SETTID flag is used"
pid = INLINE_SYSCALL (fork, 0);
#endif
Мы аналитически определяемARCH_FORK
Случай,ARCH_FORK
определено вnptl\sysdeps\unix\sysv\linux\i386\fork.c
, код выглядит следующим образом:
#define ARCH_FORK() \
INLINE_SYSCALL (clone, 5, \
CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD, 0, \
NULL, NULL, &THREAD_SELF->tid)
Код INLINE_SYSCALL вsysdeps\unix\sysv\linux\i386\sysdep.h
#undef INLINE_SYSCALL
#define INLINE_SYSCALL(name, nr, args...) \
({ \
unsigned int resultvar = INTERNAL_SYSCALL (name, , nr, args); \
if (__builtin_expect (INTERNAL_SYSCALL_ERROR_P (resultvar, ), 0)) \
{ \
__set_errno (INTERNAL_SYSCALL_ERRNO (resultvar, )); \
resultvar = 0xffffffff; \
} \
(int) resultvar; })
INLINE_SYSCALL
В основном вызывается под тем же файломINTERNAL_SYSCALL
# define INTERNAL_SYSCALL(name, err, nr, args...) \
({ \
register unsigned int resultvar; \
EXTRAVAR_##nr \
asm volatile ( \
LOADARGS_##nr \
"movl %1, %%eax\n\t" \
"int $0x80\n\t" \
RESTOREARGS_##nr \
: "=a" (resultvar) \
: "i" (__NR_##name) ASMFMT_##nr(args) : "memory", "cc"); \
(int) resultvar; })
#define __NR_clone 120
вот абзацвстроенная сборкакод, где__NR_##name
значение__NR_clone
то есть 120. Здесь в основном два шага:
- Установите значение регистра eax на 120
- воплощать в жизнь
int $0x80
застрять
int $0x80
Инструкция приведет к тому, что процессор попадет в прерывание и выполнит соответствующий обработчик прерывания 0x80. Но перед этим процессор еще долженкоммутация стека.
Поскольку в Linux пользовательский режим и режим ядра используют разные стеки (см.статья), они отвечают за свои собственные вызовы функций и не мешают друг другу. в исполненииint $0x80
Когда программе необходимо переключиться из пользовательского режима в режим ядра, текущий стек программы также долженПереключение с пользовательского стека на стек ядра. Соответственно, когда выполнение программы прерывания завершается и возвращается, текущий стек должен бытьПереключиться со стека ядра обратно на пользовательский стек.
Текущий стек, упомянутый здесь, относится кESP-регистрСтек, на который указывает значение . Значение ESP находится в области пользовательского стека, тогда текущий стек программы является пользовательским стеком, и наоборот. Кроме того, значение регистра SS указывает на страницу, на которой находится текущий стек. Следовательно, процесс переключения пользовательского стека на стек ядра таков:
- Сохраните текущие значения ESP, SS и других регистров в стек ядра.
- Установите значения ESP, SS и т. д. в соответствующие значения стека ядра.
Наоборот, процесс перехода из стека ядра в пользовательский стек: восстановление значений таких регистров, как ESP и SS, то есть установка исходных значений ESP, SS и других, сохраненных в стеке ядра, обратно в соответствующие регистры.
обработчик прерывания
После переключения на стек ядра начинается выполнение таблицы векторов прерываний.0x80
№ обработчик прерывания. Обработчики прерываний в дополнение к системным вызовам (0x80
) и деление на 0 исключение (0x00
), исключение ошибки страницы (0x14
) и так далее, вarch\i386\kernel\traps.c
документtrap_init
Метод описывает процесс регистрации обработчика прерывания в таблице векторов прерываний:
void __init trap_init(void)
{
#ifdef CONFIG_EISA
void __iomem *p = ioremap(0x0FFFD9, 4);
if (readl(p) == 'E'+('I'<<8)+('S'<<16)+('A'<<24)) {
EISA_bus = 1;
}
iounmap(p);
#endif
#ifdef CONFIG_X86_LOCAL_APIC
init_apic_mappings();
#endif
set_trap_gate(0,÷_error);
set_intr_gate(1,&debug);
set_intr_gate(2,&nmi);
set_system_intr_gate(3, &int3); /* int3-5 can be called from all */
set_system_gate(4,&overflow);
set_system_gate(5,&bounds);
set_trap_gate(6,&invalid_op);
set_trap_gate(7,&device_not_available);
set_task_gate(8,GDT_ENTRY_DOUBLEFAULT_TSS);
set_trap_gate(9,&coprocessor_segment_overrun);
set_trap_gate(10,&invalid_TSS);
set_trap_gate(11,&segment_not_present);
set_trap_gate(12,&stack_segment);
set_trap_gate(13,&general_protection);
set_intr_gate(14,&page_fault);
set_trap_gate(15,&spurious_interrupt_bug);
set_trap_gate(16,&coprocessor_error);
set_trap_gate(17,&alignment_check);
#ifdef CONFIG_X86_MCE
set_trap_gate(18,&machine_check);
#endif
set_trap_gate(19,&simd_coprocessor_error);
set_system_gate(SYSCALL_VECTOR,&system_call);
/*
* Should be a barrier for any external CPU state.
*/
cpu_init();
trap_init_hook();
}
SYSCALL_VECTOR
Определяется следующим образом:
#define SYSCALL_VECTOR 0x80
так0x80
Соответствующий обработчикsystem_call
этот метод, который находится вarch\i386\kernel\entry.S
ENTRY(system_call)
//code 1: 保存各种寄存器
SAVE_ALL
...
jnz syscall_trace_entry
//如果传入的系统调用号大于最大的系统调用号,则跳转到无效调用处理
cmpl $(nr_syscalls), %eax
jae syscall_badsys
syscall_call:
//code 2: 根据系统调用号(存储在eax中)来调用对应的系统调用程序
call *sys_call_table(,%eax,4)
//保存系统调用返回值到eax寄存器中
movl %eax,EAX(%esp) # store the return value
...
restore_all:
//code 3:恢复各种寄存器的值 以及执行iret指令
RESTORE_ALL
...
В основном делится на несколько этапов:
1. Сохраните различные регистры
2. Выполните соответствующую программу системного вызова в соответствии с номером системного вызова и сохраните возвращенный результат в eax
3. Восстановить различные регистры
который содержит различные регистрыSAVE_ALL
Определено в entry.S:
#define SAVE_ALL \
cld; \
pushl %es; \
pushl %ds; \
pushl %eax; \
pushl %ebp; \
pushl %edi; \
pushl %esi; \
pushl %edx; \
pushl %ecx; \
pushl %ebx; \
movl $(__USER_DS), %edx; \
movl %edx, %ds; \
movl %edx, %es;
sys_call_table
Определено в entry.S:
.data
ENTRY(sys_call_table)
.long sys_restart_syscall /* 0 - old "setup()" system call, used for restarting */
.long sys_exit
.long sys_fork
.long sys_read
.long sys_write
.long sys_open /* 5 */
...
.long sys_sigreturn
.long sys_clone /* 120 */
...
sys_call_table
Это таблица системных вызовов, каждый длинный элемент (4 байта) является адресом системного вызова, поэтому*sys_call_table(,%eax,4)
Значит этоsys_call_table
Верхнее смещение равно0+%eax*4
Системный вызов, на который указывает элемент, т.е. первый%eax
системный вызов. вышеfork
Системный вызов, наконец, устанавливается на значение eax равно 120, затем завершается окончательное выполнение.sys_clone
Эта функция, обратите внимание на ее реализацию и второй системный вызовsys_fork
В основном то же самое, но параметры разные, вы можете увидеть разницу между форком и клономздесь, код показан ниже:
//kernel\fork.c
asmlinkage int sys_fork(struct pt_regs regs)
{
return do_fork(SIGCHLD, regs.esp, ®s, 0, NULL, NULL);
}
asmlinkage int sys_clone(struct pt_regs regs)
{
unsigned long clone_flags;
unsigned long newsp;
int __user *parent_tidptr, *child_tidptr;
clone_flags = regs.ebx;
newsp = regs.ecx;
parent_tidptr = (int __user *)regs.edx;
child_tidptr = (int __user *)regs.edi;
if (!newsp)
newsp = regs.esp;
return do_fork(clone_flags, newsp, ®s, 0, parent_tidptr, child_tidptr);
}
Проанализирован базовый процесс системного вызова, а остальная конкретная логика обработки не имеет отношения к данной статье и анализироваться не будет, заинтересованные студенты могут убедиться в этом сами.
Общая схема телефонного разговора выглядит следующим образом:
End
Основная причина желания написать эту статью в том, что я прочитал ее много лет назад.«Саморазвитие программиста»В этой книге у меня было некоторое представление о системных вызовах и раньше, но оно было очень фрагментарным и расплывчатым.Прочитав главу о системных вызовах в этой книге, я развеял многие сомнения. В целом, это хорошая книга, но моя связанная с ней основа относительно слаба, так что я мало что выиграю.