Что быстрее: Rust или C?
Не нужно гадать, можно просто посмотреть ночные лидерборды проекта Github Primes. Каждую ночь до 100 разных языков программирования прогоняются по бенчмаркам на одном и том же алгоритме поиска простых чисел:
https://plummerssoftwarellc.github.io/PrimeView/report
👉 @Cpportal
Не нужно гадать, можно просто посмотреть ночные лидерборды проекта Github Primes. Каждую ночь до 100 разных языков программирования прогоняются по бенчмаркам на одном и том же алгоритме поиска простых чисел:
https://plummerssoftwarellc.github.io/PrimeView/report
Please open Telegram to view this post
VIEW IN TELEGRAM
🤣16❤7
getrusage - Linux syscall #98
У каждого процесса в Linux есть скрытая "табель успеваемости". Там фиксируется, сколько CPU-времени ты реально сжег, сколько памяти успел отхватить, и как планировщик дергал твой процесс. Все это можно вытащить одним системным вызовом: getrusage (syscall #98 на x86_64).
Структура rusage, которую он заполняет, это та же самая структура, которую использует команда time. Когда ты видишь user, sys и real time, эти значения ядро как раз и прокидывает через struct rusage.
Но там не только про время. Самая мощная штука, которую легко пропустить: переключения контекста.
getrusage делит их на две разные категории:
Voluntary (ru_nvcsw): процесс сам добровольно отдал CPU до того, как закончился его тайм-слайс. Обычно это значит, что он ждет I/O, mutex или какой-то ресурс.
Involuntary (ru_nivcsw): ядро принудительно сняло процесс с CPU. Либо проснулся более приоритетный процесс, либо кончился тайм-слайс.
Это разделение реально дает чит-код для дебага.
Много voluntary переключений? Скорее всего ты уперся в I/O или бодался за локи. Много involuntary? Значит, ты CPU-bound или воюешь за ресурсы на забитом сервере.
Еще там есть ru_maxrss (Maximum Resident Set Size) - пиковое потребление физической RAM процессом, в килобайтах.
Ниже полный пример на C: он немного грузит CPU, а потом печатает свою же "карточку":
В следующий раз, когда приложение "тормозит", не шамань. Спроси у ядра, что реально произошло.
👉 @Cpportal
У каждого процесса в Linux есть скрытая "табель успеваемости". Там фиксируется, сколько CPU-времени ты реально сжег, сколько памяти успел отхватить, и как планировщик дергал твой процесс. Все это можно вытащить одним системным вызовом: getrusage (syscall #98 на x86_64).
Структура rusage, которую он заполняет, это та же самая структура, которую использует команда time. Когда ты видишь user, sys и real time, эти значения ядро как раз и прокидывает через struct rusage.
Но там не только про время. Самая мощная штука, которую легко пропустить: переключения контекста.
getrusage делит их на две разные категории:
Voluntary (ru_nvcsw): процесс сам добровольно отдал CPU до того, как закончился его тайм-слайс. Обычно это значит, что он ждет I/O, mutex или какой-то ресурс.
Involuntary (ru_nivcsw): ядро принудительно сняло процесс с CPU. Либо проснулся более приоритетный процесс, либо кончился тайм-слайс.
Это разделение реально дает чит-код для дебага.
Много voluntary переключений? Скорее всего ты уперся в I/O или бодался за локи. Много involuntary? Значит, ты CPU-bound или воюешь за ресурсы на забитом сервере.
Еще там есть ru_maxrss (Maximum Resident Set Size) - пиковое потребление физической RAM процессом, в килобайтах.
Ниже полный пример на C: он немного грузит CPU, а потом печатает свою же "карточку":
#include <stdio.h>
#include <sys/resource.h>
#include <unistd.h>
int main() {
struct rusage usage;
volatile int i = 0;
// Burn some CPU cycles to generate user time
for(i = 0; i < 500000000; i++);
// Get statistics for the calling process
if (getrusage(RUSAGE_SELF, &usage) == 0) {
printf("User CPU time: %ld.%06ld s\n",
usage.ru_utime.tv_sec, usage.ru_utime.tv_usec);
printf("System CPU time: %ld.%06ld s\n",
usage.ru_stime.tv_sec, usage.ru_stime.tv_usec);
printf("Max RAM used: %ld KB\n", usage.ru_maxrss);
printf("Voluntary switches: %ld\n", usage.ru_nvcsw);
printf("Involuntary switches: %ld\n", usage.ru_nivcsw);
}
return 0;
}
В следующий раз, когда приложение "тормозит", не шамань. Спроси у ядра, что реально произошло.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤13🔥6
Please open Telegram to view this post
VIEW IN TELEGRAM
😁22❤4💊3👍2
Data Structures for Text Sequences, Charles Crowley
Добавляю эту статью в список чтения. Текстовый редактор это один из самых интересных примеров софта, где все крутится вокруг последовательностей текста, и мне особенно любопытен gap buffer.
https://www.cs.unm.edu/~crowley/papers/sds.pdf
👉 @Cpportal
Добавляю эту статью в список чтения. Текстовый редактор это один из самых интересных примеров софта, где все крутится вокруг последовательностей текста, и мне особенно любопытен gap buffer.
https://www.cs.unm.edu/~crowley/papers/sds.pdf
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8👍3
sysinfo - Linux syscalls #99
Парсить текстовые файлы, чтобы проверить состояние системы на Linux, может быть неэффективно. Каждый раз, когда ты делаешь
Этот системный вызов возвращает сырой бинарный снимок “жизненных показателей” системы за один context switch:
- Uptime (секунды с момента загрузки)
- Load averages
- Всего и свободно RAM
- Использование swap
- Количество процессов
На самом деле в manpage отмечено, что вся информация, которую дает этот syscall, также доступна через
Ниже полный, запускаемый пример на C, чтобы собрать свой “мгновенный” системный монитор:
👉 @Cpportal
Парсить текстовые файлы, чтобы проверить состояние системы на Linux, может быть неэффективно. Каждый раз, когда ты делаешь
cat /proc/meminfo или читаешь /proc/uptime, ядро вынуждено форматировать внутренние данные в текст, а твоя программа потом парсит этот текст обратно. Есть способ быстрее: sysinfo (syscall #99 на x86_64).Этот системный вызов возвращает сырой бинарный снимок “жизненных показателей” системы за один context switch:
- Uptime (секунды с момента загрузки)
- Load averages
- Всего и свободно RAM
- Использование swap
- Количество процессов
На самом деле в manpage отмечено, что вся информация, которую дает этот syscall, также доступна через
/proc/meminfo и /proc/loadavg. Большинство популярных утилит вроде uptime и top реально читают данные именно из /proc. Но если ты пишешь код, чувствительный к производительности, и тебе нужны эти метрики без оверхеда на открытие файлов и парсинг текста, sysinfo дает все одним вызовом.Ниже полный, запускаемый пример на C, чтобы собрать свой “мгновенный” системный монитор:
#include <stdio.h>
#include <sys/sysinfo.h>
int main() {
struct sysinfo si;
// Вызов syscall #99
if (sysinfo(&si) != 0) {
perror("sysinfo failed");
return 1;
}
printf("System Vital Signs:\n");
printf("-------------------\n");
printf("Uptime: %ld seconds\n", si.uptime);
printf("Processes: %d running\n", si.procs);
// Считаем память в мегабайтах
// totalram масштабируется через mem_unit (обычно 1 байт, но может быть больше)
unsigned long long total_mem =
(unsigned long long)si.totalram * si.mem_unit;
unsigned long long free_mem =
(unsigned long long)si.freeram * si.mem_unit;
printf("Total RAM: %llu MB\n", total_mem / 1024 / 1024);
printf("Free RAM: %llu MB\n", free_mem / 1024 / 1024);
return 0;
}
Please open Telegram to view this post
VIEW IN TELEGRAM
1👍9🔥5❤4
В этом месяце hello world сказал: hello world!
Термин ввели в культовой книге по программированию 1978 года The C Programming Language, написанной Брайаном Керниганом и Деннисом Ритчи: https://go.aws/37TmOZq
👉 @Cpportal
Термин ввели в культовой книге по программированию 1978 года The C Programming Language, написанной Брайаном Керниганом и Деннисом Ритчи: https://go.aws/37TmOZq
Please open Telegram to view this post
VIEW IN TELEGRAM
❤23😁5👍2
times (Linux syscall #100)
Твоя Linux-прога живет в двух параллельных мирах: userspace и kernel space. Когда ты оптимизируешь код, важно понимать, какой мир жрет твои циклы. Для этого и нужен syscall
- Real это время по настенным часам (включая ожидание I/O).
- User это время CPU, потраченное на твой код (циклы, математика).
- Sys это время CPU, которое ядро потратило “за тебя” (обработка запросов).
Есть еще мощная вторичная фича, рассчитанная на shell’ы. В структуре есть
Так shell может отчитаться по суммарному потреблению ресурсов сложного скрипта или пайплайна, а не только по самому процессу shell.
Вот C-прога, которая профилирует саму себя. Она специально жжет CPU математикой (User time), а потом спамит дешевым syscall’ом (System time), чтобы показать разделение.
👉 @Cpportal
Твоя Linux-прога живет в двух параллельных мирах: userspace и kernel space. Когда ты оптимизируешь код, важно понимать, какой мир жрет твои циклы. Для этого и нужен syscall
times: #100 на x86_64. Когда ты запускаешь time ./app, видишь вывод Real, User и Sys.- Real это время по настенным часам (включая ожидание I/O).
- User это время CPU, потраченное на твой код (циклы, математика).
- Sys это время CPU, которое ядро потратило “за тебя” (обработка запросов).
times() это мост к этой статистике по CPU. Он заполняет struct tms счетчиками, измеренными в “тиках” (clock ticks). Чтобы перевести тики в секунды, нужен тикрейт: sysconf(_SC_CLK_TCK), который обычно возвращает 100 на Linux (то есть 100 тиков в секунду).Есть еще мощная вторичная фича, рассчитанная на shell’ы. В структуре есть
tms_cutime и tms_cstime (буква c как child). Когда процесс ждет завершения дочерних процессов (через wait), CPU-стоимость детей складывается в эти поля у родителя. Это накопление рекурсивное: время “внуков” поднимается вверх на каждом уровне wait, но время внуков, которых никто так и не дождался, теряется навсегда.Так shell может отчитаться по суммарному потреблению ресурсов сложного скрипта или пайплайна, а не только по самому процессу shell.
Вот C-прога, которая профилирует саму себя. Она специально жжет CPU математикой (User time), а потом спамит дешевым syscall’ом (System time), чтобы показать разделение.
#include <stdio.h>
#include <sys/times.h>
#include <unistd.h>
int main() {
struct tms t;
long clk_tck = sysconf(_SC_CLK_TCK);
volatile int k = 0;
printf("Burning User cycles...\n");
// Heavy math loop (User Time)
for (long i = 0; i < 500000000; i++) {
k += i;
}
printf("Burning System cycles...\n");
// Spamming syscalls (System Time)
for (long i = 0; i < 5000000; i++) {
getpid();
}
// Retrieve the counters
times(&t);
printf("\nTime Analysis:\n");
printf("User CPU: %.2f s\n", (double)t.tms_utime / clk_tck);
printf("System CPU: %.2f s\n", (double)t.tms_stime / clk_tck);
return 0;
}
Please open Telegram to view this post
VIEW IN TELEGRAM
❤13🔥6👍5👎1
Современный C++ отлично подходит для написания кастомных DSL.
Я легко могу собрать простой персональный трекер расходов в нескольких строках кода.
👉 @Cpportal
Я легко могу собрать простой персональный трекер расходов в нескольких строках кода.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8👍4
Нашёл познавательную статью от уже знакомого нам автора : Создание 16-битного CPU с нуля на C (пошагово)
Создание 16-битного CPU с нуля на C может звучать пугающе, но это один из самых полезных и увлекательных проектов, за которые программист может взяться. В этой статье автор пошагово проведет вас через весь процесс создания рабочего эмулятора 16-битного CPU на C.
Что понадобится:
- Базовые знания программирования на C
- Компилятор C (GCC, Clang или MSVC)
- Любопытство и терпение
Прежде чем начнете писать код, позвольте представить вам аналогию, которая будет сопровождать вас на протяжении всей статьи.
Полный код: https://github.com/vixhal-baraiya/16bit-cpu
👉 @Cpportal
Создание 16-битного CPU с нуля на C может звучать пугающе, но это один из самых полезных и увлекательных проектов, за которые программист может взяться. В этой статье автор пошагово проведет вас через весь процесс создания рабочего эмулятора 16-битного CPU на C.
Что понадобится:
- Базовые знания программирования на C
- Компилятор C (GCC, Clang или MSVC)
- Любопытство и терпение
Прежде чем начнете писать код, позвольте представить вам аналогию, которая будет сопровождать вас на протяжении всей статьи.
Полный код: https://github.com/vixhal-baraiya/16bit-cpu
Please open Telegram to view this post
VIEW IN TELEGRAM
❤15
C++ совет дня
Наследование от стандартных типов это отличный способ реализовать аккуратные extension-методы.
Вот небольшой utility для
https://godbolt.org/z/TTxjaMK6o
👉 @Cpportal
Наследование от стандартных типов это отличный способ реализовать аккуратные extension-методы.
Вот небольшой utility для
std::string_view, который можно использовать, чтобы декларативно обрезать префиксы и суффиксы.https://godbolt.org/z/TTxjaMK6o
Please open Telegram to view this post
VIEW IN TELEGRAM
❤20🤔4💊2
Команда гениев собрала браузер на С++ вообще с нуля.
❤️ Ни строчки кода из Chrome. Ни из Firefox. Ничего не переиспользовано.
Ladybird Browser это:
→ на 100% независимый движок браузера
→ написан на C++ с нуля
→ не форк Chromium, как почти любой "новый" браузер, который ты видел
→ полностью open source, контрибьютить может кто угодно
Chrome сейчас по-тихому крутится под капотом у 95% интернета.
И это open source проект, который пытается это изменить.
59 200+ звезд на GitHub.
👉 @Cpportal
Ladybird Browser это:
→ на 100% независимый движок браузера
→ написан на C++ с нуля
→ не форк Chromium, как почти любой "новый" браузер, который ты видел
→ полностью open source, контрибьютить может кто угодно
Chrome сейчас по-тихому крутится под капотом у 95% интернета.
И это open source проект, который пытается это изменить.
59 200+ звезд на GitHub.
Please open Telegram to view this post
VIEW IN TELEGRAM
🤯26❤10🔥8👎2
getuid: Linux syscall #102
В Linux "кто ты" и "что тебе можно" часто разные вещи. У процесса есть две ключевые идентичности:
- Real UID (RUID): кто запустил процесс (ты)
- Effective UID (EUID): чьи права сейчас действуют (с привилегиями)
Это завязано на syscall getuid (№102 на x86_64).
Зачем нужны два UID?
Возьми команду
У бинарника
- Effective UID временно становится
- Real UID остается
Дальше программа вызывает
Ниже полный, запускаемый пример на C, который показывает идентичности текущего процесса. Если запустить обычно, оба UID совпадут. Если сменить владельца на root и выставить Set-UID бит (
getuid состоит в маленьком элитном клубе системных вызовов: он принимает 0 аргументов, гарантированно никогда не падает, и никогда не меняет
👉 @Cpportal
В Linux "кто ты" и "что тебе можно" часто разные вещи. У процесса есть две ключевые идентичности:
- Real UID (RUID): кто запустил процесс (ты)
- Effective UID (EUID): чьи права сейчас действуют (с привилегиями)
Это завязано на syscall getuid (№102 на x86_64).
Зачем нужны два UID?
Возьми команду
passwd. Она позволяет обычному пользователю поменять пароль. Но хэши паролей лежат в системном файле, который защищен и писать в него может только root. Как тогда обычный пользователь может записать что-то в файл, принадлежащий root?У бинарника
passwd включен специальный бит Set-UID. Когда ты его запускаешь:- Effective UID временно становится
0 (root), чтобы дать доступ на запись в файл.- Real UID остается
1000 (ты).Дальше программа вызывает
getuid(), чтобы проверить Real UID. По нему она понимает, root ты или обычный пользователь, и если ты обычный пользователь, по умолчанию выбирает твой аккаунт и гарантирует, что ты можешь менять только свою запись пароля. Без getuid() Set-UID программа имела бы root-права, но не имела бы способа определить, кто именно ее запустил.Ниже полный, запускаемый пример на C, который показывает идентичности текущего процесса. Если запустить обычно, оба UID совпадут. Если сменить владельца на root и выставить Set-UID бит (
chmod u+s), увидишь, что они расходятся.#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main() {
// Syscall 102 (x86_64): getuid (Кто меня запустил?)
uid_t ruid = getuid();
// Syscall 107 (x86_64): geteuid (От чьего имени я действую?)
uid_t euid = geteuid();
printf("Real UID (Identity): %d\n", ruid);
printf("Effective UID (Powers): %d\n", euid);
if (ruid != euid) {
printf("Process is running with borrowed privileges!\n");
} else {
printf("Process is running with standard privileges.\n");
}
return 0;
}
getuid состоит в маленьком элитном клубе системных вызовов: он принимает 0 аргументов, гарантированно никогда не падает, и никогда не меняет
errno.Please open Telegram to view this post
VIEW IN TELEGRAM
❤8👍1
Если тебе нравится перегрузка операторов в C++, то тебе зайдет и перегрузка пробелов! -
уверяет Страуструп
На самом деле, это первоапрельский прикол от Страуструпа, замаскированный под “предложение для C++2000”. Он сам прямо пишет, что это April Fool’s Joke.
Глянуть: https://www.stroustrup.com/whitespace98.pdf
Please open Telegram to view this post
VIEW IN TELEGRAM
😁24❤6
Не верь байкам.
C++ жив как никогда.
Вот реализация алгоритма решета для простых чисел на современном C++
👉 @Cpportal
C++ жив как никогда.
Вот реализация алгоритма решета для простых чисел на современном C++
#include <algorithm>
#include <print>
#include <ranges>
#include <vector>
#include <unordered_set>
// Returns primes in range: 3...2 * n + 2
std::vector<int> sieve_sundaram(int n) {
auto sieve =
std::views::iota(1, n + 1)
| std::ranges::to<std::unordered_set>();
auto sieved_out =
std::views::iota(1, n + 1)
| std::views::transform([n](int j) {
return
std::views::iota(j, n + 1)
| std::views::transform([j](int i) { return i + j + 2 * i * j; })
| std::views::take_while([n](int x) { return x <= n; });
})
| std::views::join;
for (int out : sieved_out)
sieve.erase(out);
auto primes = sieve
| std::views::transform([](int x) { return x * 2 + 1; })
| std::ranges::to<std::vector>();
std::ranges::sort(primes);
return primes;
}
int main() {
std::println("Primes: {}", sieve_sundaram(16));
// Primes: [3, 5, 7, 11, 13, 17, 19, 23, 29, 31]
}
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥21❤5💊1
Собрал JavaScript-рантайм на C, чтобы реально понять, как устроены Node.js, Bun и Deno
Jade Runtime покрывает:
▪️ интеграцию движка JavaScriptCore
▪️ архитектуру event loop на libuv
▪️ биндинги к нативному API
▪️ реализацию асинхронного I/O
Полное руководство с кодом - https://devlogs.xyz/blog/building-a-javaScript-runtime
👉 @Cpportal
Jade Runtime покрывает:
Полное руководство с кодом - https://devlogs.xyz/blog/building-a-javaScript-runtime
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤2🔥2👎1
This media is not supported in your browser
VIEW IN TELEGRAM
Большинство людей каждый день пользуются Linux.
Но реально мало кто понимает, что происходит после того, как ты нажал кнопку питания.
Вот какая последовательность шагов проходит Linux, прежде чем ты увидишь экран логина:
Нажали power → BIOS или UEFI инициализирует железо и гоняет POST.
Прошивка находит на диске загрузчик (bootloader).
Bootloader грузит в RAM Linux kernel и initramfs.
Ядро распаковывается и забирает управление CPU.
Инициализируются менеджер памяти и планировщик (scheduler).
Поднимаются драйверы устройств, чтобы общаться с железом.
Монтируется временная корневая файловая система из initramfs.
Стартует PID 1 как systemd, начинается userspace.
Системные сервисы и демоны запускаются по порядку.
Становится доступен login prompt или GUI.
Ты запускаешь команду, и она становится процессом с PID.
Процесс работает в user mode с ограниченными правами.
Когда нужен доступ к железу, делается системный вызов (system call).
CPU переключается из user mode в kernel mode.
Ядро валидирует запрос и выполняет его через драйверы.
Результат возвращается обратно в userspace.
Планировщик постоянно раздаёт процессам кванты CPU.
Виртуальная память изолирует и защищает процессы.
Файловая система даёт абстракцию данных поверх хранилища.
Сетевой стек обрабатывает пакеты внутри ядра.
Linux это ядро, которое координирует железо, процессы, память и безопасность через жёсткий контроль привилегий.
👉 @Cpportal
Но реально мало кто понимает, что происходит после того, как ты нажал кнопку питания.
Вот какая последовательность шагов проходит Linux, прежде чем ты увидишь экран логина:
Нажали power → BIOS или UEFI инициализирует железо и гоняет POST.
Прошивка находит на диске загрузчик (bootloader).
Bootloader грузит в RAM Linux kernel и initramfs.
Ядро распаковывается и забирает управление CPU.
Инициализируются менеджер памяти и планировщик (scheduler).
Поднимаются драйверы устройств, чтобы общаться с железом.
Монтируется временная корневая файловая система из initramfs.
Стартует PID 1 как systemd, начинается userspace.
Системные сервисы и демоны запускаются по порядку.
Становится доступен login prompt или GUI.
Ты запускаешь команду, и она становится процессом с PID.
Процесс работает в user mode с ограниченными правами.
Когда нужен доступ к железу, делается системный вызов (system call).
CPU переключается из user mode в kernel mode.
Ядро валидирует запрос и выполняет его через драйверы.
Результат возвращается обратно в userspace.
Планировщик постоянно раздаёт процессам кванты CPU.
Виртуальная память изолирует и защищает процессы.
Файловая система даёт абстракцию данных поверх хранилища.
Сетевой стек обрабатывает пакеты внутри ядра.
Linux это ядро, которое координирует железо, процессы, память и безопасность через жёсткий контроль привилегий.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍17❤5
Пример на C, как сымитировать "class"
👉 @Cpportal
#include <stdio.h>
typedef struct Counter Counter;
struct Counter {
int a;
int b;
void (*add_and_print)(Counter *);
};
void counter_add_and_print(Counter *c) {
c->a += c->b;
printf("значение = %d (добавили %d)\n", c->a, c->b);
}
int main(void) {
Counter c = { .a = 0, .b = 3, .add_and_print = counter_add_and_print };
c.add_and_print(&c);
c.add_and_print(&c);
return 0;
}
Please open Telegram to view this post
VIEW IN TELEGRAM
❤25👎5👍1
Классный инсайт про хеш-функцию djb2 из модуля Hash, который идёт в комплекте с Jai.
👉 @Cpportal
djb2_hash :: (s: string) -> u32 {
// Хеш djb2.
// Людям он, похоже, нравится, но для строк ассетов, которые мы часто видим,
// он выглядит довольно последовательным. Например, если у тебя имена ассетов
// "foo1", "foo2", "foo3", они все захешируются в последовательные слоты,
// потому что отличаются только последним символом, а эти символы идут подряд!
hash := HASH_INIT;
for 0..s.count-1 #no_abc #no_aoc {
hash = ((hash << 5) + hash) + s[it]; // hash * 33 + c
}
return hash;
}Please open Telegram to view this post
VIEW IN TELEGRAM
❤8
Люди думают, что C это строгий язык
А C позволяет между делом вплести
Не показывайте это питонистам, они расплачутся.🥱
👉 @Cpportal
А C позволяет между делом вплести
switch внутрь while, поломать пространство-время, и оно спокойно скомпилится.Не показывайте это питонистам, они расплачутся.
#include <stdio.h>
int main() {
int i = 0;
switch (i) {
case 0:
while (i < 3) {
printf("%d", i);
case 1:
i++;
}
}
return 0;
}
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔28👍9❤5🤣3🔥1
setgid - системный вызов Linux #106
Идентичность процесса в Linux это иллюзия.
Ядро видит потоки.
POSIX ожидает процессы.
Возьмем
Он выставляет effective group ID (эффективный GID) у вызывающего процесса. Если вызывающий привилегирован (имеет capability
Если определить "процесс" как группу потоков, которые шарят память, ядро не обязано заставлять их шарить credentials. Если обойти стандартную библиотеку и дернуть сырой syscall в многопоточном приложении, изменится только вызывающий поток. В итоге получишь процесс с раздвоенной идентичностью: один поток уже ограничен, а фоновые worker-потоки продолжают работать со старыми групповыми привилегиями.
Чтобы это исправить, реализация потоков glibc NPTL делает синхронизационный танец. Когда ты вызываешь
Ниже C-программа, которая демонстрирует вызов raw syscall.
Это идеальный пример того, как механизм в ядре ОС отличается от спецификации POSIX , и какой “клей” нужен, чтобы все это склеить.
👉 @Cpportal
Идентичность процесса в Linux это иллюзия.
Ядро видит потоки.
POSIX ожидает процессы.
Возьмем
setgid (syscall #106 на x86_64).Он выставляет effective group ID (эффективный GID) у вызывающего процесса. Если вызывающий привилегирован (имеет capability
CAP_SETGID), он также выставляет real GID (реальный GID) и saved set-group-ID (сохраненный setgid). Чаще всего это видно, когда сервер вроде Nginx сбрасывает привилегии с root на менее привилегированную группу ради безопасности. Обычно ты предполагаешь, что это относится ко всему процессу. Но вот подвох: в Linux ядро ведет учет credentials (учетных данных) на уровне потока.Если определить "процесс" как группу потоков, которые шарят память, ядро не обязано заставлять их шарить credentials. Если обойти стандартную библиотеку и дернуть сырой syscall в многопоточном приложении, изменится только вызывающий поток. В итоге получишь процесс с раздвоенной идентичностью: один поток уже ограничен, а фоновые worker-потоки продолжают работать со старыми групповыми привилегиями.
Чтобы это исправить, реализация потоков glibc NPTL делает синхронизационный танец. Когда ты вызываешь
setgid(), обертка не просто вызывает сырой syscall. Она шлет сигнал каждому другому потоку в процессе. Обработчик сигнала в каждом потоке затем вызывает тот же syscall для самого себя, обновляя свои credentials, чтобы они совпали. glibc ждет, пока все потоки закончат обновление, и только потом возвращает управление. Так она выполняет семантику POSIX, где требуется, чтобы все потоки в процессе имели одинаковые credentials.Ниже C-программа, которая демонстрирует вызов raw syscall.
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <errno.h>
int main() {
// We will try to switch to GID 1000
gid_t target_gid = 1000;
printf("Current GID: %d\n", getgid());
// We use the raw syscall (setgid)
// On a multi-threaded app, this is dangerous!
// It only updates the credential for the CURRENT thread.
printf("Calling raw setgid syscall...\n");
long ret = syscall(SYS_setgid, target_gid);
if (ret == 0) {
printf("Success! New GID: %d\n", getgid());
} else {
perror("Syscall failed (are you root?)");
}
return 0;
}
Это идеальный пример того, как механизм в ядре ОС отличается от спецификации POSIX , и какой “клей” нужен, чтобы все это склеить.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤10