📜 العقد المقدس (Qet4 API)
مكتبة qet4_api هي الواجهة الثابتة الوحيدة بين النواة والعالم الخارجي. كل برنامج يعمل في فضاء المستخدم (Userspace) سواء كان سائقاً (Driver) أو خدمة (Service) يعتمد فقط على هذا العقد.
القاعدة الذهبية: أرقام الاستدعاءات وتوقيعاتها لا تتغير أبداً.
1. جدول استدعاءات النظام الكامل (Syscalls)
يحتوي العقد حالياً على 12 استدعاءً ثابتاً:
| الرقم | الاسم | الوظيفة | المعاملات (arg1 ➔ arg4) | القيمة المرجعة | الصلاحية / ملاحظات |
|---|---|---|---|---|---|
0 | SYS_EXIT | إنهاء العملية الحالية | - | - | للجميع |
1 | SYS_WRITE | كتابة نص على الشاشة | arg1: مؤشر النص, arg2: الطول | - | للجميع |
2 | SYS_SEND | إرسال رسالة IPC | arg1: رقم المنفذ, arg2: مؤشر, arg3: طول | - | للجميع |
3 | SYS_RECV | استقبال رسالة (يحظر) | arg1: مؤشر, arg2: حجم المخزن | طول الرسالة الفعلي | للجميع |
4 | SYS_YIELD | التنازل عن المعالج | - | - | للجميع |
5 | SYS_SPAWN | إطلاق عملية جديدة | arg1: مؤشر ELF, arg2: الحجم, arg4: مؤشر لمنفذ الرد | PID العملية | PID 1 (init) فقط |
6 | SYS_GET_SENDER | جلب PID المُرسل | - | PID المُرسل | للجميع |
7 | SYS_DRAW | رسم حرف على VGA | arg1: col, arg2: row, arg3: بيانات الحرف (حرف+لون) | - | للجميع |
8 | SYS_READ_KEY | قراءة كيبورد | - | Scancode | input_server فقط |
9 | SYS_GET_FB_INFO | معلومات الـ Framebuffer | arg1: مؤشر لـ FbInfo | != u64::MAX للنجاح | للجميع |
10 | SYS_DRAW_FB | رسم Pixels | arg1: مؤشر الـ src, arg2: offset, arg3: len | 0 للنجاح | للجميع |
11 | SYS_DRAW_CHAR_FB | رسم حرف على FB | arg1: (حرف+احداثيات), arg2: fg, arg3: bg | - | للجميع |
98 | SYS_ARG_ECHO | اختبار تمرير المسجلات | arg1 ➔ arg4 | القيمة المدخلة | اختبار فقط (abi-selftest) |
99 | SYS_GET_SWITCH_COUNT | عداد التبديلات | - | عدد التبديلات | اختبار فقط (abi-selftest) |
ملاحظة: يتم تفعيل الاستدعاءات الاختبارية (98 و 99) فقط عند تجميع المكتبة بعلم
abi-selftestوالنواة بعلمabi-test-input.
2. اتفاقية الاستدعاء (Syscall ABI)
تستخدم النواة مسجلات محددة لتمرير القيم. من الضروري جداً الالتزام بالـ Clobbers (المسجلات التي تتلفها النواة)، وإلا ستفقد قيمك بشكل عشوائي!
rax= رقم الاستدعاء (وخروج القيمة المرجعة).rdi= المعامل الأول (arg1).rsi= المعامل الثاني (arg2).rdx= المعامل الثالث (arg3).r10= المعامل الرابع (arg4). (ملاحظة: نستخدمr10بدلاً منrcxلأن تعليمةsyscallتكتب عنوان العودة فيrcx).- المسجلات المتخربة (Clobbers):
rcx,r11,r8,r9,r10. النواة تقوم بترجمة الاتفاقية وتكتب فوق هذه المسجلات.
إليك الكود الفعلي من المكتبة لدالة syscall3 والذي يوضح الاتفاقية بأكملها:
#[inline(always)]
fn syscall3(num: u64, arg1: u64, arg2: u64, arg3: u64) -> u64 {
let ret: u64;
unsafe {
core::arch::asm!(
"syscall",
inlateout("rax") num => ret,
in("rdi") arg1,
in("rsi") arg2,
in("rdx") arg3,
out("rcx") _,
out("r11") _,
out("r8") _,
out("r9") _,
out("r10") _,
options(nostack),
);
}
ret
}3. جدول أكواد الأخطاء (Error Codes)
النواة تعيد القيم السالبة كرسائل أخطاء (نظراً لعدم استخدام Result في الأسمبلي المباشر). تعني القيمة نفس الشيء أو شيئاً مختلفاً حسب الاستدعاء:
| الكود المُرجع | SYS_SEND (إرسال) | SYS_SPAWN (إطلاق عملية) | SYS_READ_KEY (الكيبورد) |
|---|---|---|---|
u64::MAX | عنوان خارج الذاكرة / رسالة أكبر من 256 | خطأ عام | فشل عام |
u64::MAX - 1 | لا تملك صلاحية على المنفذ | EFAULT (مؤشر المنفذ فاسد) | EACCES (ليس input_server) |
u64::MAX - 2 | Mailbox Full (صندوق المستلم ممتلئ) | EPERM (عملية غير PID 1) | - |
4. إطلاق العمليات (SYS_SPAWN)
المثال التالي يوضح كيفية استدعاء دالة spawn من المصدر الفعلي، مع ملاحظة تمرير المعامل الرابع كعنوان port_out ليكتب عليه الكرنل:
/// إطلاق عملية مستخدم جديدة من بيانات ELF مدمجة
///
/// ⚠️ يتطلب صلاحية PID 1 (init) — EPERM لأي عملية أخرى
///
/// - arg4 = r10 = عنوان `port_out` → الـ kernel يكتب فيه رقم المنفذ
/// - النجاح: Ok((pid, port))
/// - الفشل: Err(errno): MAX=عام، MAX-1=EFAULT، MAX-2=EPERM
pub fn spawn(elf_data: &[u8]) -> Result<(u64, usize), u64> {
let mut port_out: u64 = u64::MAX;
let mut ret = SYS_SPAWN;
unsafe {
core::arch::asm!(
"syscall",
inlateout("rax") ret,
in("rdi") elf_data.as_ptr() as u64,
in("rsi") elf_data.len() as u64,
in("r10") &mut port_out as *mut u64 as u64, // arg4
out("rcx") _,
out("r11") _,
options(nostack),
);
}
if ret >= u64::MAX - 2 {
return Err(ret);
}
if port_out == u64::MAX {
return Err(u64::MAX);
}
Ok((ret, port_out as usize))
}