مرحبا بالجميع. في هذه المقالة ، أود أن أشارك تجربة إنشاء وحدة Linux kernel بسيطة. ستكون هذه المقالة مفيدة لأولئك الذين يرغبون في فهم كيفية كتابة وحدات النواة ، ولكن لا يعرفون من أين تبدأ.
لطالما رغبت في فهم هذا الموضوع ، لكن حتى وقت قريب لم أكن أعرف كيف أتناوله. أردت أن تكون الوحدة بسيطة بما يكفي ، ولكنها أكثر تعقيدًا من الرسالة "Hello world!" الإخراج في ملف السجل. في النهاية ، قررت محاولة وميض LED. كان الهدف الإضافي هو استنتاج المعلمة المسؤولة عن تكرار الوميض في sysfs.
للتجربة ، استخدمت لوحة Orange Pi One مع Ubuntu Linux على اللوحة (إصدار kernel 3.4.113). من أجل بناء وحدة kernel ، تحتاج إلى مترجم gcc ، أداة المساعدة ، وملفات رأس kernel. لتثبيت ملفات الرأس ، قم بتشغيل الأمر التالي:
sudo apt-get install linux-headers-$(uname -r)
بعد ذلك ، سأحلل في رأيي الأجزاء الأكثر إثارة للاهتمام من الوحدة. من أجل توفير المساحة ، لن أعطي جميع التعليمات البرمجية هنا ، وهي متاحة على
github مع ملف make.
في الوحدة ، استخدمت ملفات الرأس:
#include <linux/kernel.h> #include <linux/module.h> #include <linux/gpio.h> #include <linux/hrtimer.h> #include <linux/moduleparam.h>
يجب تمكين kernel.h و module.h دائمًا عند كتابة وحدة kernel ، gpio.h مسؤول فعليًا عن العمل مع GPIO ، hrtimer.h (مؤقت عالي الدقة) هو ملف رأس المؤقت ، moduleparam.h مطلوب لعرض المعلمات في sysfs.
لكي لا يلمع متغيراتها ووظائفها في نواة النظام ، يجب وصفها جميعًا على أنها ثابتة. فقط في الحالة ، ألاحظ أن النواة مكتوبة بلغة C وثابتة ، على عكس C ++ ، فهذا يعني أن الكائن متاح فقط داخل الملف القابل للتنفيذ.
نقطة الدخول هي:
static int blink_module_init(void)
هنا أقوم بتهيئة المتغيرات التي سأستخدمها في المستقبل ، بما في ذلك:
gpio_timer_interval = ktime_set(gpio_blink_interval_s, 0);
يقوم ktime_set بتهيئة نوع بيانات ktime_t عن طريق إعطائه عدد الثواني المطلوب (gpio_blink_interval_s) والنانو ثانية (0). في المستقبل ، سيتم استخدام هذا المتغير بواسطة المؤقت.
فيما يلي طلب لاستخدام GPIO:
err = gpio_request(BLINK_PIN_NR, "blink_led");
ترجع هذه الدالة 0 إذا نجحت ، لذلك أتحقق في المستقبل من أنها عادت. بعد ذلك ، يجب تعيين الدبوس المحدد على إخراج الإشارة وتحديد القيمة الافتراضية.
err = gpio_direction_output(BLINK_PIN_NR, GPIOF_INIT_LOW);
إذا لم تكن هناك أخطاء ، فقم بتهيئة الموقت وبدء تشغيله
hrtimer_init(&gpio_blink_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); gpio_blink_timer.function = &gpio_blink_timer_callback; hrtimer_start(&gpio_blink_timer, gpio_timer_interval, HRTIMER_MODE_REL);
ستكون وظيفة رد الاتصال بالمؤقت gpio_blink_timer_callback. في هذه الوظيفة ، أغير قيمة الدبوس إلى العكس
gpio_value ^= 0x01; gpio_set_value(BLINK_PIN_NR, gpio_value);
أسأل متى يجب أن يعمل المؤقت في المرة القادمة
hrtimer_forward_now(&gpio_blink_timer, gpio_timer_interval);
وإرجاع HRTIMER_RESTART.
الآن ، دعونا نلقي نظرة على كيفية إظهار نوع من المتغيرات في sysfs. لهذا أستخدم ماكرو
module_param_cb(gpio_blink_interval_s, &kp_ops, &gpio_blink_interval_s, 0660);
المعلمة الأولى لهذا الماكرو هو اسم الملف في sysfs. والثاني هو بنية بيانات تحتوي على وظائف رد الاتصال. المعلمة الثالثة هي مؤشر للمتغير الحقيقي وأذونات الملف الرابعة في sysfs.
يتم استدعاء وظائف kp_ops عندما يغير المستخدم قيم ملف sysfs أو يقرأ قيمته. إليك كيف قمت بتهيئتها:
static const struct kernel_param_ops kp_ops = { .set = &set_blink_interval, .get = &get_blink_interval };
في هذه الحالة ، تعد set مهمة ، لأنها تحدد قيمة جديدة ، gpio_timer_interval.
gpio_timer_interval = ktime_set(gpio_blink_interval_s, 0);
في نقطة الخروج ، أمحو جميع الموارد المستخدمة
static void blink_module_exit(void) { hrtimer_cancel(&gpio_blink_timer); gpio_set_value(BLINK_PIN_NR, 0); gpio_free(BLINK_PIN_NR); printk(KERN_ALERT "Blink module unloaded\n"); }
يجب الإشارة إلى نقاط الدخول والخروج في وحدات الماكرو المقابلة
module_init(blink_module_init); module_exit(blink_module_exit);
يبدو أنه يصف جميع النقاط الهامة. إذا كان لدى القراء أي أسئلة ، فسأكون سعيدًا للإجابة عليها في التعليقات.