Еще в первый год моего знакомства с Ubuntu, мне стало интересно, как же работает ядро этой ОС. Я решил со всей серъезностью во всем разобратся, скачал 80-мегабайтовый архив исходников, и… всё! Я не знал ни с чего начать, ни чем закончить. Открывал поочереди случайные файлы и тут же терялся. Думаю такое случалось с каждым бывалым линуксоидом. Сейчас же я набрался опыта и хотел бы им поделиться.
В этой статье я раскажу, как я искал код системного вызова mkdir.
Начнем с того, что функция mkdir определена в sys/stat.h
. Прототип выглядит следующим образом:
/* Create a new directory named PATH, with permission bits MODE. */
extern int mkdir (__const char *__path, __mode_t __mode)
__THROW __nonnull ((1));
Этат заголовочный файл является частью стандарта POSIX. Linux является почти полностью POSIX-совместим, а значит должен реализовывать mkdir именно с такой сигнатурой.
Но даже зная сигнатуру, найти собственно код системного вызова вовсе не просто…
И в правду ack "int mkdir"
вернет:
security/inode.c
103:static int mkdir(struct inode *dir, struct dentry *dentry, int mode)tools/perf/util/util.c
4:int mkdir_p(char *path, mode_t mode)tools/perf/util/util.h
259:int mkdir_p(char *path, mode_t mode);
Понятно, что ни одна сигнатура полностью не совпадает. Где же находится реализация функции mkdir? Каков алгоритм нахождения реализаций системных вызовов в ядре Linux?
Системные вызовы работают не так, как обычные функции. Точне функциями они вовсе не являются. Для выполнений системного вызова нужно немного ассемблерного кода. По большому счету, в регистр EAX ложится номер системного вызова (кстати говоря, к системным вызовам обращаются по номеру, а не по адресу), в остальные регистры кладутся аргументы: первый в EBX, второй в ECX, третий в EDX, четвертый в ESX, пятый в EDI. Кстати, именно по этому системный вызов не может иметь более 5 аргументов. После расположения всех нужных значений программа, которыя хочет сделать системный вызов, выполняет 128-е прерывание (на асемблере: int 0x80). Прерывание, переводит процессор в режим ядра и передает управление по заранее оговореному с ядром адресу. Как видим, системные вызовы работают на более низком уровне, чем С-функции.
Номер любого системного вызова можно найти в usr/include/asm*/unistd.h
:
#define __NR_mkdir 83
__SYSCALL(__NR_mkdir, sys_mkdir)
Тобиш системный вызов mkdir имеет номер 83.
Если вы програмировали в user space под Linux, вы скорее всего знаете, что как правило для выполнения системных вызовов таки используются C-функции. Откуда они берутся? Те функции — это просто обертки из библиотеки GNU libc. Кажому системному вызову соответствует функция-обертка. Сами же функции делают все те же прерывания.
Итак где же теперь искать mkdir? В теории системный вызов мог бы быть реализован просто на ассемблере, тогда соответствующей ему С-функции просто не было бы, одна это не так. В Linux каждый системный вызов определен в include/linux/syscalls.h
:
asmlinkage long sys_mkdir(const char __user *pathname, int mode);
Реализация же находится в соответсвующей части ядра. Тут просто нада знать, что mkdir является частью файловой подсистемы VFS и определен в fs/namei.c
:
SYSCALL_DEFINE2(mkdir, const char __user *, pathname, umode_t, mode)
{
return sys_mkdirat(AT_FDCWD, pathname, mode);
}
SYSCALL_DEFINE2 — это один из макросов серии SYSCALL_DEFINEx, где х — число аргументов системного вызова. В коде выше вызывается другой системный вызов — sys_mkdirat, который так же находится в fs/namei.c
. Обратите внимение, что тут системный вызов выполняется вызовом функции, потому что вызывающий код уже исполняется в режиме ядра.
SYSCALL_DEFINE3(mkdirat, int, dfd, const char __user *, pathname, umode_t, mode)
{
struct dentry *dentry;
struct path path;
int error;
dentry = user_path_create(dfd, pathname, &path, 1);
if (IS_ERR(dentry))
return PTR_ERR(dentry);
if (!IS_POSIXACL(path.dentry->d_inode))
mode &= ~current_umask();
error = security_path_mkdir(&path, dentry, mode);
if (!error)
error = vfs_mkdir(path.dentry->d_inode, dentry, mode);
done_path_create(&path, dentry);
return error;
}
А вот тут уже интересное! Встречаем первые проверки и опять же передача управления еще другой функции — vfs_mkdir, которая определена все в том же fs/namei.h
:
int vfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
{
int error = may_create(dir, dentry);
unsigned max_links = dir->i_sb->s_max_links;
if (error)
return error;
if (!dir->i_op->mkdir)
return -EPERM;
mode &= (S_IRWXUGO|S_ISVTX);
error = security_inode_mkdir(dir, dentry, mode);
if (error)
return error;
if (max_links && dir->i_nlink >= max_links)
return -EMLINK;
error = dir->i_op->mkdir(dir, dentry, mode);
if (!error)
fsnotify_mkdir(dir, dentry);
return error;
}
Еще проверки, еще передача управления. Стоит сказать, что Linux — очень многоуровневая система, где обязаности распределены по разным частям системы. Поэтому и не странно, что в коде выше логика постоянно делегируется. В последнем куске кода вызывается dir->i_op->mkdir(dir, dentry, mode). Идем по следам! dir имеет тип inode*. Из определения структуры inode узнаем, что указатель i_op имеет тип inode_operations*. Последняя структура содержит указатели на функции операций, которые можно сделать над данным узлом, при чем реализации разные для разных файловых систем. То есть взависимости от того, какой файловой системе принадлежит наш dir, структура inode_operations будет содержать указатели на те или иные реализации.
К примеру для ext4 реализицию mkdir находим в fs/ext4/namei.c
static int ext4_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
{
handle_t *handle;
struct inode *inode;
struct buffer_head *dir_block = NULL;
struct ext4_dir_entry_2 *de;
struct ext4_dir_entry_tail *t;
unsigned int blocksize = dir->i_sb->s_blocksize;
int csum_size = 0;
int err, retries = 0;
if (EXT4_HAS_RO_COMPAT_FEATURE(dir->i_sb,
EXT4_FEATURE_RO_COMPAT_METADATA_CSUM))
csum_size = sizeof(struct ext4_dir_entry_tail);
if (EXT4_DIR_LINK_MAX(dir))
return -EMLINK;
dquot_initialize(dir);
retry:
handle = ext4_journal_start(dir, EXT4_DATA_TRANS_BLOCKS(dir->i_sb) +
EXT4_INDEX_EXTRA_TRANS_BLOCKS + 3 +
EXT4_MAXQUOTAS_INIT_BLOCKS(dir->i_sb));
if (IS_ERR(handle))
return PTR_ERR(handle);
if (IS_DIRSYNC(dir))
ext4_handle_sync(handle);
inode = ext4_new_inode(handle, dir, S_IFDIR | mode,
&dentry->d_name, 0, NULL);
err = PTR_ERR(inode);
if (IS_ERR(inode))
goto out_stop;
inode->i_op = &ext4_dir_inode_operations;
inode->i_fop = &ext4_dir_operations;
inode->i_size = EXT4_I(inode)->i_disksize = inode->i_sb->s_blocksize;
if (!(dir_block = ext4_bread(handle, inode, 0, 1, &err))) {
if (!err) {
err = -EIO;
ext4_error(inode->i_sb,
"Directory hole detected on inode %lun",
inode->i_ino);
}
goto out_clear_inode;
}
BUFFER_TRACE(dir_block, "get_write_access");
err = ext4_journal_get_write_access(handle, dir_block);
if (err)
goto out_clear_inode;
de = (struct ext4_dir_entry_2 *) dir_block->b_data;
de->inode = cpu_to_le32(inode->i_ino);
de->name_len = 1;
de->rec_len = ext4_rec_len_to_disk(EXT4_DIR_REC_LEN(de->name_len),
blocksize);
strcpy(de->name, ".");
ext4_set_de_type(dir->i_sb, de, S_IFDIR);
de = ext4_next_entry(de, blocksize);
de->inode = cpu_to_le32(dir->i_ino);
de->rec_len = ext4_rec_len_to_disk(blocksize -
(csum_size + EXT4_DIR_REC_LEN(1)),
blocksize);
de->name_len = 2;
strcpy(de->name, "..");
ext4_set_de_type(dir->i_sb, de, S_IFDIR);
set_nlink(inode, 2);
if (csum_size) {
t = EXT4_DIRENT_TAIL(dir_block->b_data, blocksize);
initialize_dirent_tail(t, blocksize);
}
BUFFER_TRACE(dir_block, "call ext4_handle_dirty_metadata");
err = ext4_handle_dirty_dirent_node(handle, inode, dir_block);
if (err)
goto out_clear_inode;
set_buffer_verified(dir_block);
err = ext4_mark_inode_dirty(handle, inode);
if (!err)
err = ext4_add_entry(handle, dentry, inode);
if (err) {
out_clear_inode:
clear_nlink(inode);
unlock_new_inode(inode);
ext4_mark_inode_dirty(handle, inode);
iput(inode);
goto out_stop;
}
ext4_inc_count(handle, dir);
ext4_update_dx_flag(dir);
err = ext4_mark_inode_dirty(handle, dir);
if (err)
goto out_clear_inode;
unlock_new_inode(inode);
d_instantiate(dentry, inode);
out_stop:
brelse(dir_block);
ext4_journal_stop(handle);
if (err == -ENOSPC && ext4_should_retry_alloc(dir->i_sb, &retries))
goto retry;
return err;
}
Понимание работы ядра — полезный навык в арсенале любого линуксоида. Надеюсь эта статья тоже была полезной!
Ресурсы:
Таблица системных вызовов Linux
Исходники Linux 3.6
Обсуждение на unix.stackexchange.com
Автор: Ganga