vDSO(7)

الاسم
vdso - نظرة عامة على الكائن المشترك الديناميكي ELF الافتراضي

موجز
#include <sys/auxv.h>

void *vdso = (uintptr_t) getauxval(AT_SYSINFO_EHDR);

الوصف
إن "vDSO" (الكائن المشترك الديناميكي الافتراضي) هو مكتبة مشتركة صغيرة يقوم النواة آليًا بوصلها في مساحة العنوان لجميع تطبيقات مساحة المستخدم. لا تحتاج التطبيقات عادةً إلى الاهتمام بهذه التفاصيل حيث يتم استدعاء vDSO بشكل شائع بواسطة مكتبة C. بهذه الطريقة يمكنك البرمجة بالطريقة العادية باستخدام الدوال القياسية وستتولى مكتبة C استخدام أي وظيفة متاحة عبر vDSO.

لماذا يوجد vDSO أصلًا؟ هناك بعض استدعاءات النظام التي يوفرها النواة والتي ينتهي الأمر باستخدامها بشكل متكرر من قبل كود مساحة المستخدم، لدرجة أن هذه الاستدعاءات يمكن أن تهيمن على الأداء الكلي. هذا يرجع إلى كل من تكرار الاستدعاء بالإضافة إلى الحمل الزائد لتبديل السياق الناتج عن الخروج من مساحة المستخدم والدخول إلى النواة.

باقي هذه الوثائق موجهة للمهتمين و/أو كُتّاب مكتبة C بدلاً من المطورين العامين. إذا كنت تحاول استدعاء vDSO في تطبيقك الخاص بدلاً من استخدام مكتبة C، فمن المرجح أنك تفعل ذلك بشكل خاطئ.

  خلفية مثال
يمكن أن يكون إجراء استدعاءات النظام بطيئًا. في أنظمة x86 ذات 32 بت، يمكنك تشغيل مقاطعة برمجية (int $0x80) لإخبار النواة بأنك ترغب في إجراء استدعاء نظام. ومع ذلك، فإن هذه التعليمات مكلفة: فهي تمر عبر مسارات معالجة المقاطعة الكاملة في البرنامج الصغير للمعالج وكذلك في النواة. تحتوي المعالجات الأحدث على تعليمات أسرع (ولكنها غير متوافقة مع الإصدارات السابقة) لبدء استدعاءات النظام. بدلاً من مطالبة مكتبة C بمعرفة ما إذا كانت هذه الوظيفة متاحة في وقت التشغيل، يمكن لمكتبة C استخدام الدوال التي توفرها النواة في vDSO.

لاحظ أن المصطلحات يمكن أن تكون مربكة. في أنظمة x86، تُسمى دالة vDSO المستخدمة لتحديد الطريقة المفضلة لإجراء استدعاء نظام "__kernel_vsyscall"، ولكن على x86-64، يشير المصطلح "vsyscall" أيضًا إلى طريقة قديمة لسؤال النواة عن الوقت الحالي أو عن وحدة المعالجة المركزية التي يعمل عليها المتصل.

أحد استدعاءات النظام المستخدمة بشكل متكرر هو gettimeofday(2). يتم استدعاء استدعاء النظام هذا مباشرة بواسطة تطبيقات مساحة المستخدم وكذلك بشكل غير مباشر بواسطة مكتبة C. فكر في الطوابع الزمنية أو حلقات التوقيت أو الاستقصاء—كل هذه تحتاج بشكل متكرر إلى معرفة الوقت الحالي. هذه المعلومات أيضًا ليست سرية—أي تطبيق في أي وضع صلاحية (الجذر أو أي مستخدم غير مميز) سيحصل على نفس الإجابة. وبالتالي ترتب النواة لوضع المعلومات المطلوبة للإجابة على هذا السؤال في ذاكرة يمكن للعملية الوصول إليها. الآن يتغير استدعاء gettimeofday(2) من استدعاء نظام إلى استدعاء دالة عادي وبعض الوصولات إلى الذاكرة.

  إيجاد vDSO
