Product SiteDocumentation Site

9.11. التوصيل الساخن: hotplug

9.11.1. مقدمة

يعالج نظام النواة الفرعي hotplug إضافة وإزالة الأجهزة ديناميكياً، عبر تحميل التعاريف المناسبة وعبر إنشاء ملفات الأجهزة الموافقة (بمساعدة udevd). في الأجهزة الحديثة والحوسبة الظاهرية، يمكن توصيل أي شيء تقريباً بشكل ساخن: من ملحقات USB/PCMCIA/IEEE 1394 الشائعة إلى أقراص SATA الصلبة، وصولاً إلى المعالجات والذواكر أيضاً.
لدى النواة قاعدة بيانات تربط رقم تعريف (ID) كل جهاز مع برنامج التعريف المطلوب. تستخدم قاعدة البيانات هذه أثناء الإقلاع لتحميل جميع تعاريف الأجهزة الملحقة التي تكتشف على النواقل المختلفة، وأيضاً عند توصيل جهاز إضافي يدعم التوصيل الساخن. ترسل رسالة إلى udevd فور جاهزية القطعة للاستعمال، حتى يتمكن من إنشاء المدخلة الموافقة في /dev/.

9.11.2. مشكلة التسمية

قبل ظهور الاتصالات الساخنة، كان من السهل تعيين أسماء ثابتة للأجهزة. كانت تعتمد تسميتها ببساطة على موقع الجهاز على الناقل الخاص به. لكن هذا غير ممكن عندما تتحرك هذه الأجهزة على النواقل. من الحالات النموذجية استخدام الكاميرا الرقمية أو مفتاح USB، حيث يظهر كل منهما للحاسوب على أنه قرص تخزيني. ربما كان اسم الجهاز المتصل أولاً /dev/sdb والمتصل ثانياً /dev/sdc (حيث يمثل /dev/sda القرص الصلب للحاسوب). أسماء الأجهزة غير ثابتة؛ بل تعتمد على ترتيب توصيل الأجهزة.
بالإضافة لذلك، يزداد عدد التعاريف التي تستخدم قيماً ديناميكية لأرقام major/minor للأجهزة، ما يحول دون إمكانية تعيين مدخلات ثابتة للأجهزة المعنية، بما أن هذه الخصائص الأساسية قد تختلف بعد إعادة الإقلاع.
لقد أنشئ udev لحل هذه المشكلة تحديداً.

9.11.3. طريقة عمل udev

عندما تُـنَـبِّه النواةُ udev إلى ظهور جهاز جديد، يجمع udev المعلومات المختلفة عن الجهاز المعني باستطلاع المدخلات المناسبة في /sys/، خصوصاً المدخلات التي تعرف الجهاز بشكل فريد (عنوان MAC للبطاقات الشبكية، الأرقام التسلسلية لبعض أجهزة USB، الخ).
بعد جمع كل هذه المعلومات، يتحقق udev من القواعد المخزنة في /etc/udev/rules.d/ و/lib/udev/rules.d/. من خلال هذه العملية يقرر كيفية تسمية الجهاز، والروابط الرمزية التي سينشئها له (لإعطائه أسماء بديلة)، والأوامر التي يجب تنفيذها. يتم استطلاع جميع الملفات، و تقيَّم جميع القواعد تسلسلياً (إلا عندما يحوي الملف توجيهات ”GOTO“). لذلك قد ترتبط عدة قواعد بحدث معين.
صيغة ملفات القواعد بسيطة جداً: كل سطر يحوي معايير اختيار وقيم متغيرات. تستخدم الأولى لتحديد الأحداث التي يجب الاستجابة لها، وتُعرِّف الأخيرة الأفعال التي يجب اتخاذها. تفصل كافة المكونات عن بعضها بفواصل، وتُفَرِّق المعاملات ضمنياً بين معايير الاختيار (التي تحوي معاملات مقارنة، مثل == أو !=) وبين توجيهات الإسناد (التي تستخدم معاملات مثل = أو += أو :=).
تستخدم معاملات المقارنة على المتغيرات التالية:
  • KERNEL: الاسم الذي تعينه النواة للجهاز؛
  • ACTION: الفعل الموافق للحدث (”add“ عندما تضاف القطعة، ”remove“ عند إزالتها)؛
  • DEVPATH: مسار مدخلة القطعة في /sys/؛
  • SUBSYSTEM: نظام النواة الفرعي الذي وَلَّدَ الطلب (هناك العديد من هذه الأنظمة، لكن من بعض الأمثلة عليها ”usb“،‏ ”ide“،‏ ”net“،‏ ”firmware“، الخ)؛
  • ATTR{attribute}: محتويات الملف attribute في المجلد /sys/$devpath/ الخاص بالجهاز. هنا تجد عنوان MAC وغيره من المعرفات الخاصة بالناقل؛
  • KERNELS وSUBSYSTEMS وATTRS{attributes} هي صيغ أخرى تُستَخدَمُ لمطابقة الخيارات المختلفة لأحد الأجهزة الآباء للجهاز الحالي؛
  • PROGRAM: يوكل الاختبار إلى البرنامج المشار إليه (true إذا أعاد القيمة 0، false فيما عدا ذلك). تخزن محتويات خرج البرنامج القياسي بحيث يستطيع اختبار RESULT إعادة استخدامها؛
  • RESULT: يجري اختبارات على الخرج القياسي المخزن أثناء آخر استدعاء لـ PROGRAM.
يمكن أن تستخدم التعابير المنتظمة لمطابقة عدة قيم في الوقت نفسه في المعاملات على اليمين. مثلاً، تطابق النجمة * أيَّ سلسلة (وحتى السلسلة الفارغة)؛ وتطابق علامة الاستفهام ? أي محرف، وتطابق الأقواس المربعة [] مجموعة من المحارف المذكورة بين القوسين المربعين (أو عكس تلك المحارف إذا كان المحرف الأول علامة تعجب، وتكتب المجالات المستمرة بالشكل a-z).
أما بخصوص معاملات الإسناد، فيسند المعامل = قيمة جديدة (ويستبدل القيمة الحالية)؛ في حال تطبيقه على لائحة، سوف يفرغها ويسند لها القيمة المعطاة فقط. المعامل := له نفس الأثر، لكنه يمنع التعديلات اللاحقة على المتغير نفسه. أما بالنسبة للمعامل +=، فهو يضيف عنصراً إلى اللائحة. يمكن تعديل المتغيرات التالية:
  • NAME: اسم ملف الجهاز الذي سينشأ في /dev/. تؤخذ عملية الإسناد الأولى بعين الاعتبار فقط؛ وتهمل الإسنادات الأخرى؛
  • SYMLINK: لائحة بالروابط الرمزية التي تشير إلى الجهاز نفسه؛
  • تعرف المتغيرات OWNER وGROUP وMODE المستخدم المالك والمجموعة المالكة للجهاز، بالإضافة إلى الصلاحيات المتعلقة به؛
  • RUN: لائحة بالبرامج التي يجب تنفيذها استجابة لهذا الحدث.
يمكن استخدام عدد من البدائل في القيم التي تسند إلى هذه المتغيرات:
  • $kernel أو %k: تكافئ KERNEL؛
  • $number أو %n: لترقيم الجهاز، مثلاً، بالنسبة للجهاز sda3، ستكون قيمته ”3“؛
  • $devpath أو %p: تكافئ DEVPATH؛
  • $attr{attribute} أو %s{attribute}: تكافئ ATTRS{attribute}؛
  • $major أو %M: رقم النواة الكبير للجهاز؛
  • $minor أو %m: رقم النواة الصغير للجهاز؛
  • $result أو %c: الخرج النصي لآخر برنامج استدعي عبر PROGRAM؛
  • وأخيراً، %% للعلامة المئوية و$$ لعلامة الدولار.
هذه القوائم غير كاملة (بل تحوي المتغيرات الأهم فقط)، لكن يجب أن تكون صفحة الدليل udev(7)‎ شاملة.

9.11.4. مثال واقعي

دعنا ندرس حالة مفتاح USB بسيط ونحاول تعيين اسم ثابت له. أولاً، يجب أن تعثر على العناصر التي تعرف المفتاح بشكل فريد. للحصول عليها، وصّل المفتاح ثم استدع udevadm info -a -n /dev/sdc مع استبدال /dev/sdc بالاسم الفعلي الذي أعطي للمفتاح).
# udevadm info -a -n /dev/sdc
[...]
  looking at device '/devices/pci0000:00/0000:00:10.3/usb1/1-2/1-2.2/1-2.2:1.0/host9/target9:0:0/9:0:0:0/block/sdc':
    KERNEL=="sdc"
    SUBSYSTEM=="block"
    DRIVER==""
    ATTR{range}=="16"
    ATTR{ext_range}=="256"
    ATTR{removable}=="1"
    ATTR{ro}=="0"
    ATTR{size}=="126976"
    ATTR{alignment_offset}=="0"
    ATTR{capability}=="53"
    ATTR{stat}=="      51      100     1208      256        0        0        0        0        0      192      25        6"
    ATTR{inflight}=="       0        0"
[...]
  looking at parent device '/devices/pci0000:00/0000:00:10.3/usb1/1-2/1-2.2/1-2.2:1.0/host9/target9:0:0/9:0:0:0':
    KERNELS=="9:0:0:0"
    SUBSYSTEMS=="scsi"
    DRIVERS=="sd"
    ATTRS{device_blocked}=="0"
    ATTRS{type}=="0"
    ATTRS{scsi_level}=="3"
    ATTRS{vendor}=="I0MEGA  "
    ATTRS{model}=="UMni64MB*IOM2C4 "
    ATTRS{rev}=="    "
    ATTRS{state}=="running"
[...]
    ATTRS{max_sectors}=="240"
[...]
  looking at parent device '/devices/pci0000:00/0000:00:10.3/usb1/1-2/1-2.2':
    KERNELS=="9:0:0:0"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{configuration}=="iCfg"
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bmAttributes}=="80"
    ATTRS{bMaxPower}=="100mA"
    ATTRS{urbnum}=="398"
    ATTRS{idVendor}=="4146"
    ATTRS{idProduct}=="4146"
    ATTRS{bcdDevice}=="0100"
[...]
    ATTRS{manufacturer}=="USB Disk"
    ATTRS{product}=="USB Mass Storage Device"
    ATTRS{serial}=="M004021000001"
[...]
لإنشاء قاعدة جديدة، يمكنك عمل اختبارات على متغيرات الجهاز، وعلى متغيرات الأجهزة الآباء. يسمح لنا المثال السابق بإنشاء قاعدتين كما يلي:
KERNEL=="sd?", SUBSYSTEM=="block", ATTRS{serial}=="M004021000001", SYMLINK+="usb_key/disk"
KERNEL=="sd?[0-9]", SUBSYSTEM=="block", ATTRS{serial}=="M004021000001", SYMLINK+="usb_key/part%n"
بعد تخزين هذه القواعد في ملف، اسمه /etc/udev/rules.d/010_local.rules على سبيل المثال، يمكنك ببساطة إزالة مفتاح USB وتوصيله من جديد. عندئذ سترى أن /dev/usb_key/disk يمثل القرص التخزيني المرتبط بمفتاح USB، وأن /dev/usb_key/part1 يمثل القسم الأول منه.