يتم تمرير العنوان الأساسي لـ vDSO (إذا كان موجودًا) بواسطة النواة إلى كل برنامج في المتجه المساعد الأولي (انظر getauxval(3))، عبر الوسم AT_SYSINFO_EHDR.

يجب ألا تفترض أن vDSO موصول في أي موقع معين في خريطة ذاكرة المستخدم. عادةً ما يتم عشوائية العنوان الأساسي في وقت التشغيل في كل مرة يتم فيها إنشاء صورة عملية جديدة (في وقت execve(2)). يتم ذلك لأسباب أمنية، لمنع هجمات "العودة إلى libc".

بالنسبة لبعض البنى، يوجد أيضًا وسم AT_SYSINFO. يُستخدم هذا فقط لتحديد موقع نقطة دخول vsyscall وغالبًا ما يتم حذفه أو تعيينه إلى 0 (بمعنى أنه غير متاح). هذا الوسم هو عودة إلى العمل الأولي لـ vDSO (انظر التاريخ أدناه) ويجب تجنب استخدامه.

  تنسيق الملف
نظرًا لأن vDSO هو صورة ELF مكتملة التكوين، يمكنك إجراء عمليات بحث عن الرموز عليه. هذا يسمح بإضافة رموز جديدة مع إصدارات النواة الأحدث، ويسمح لمكتبة C باكتشاف الوظائف المتاحة في وقت التشغيل عند التشغيل تحت إصدارات نواة مختلفة. غالبًا ما تقوم مكتبة C بالاكتشاف مع الاستدعاء الأول ثم تخبئ النتيجة للاستدعاءات اللاحقة.

جميع الرموز أيضًا مُرقّمة الإصدارات (باستخدام تنسيق إصدار GNU). هذا يسمح للنواة بتحديث توقيع الدالة دون كسر التوافق مع الإصدارات السابقة. هذا يعني تغيير الوسائط التي تقبلها الدالة وكذلك القيمة المُرجعة. وبالتالي، عند البحث عن رمز في vDSO، يجب عليك دائمًا تضمين الإصدار لمطابقة ABI الذي تتوقعه.

عادةً ما يتبع vDSO اصطلاح التسمية بإضافة بادئة لجميع الرموز بـ "__vdso_" أو "__kernel_" لتمييزها عن الرموز القياسية الأخرى. على سبيل المثال، تُسمى دالة "gettimeofday" "__vdso_gettimeofday".

تستخدم اصطلاحات استدعاء C القياسية عند استدعاء أي من هذه الدوال. لا داعي للقلق بشأن سلوك السجلات أو المكدس الغريب.

ملاحظات

  المصدر
عند تجميع النواة، ستقوم آليًا بتجميع وربط كود vDSO لك. ستجده غالبًا تحت الدليل الخاص بالبنية:

    find arch/$ARCH/ -name '*vdso*.so*' -o -name '*gate*.so*'

  أسماء vDSO
يختلف اسم vDSO عبر البنى. غالبًا ما يظهر في أشياء مثل مخرجات ldd(1) الخاصة بـ glibc. لا ينبغي أن يهم الاسم الدقيق لأي كود، لذا لا تقم بترميزه بشكل ثابت.

ABI المستخدم | اسم vDSO
aarch64 | linux-vdso.so.1
arm | linux-vdso.so.1
ia64 | linux-gate.so.1
mips | linux-vdso.so.1
ppc/32 | linux-vdso32.so.1
ppc/64 | linux-vdso64.so.1
riscv | linux-vdso.so.1
s390 | linux-vdso32.so.1
s390x | linux-vdso64.so.1
sh | linux-gate.so.1
i386 | linux-gate.so.1
x86-64 | linux-vdso.so.1
x86/x32 | linux-vdso.so.1

  strace(1)، seccomp(2)، و vDSO
عند تتبع استدعاءات النظام باستخدام strace(1)، فإن الرموز (استدعاءات النظام) التي يصدرها vDSO لن تظهر في مخرجات التتبع. كذلك لن تكون استدعاءات النظام تلك مرئية لمرشحات seccomp(2).

ملاحظات خاصة بالبنية
توفر الأقسام الفرعية أدناه ملاحظات خاصة بالبنية حول vDSO.

لاحظ أن vDSO المستخدم يعتمد على ABI لرمز مساحة المستخدم وليس على ABI للنواة. وبالتالي، على سبيل المثال، عند تشغيل ملف ثنائي ELF 32 بت i386، ستحصل على نفس vDSO بغض النظر عن تشغيله تحت نواة i386 32 بت أو تحت نواة x86-64 64 بت. لذلك، يجب استخدام اسم ABI لمساحة المستخدم لتحديد أي من الأقسام أدناه ذو صلة.

  دوال ARM
يسرد الجدول أدناه الرموز التي يصدرها vDSO.

رمز | version
__vdso_gettimeofday | LINUX_2.6 (مُصدر منذ Linux 4.1)
__vdso_clock_gettime | LINUX_2.6 (مُصدر منذ Linux 4.1)

بالإضافة إلى ذلك، يحتوي منفذ ARM على صفحة كود مليئة بدوال الأدوات المساعدة. نظرًا لأنها مجرد صفحة كود خام، فلا توجد معلومات ELF للقيام بعمليات بحث عن الرموز أو إصدارات. لكنها توفر دعمًا لإصدارات مختلفة.

للحصول على معلومات حول صفحة الكود هذه، من الأفضل الرجوع إلى توثيق النواة لأنه مفصل للغاية ويغطي كل ما تحتاج معرفته: Documentation/arm/kernel_user_helpers.rst.

  دوال aarch64
يسرد الجدول أدناه الرموز التي يصدرها vDSO.

رمز | version
__kernel_rt_sigreturn | LINUX_2.6.39
__kernel_gettimeofday | LINUX_2.6.39
__kernel_clock_gettime | LINUX_2.6.39
__kernel_clock_getres | LINUX_2.6.39

  دوال bfin (Blackfin) (تمت إزالة المنفذ في Linux 4.17)
نظرًا لأن وحدة المعالجة المركزية هذه تفتقر إلى وحدة إدارة الذاكرة (MMU)، فإنها لا تنشئ vDSO بالمعنى التقليدي. بدلاً من ذلك، تقوم في وقت الإقلاع بتعيين بضع دوال خام إلى موقع ثابت في الذاكرة. ثم تستدعي تطبيقات مساحة المستخدم مباشرة تلك المنطقة. لا يوجد توفير للتوافق العكسي بخلاف استنشاق الأوبكود الخام، ولكن نظرًا لأنها وحدة معالجة مركزية مدمجة، يمكنها الإفلات من الأمور—بعض تنسيقات الكائنات التي تشغلها ليست حتى مبنية على ELF (إنها bFLT/FLAT).

للحصول على معلومات حول صفحة الكود هذه، من الأفضل الرجوع إلى التوثيق العام:
http://docs.blackfin.uclinux.org/doku.php?id=linux-kernel:fixed-code

  دوال mips
يسرد الجدول أدناه الرموز التي يصدرها vDSO.

رمز | version
__kernel_gettimeofday | LINUX_2.6 (مُصدر منذ Linux 4.4)
__kernel_clock_gettime | LINUX_2.6 (مُصدر منذ Linux 4.4)

  دوال ia64 (Itanium)
يسرد الجدول أدناه الرموز التي يصدرها vDSO.

رمز | version
__kernel_sigtramp | LINUX_2.5
__kernel_syscall_via_break | LINUX_2.5
__kernel_syscall_via_epc | LINUX_2.5

منفذ Itanium صعب بعض الشيء. بالإضافة إلى vDSO أعلاه، يحتوي أيضًا على "استدعاءات نظام خفيفة الوزن" (تُعرف أيضًا باسم "استدعاءات النظام السريعة" أو "fsys"). يمكنك استدعاؤها عبر مساعد vDSO __kernel_syscall_via_epc. استدعاءات النظام المدرجة هنا لها نفس الدلالات كما لو استدعيتها مباشرة عبر syscall(2)، لذا ارجع إلى التوثيق ذي الصلة لكل منها. يسرد الجدول أدناه الدوال المتاحة عبر هذه الآلية.

function
clock_gettime
getcpu
getpid
getppid
gettimeofday
set_tid_address

  دوال parisc (hppa)
يحتوي منفذ parisc على صفحة كود بدوال أدوات مساعدة تُسمى صفحة بوابة. بدلاً من استخدام نهج المتجه المساعد ELF العادي، يمرر عنوان الصفحة إلى العملية عبر سجل SR2. أذونات الصفحة تجعل مجرد تنفيذ تلك العناوين ينفذ آليًا بامتيازات النواة وليس في مساحة المستخدم. يتم ذلك لمطابقة طريقة عمل HP-UX.

نظرًا لأنها مجرد صفحة كود خام، فلا توجد معلومات ELF للقيام بعمليات بحث عن الرموز أو إصدارات. ببساطة، استدعِ الإزاحة المناسبة عبر تعليمة الفرع، على سبيل المثال:

    ble <offset>(%sr2, %r0)

إزاحة | function
00b0 | lws_entry (عمليات CAS)
00e0 | set_thread_pointer (مستخدم بواسطة glibc)
0100 | linux_gateway_entry (syscall)

  دوال ppc/32
يسرد الجدول أدناه الرموز التي يصدرها vDSO. الدوال الموسومة بـ * متاحة فقط عندما يكون النواة نواة PowerPC64 (64-بت).

رمز | version
__kernel_clock_getres | LINUX_2.6.15
__kernel_clock_gettime | LINUX_2.6.15
__kernel_clock_gettime64 | LINUX_5.11
__kernel_datapage_offset | LINUX_2.6.15
__kernel_get_syscall_map | LINUX_2.6.15
__kernel_get_tbfreq | LINUX_2.6.15
__kernel_getcpu * | LINUX_2.6.15
__kernel_gettimeofday | LINUX_2.6.15
__kernel_sigtramp_rt32 | LINUX_2.6.15
__kernel_sigtramp32 | LINUX_2.6.15
__kernel_sync_dicache | LINUX_2.6.15
__kernel_sync_dicache_p5 | LINUX_2.6.15

قبل لينكس 5.6، الساعات CLOCK_REALTIME_COARSE و CLOCK_MONOTONIC_COARSE غير مدعومة بواسطة واجهتي __kernel_clock_getres و __kernel_clock_gettime؛ ترجع النواة إلى استدعاء النظام الحقيقي.

  دوال ppc/64
يسرد الجدول أدناه الرموز التي يصدرها vDSO.

رمز | version
__kernel_clock_getres | LINUX_2.6.15
__kernel_clock_gettime | LINUX_2.6.15
__kernel_datapage_offset | LINUX_2.6.15
__kernel_get_syscall_map | LINUX_2.6.15
__kernel_get_tbfreq | LINUX_2.6.15
__kernel_getcpu | LINUX_2.6.15
__kernel_gettimeofday | LINUX_2.6.15
__kernel_sigtramp_rt64 | LINUX_2.6.15
__kernel_sync_dicache | LINUX_2.6.15
__kernel_sync_dicache_p5 | LINUX_2.6.15

قبل لينكس 4.16، الساعات CLOCK_REALTIME_COARSE و CLOCK_MONOTONIC_COARSE غير مدعومة بواسطة واجهتي __kernel_clock_getres و __kernel_clock_gettime؛ ترجع النواة إلى استدعاء النظام الحقيقي.

  دوال riscv
يسرد الجدول أدناه الرموز التي يصدرها vDSO.

رمز | version
__vdso_rt_sigreturn | LINUX_4.15
__vdso_gettimeofday | LINUX_4.15
__vdso_clock_gettime | LINUX_4.15
__vdso_clock_getres | LINUX_4.15
__vdso_getcpu | LINUX_4.15
__vdso_flush_icache | LINUX_4.15

  دوال s390
يسرد الجدول أدناه الرموز التي يصدرها vDSO.

رمز | version
__kernel_clock_getres | LINUX_2.6.29
__kernel_clock_gettime | LINUX_2.6.29
__kernel_gettimeofday | LINUX_2.6.29

  دوال s390x
يسرد الجدول أدناه الرموز التي يصدرها vDSO.

رمز | version
__kernel_clock_getres | LINUX_2.6.29
__kernel_clock_gettime | LINUX_2.6.29
__kernel_gettimeofday | LINUX_2.6.29

  دوال sh (SuperH)
يسرد الجدول أدناه الرموز التي يصدرها vDSO.

رمز | version
__kernel_rt_sigreturn | LINUX_2.6
__kernel_sigreturn | LINUX_2.6
__kernel_vsyscall | LINUX_2.6

  دوال i386
يسرد الجدول أدناه الرموز التي يصدرها vDSO.

رمز | version
__kernel_sigreturn | LINUX_2.5
__kernel_rt_sigreturn | LINUX_2.5
__kernel_vsyscall | LINUX_2.5
__vdso_clock_gettime | LINUX_2.6 (مُصدر منذ لينكس 3.15)
__vdso_gettimeofday | LINUX_2.6 (مُصدر منذ لينكس 3.15)
__vdso_time | LINUX_2.6 (مُصدر منذ لينكس 3.15)

  دوال x86-64
يسرد الجدول أدناه الرموز التي يصدرها vDSO. جميع هذه الرموز متاحة أيضًا بدون البادئة "__vdso_"، لكن يجب تجاهل تلك والالتزام بالأسماء أدناه.

رمز | version
__vdso_clock_gettime | LINUX_2.6
__vdso_getcpu | LINUX_2.6
__vdso_gettimeofday | LINUX_2.6
__vdso_time | LINUX_2.6

  دوال x86/x32
يسرد الجدول أدناه الرموز التي يصدرها vDSO.

رمز | version
__vdso_clock_gettime | LINUX_2.6
__vdso_getcpu | LINUX_2.6
__vdso_gettimeofday | LINUX_2.6
__vdso_time | LINUX_2.6

  السجل
كان vDSO في الأصل مجرد دالة واحدة—vsyscall. في النوى الأقدم، قد ترى هذا الاسم في خريطة ذاكرة عملية بدلاً من "vdso". بمرور الوقت، أدرك الأشخاص أن هذه الآلية كانت طريقة رائعة لتمرير وظائف أكثر إلى مساحة المستخدم، لذا أُعيد تصورها كـ vDSO بالتنسيق الحالي.

انظر أيضًا
syscalls(2)، getauxval(3)، proc(5)

المستندات والأمثلة والشفرة المصدرية في شجرة شفرة مصدر لينكس:

    Documentation/ABI/stable/vdso
    Documentation/ia64/fsys.rst
    Documentation/vDSO/* (يتضمن أمثلة لاستعمال vDSO)

find arch/ -iname '*vdso*' -o -iname '*gate*'

http://articles.manugarg.com/systemcallinlinux2_6.html

https://lwn.net/Articles/446528/

http://www.linuxjournal.com/content/creating-vdso-colonels-other-chicken

http://www.trilithium.com/johan/2005/08/linux-gate/

ترجمة
تُرجمت هذه الصفحة من الدليل بواسطة زايد السعيدي <zayed.alsaidi@gmail.com>

هذه الترجمة هي وثيقة مجانية؛ راجع رخصة جنو العامة الإصدار 3 (https://www.gnu.org/licenses/gpl-3.0.html) أو ما بعده للاطلاع على شروط حقوق النشر. لا توجد أي ضمانات.

إذا وجدت أي أخطاء في ترجمة صفحة الدليل هذه، يرجى إرسال بريد إلكتروني إلى قائمة بريد المترجمين: kde-l10n-ar@kde.org.