استخدام ملفات Linux Kernel Sequence

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

ضع في اعتبارك موضوع استخدام ملفات التسلسل في Linux kernel ، وتعتبر هذه الملفات هي الآلية الأكثر ملاءمة للطباعة من وضع kernel. ولكن في الممارسة العملية ، فإن استخدامها بشكل صحيح أصعب بكثير مما تعتقد.

تتوفر الكثير من المواد حول هذا الموضوع عبر الإنترنت. الأفضل هو شفرة المصدر للنواة نفسها التي لديها تعليقات مفصلة تماما. المشكلة في مصدر المعلومات هذا هي حجمها. إذا كنت لا تعرف بالضبط ما الذي تبحث عنه ، فمن الأفضل إذا كان لديك وقت محدود فقط ، وليس لمحاولة على الإطلاق. بالنسبة لي ، عندما أصبحت مهتمة بالموضوع ، قدمت Google العديد من المصادر التي تبدو ممتازة للمعلومات المتعلقة ببحثي: الكتاب الشهير The Linux Kernel Module Programming Guide وسلسلة من المقالات التي كتبها Rob Day . هذه المصادر ليست جديدة ، لكنها قوية للغاية.

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

يبدو أن النسخة المطبوعة لأي شيء هي أبسط مهمة في البرمجة. لكن من خلال العمل في وضع kernel ، يفرض نظام التشغيل العديد من القيود التي قد لا يمكن تصورها تمامًا لمطور برنامج التطبيق. في وضع kernel ، يكون حجم المخزن المؤقت للطباعة محددًا بحجم صفحة الذاكرة الظاهرية. بالنسبة للهندسة المعمارية x86 ، فهي أربعة كيلو بايت. لذلك ، يجب أن يحقق برنامج جيد عند طباعة كميات كبيرة من البيانات أولاً ملء المخزن المؤقت إلى الحد الأقصى ، ثم طباعته ، ثم كرر هذا التكرار حتى يتم استنفاد بيانات الطباعة تمامًا. يمكنك بالطبع طباعة حرف بحرف ، الأمر الذي من شأنه تبسيط كل شيء إلى حد كبير ، لكننا نتحدث عن برامج جيدة.

المصادر المذكورة أعلاه كانت أسوأ قليلاً من المتوقع. في الكتاب على سبيل المثال ، تبين أن بعض المعلومات غير صحيحة بشكل عام وهذا ما دفعني إلى كتابة هذه الملاحظة. من الشائع اعتبار أن المعلومات المقدمة في شكل مخطط صورة هي الأسهل لفهمها واستخدامها. لكن في هذا الكتاب ، فإن الصورة المتعلقة بالموضوع غير صحيحة. يمكن أن يؤدي استخدام مثل هذا المخطط إلى أخطاء خطيرة ، على الرغم من أن المثال الموجود في الكتاب يعمل بشكل صحيح ويتبع هذا المخطط بالذات. هذا يرجع إلى حقيقة أنه في هذا المثال ، يتم طباعة عدد قليل من وحدات البايت في وقت يتم فيه الوصول إلى / proc / iter. إذا كنت تستخدمه كقالب لطباعة نصوص أكبر من صفحة الذاكرة ، فستكون هناك مفاجآت. لا تحتوي سلسلة المقالات المذكورة أعلاه على أخطاء واضحة ، ولكنها لا تقدم بعض التفاصيل المهمة لفهم الموضوع.

لذلك ، لننظر أولاً في المخطط الصحيح لكيفية العمل مع ملف تسلسل.



للعمل مع مثل هذا الملف ، تحتاج إلى إنشاء وظائف start () ، stop () ، next () show (). أسماء هذه الوظائف يمكن أن تكون أي ، لقد اخترت أقصر الكلمات التي تتوافق في معنى لإجراءات وظائف. عندما تكون هذه الوظائف موجودة ومتصلة بشكل صحيح بأنظمة kernel ، فإنها تبدأ العمل تلقائيًا عند الوصول إلى الملف المرتبط بها في دليل / proc. الشيء الأكثر إرباكًا هو استخدام الدالة stop () ، والتي يمكن استدعاؤها في ثلاثة سياقات مختلفة. يطلق عليه بعد start () يعني إنهاء مهمة الطباعة. يطلق عليها بعد show () يعني أن عملية الطباعة الأخيرة إلى المخزن المؤقت (عادة ما تستخدم وظيفة seq_printf لهذا) تجاوزت المخزن المؤقت للصفحة وتم إلغاء عملية الطباعة هذه. مكالماته بعد next () هي الحالة الأكثر إثارة للاهتمام التي تحدث عند طباعة بعض البيانات إلى المخزن المؤقت وتحتاج إما إلى إنهاء المهمة أو استخدام بيانات جديدة. على سبيل المثال ، لنفترض أن ملفنا في دليل / proc ، عند الوصول إليه ، ينتج أولاً بعض المعلومات عن أجهزة الكتلة ، ثم عن تلك التي تستخدم الأحرف. أولاً ، تقوم دالة start () بتهيئة الطباعة لأجهزة الكتلة ، وتستخدم الوظائف التالية () ، وربما () هذه بيانات التهيئة لطباعة معلومات خطوة بخطوة حول أجهزة الكتلة. عندما يكون كل شيء جاهزًا ، بعد آخر مكالمة إلى next () ، سيتم إجراء المكالمة المدعومة للتوقف () ، وبعد ذلك يتم استدعاء start () ، والتي يجب أن تبدأ هذه المرة بالفعل مزيدًا من الطباعة لأجهزة الأحرف.

أعطي مثالاً معدلاً قليلاً (محتويات الملف evens.c) من المقال بواسطة Rob Day. اضطررت إلى استبدال استدعاء دالة ، والتي غابت في الألباب الحديثة بمكافئها الفعلي. التعليقات أيضا تغيرت قليلا.

#include <linux/module.h> #include <linux/moduleparam.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/proc_fs.h> #include <linux/fs.h> #include <linux/seq_file.h> #include <linux/slab.h> static int limit = 10; //default value, it can be changed here or module_param(limit, int, S_IRUGO); //transfered as a module parameter static int* even_ptr; //we will work with dynamic memory /** * start */ static void *ct_seq_start(struct seq_file *s, loff_t *pos) { printk(KERN_INFO "Entering start(), pos = %Ld, seq-file pos = %lu.\n", *pos, s->count); if (*pos >= limit) { // are we done? printk(KERN_INFO "Apparently, we're done.\n"); return NULL; } //Allocate an integer to hold our increasing even value even_ptr = kmalloc(sizeof(int), GFP_KERNEL); if (!even_ptr) // fatal kernel allocation error return NULL; printk(KERN_INFO "In start(), even_ptr = %pX.\n", even_ptr); *even_ptr = (*pos)*2; return even_ptr; } /** * show */ static int ct_seq_show(struct seq_file *s, void *v) { printk(KERN_INFO "In show(), even = %d.\n", *(int*)v); seq_printf(s, "The current value of the even number is %d\n", *(int*)v); return 0; } /** * next */ static void *ct_seq_next(struct seq_file *s, void *v, loff_t *pos) { printk(KERN_INFO "In next(), v = %pX, pos = %Ld, seq-file pos = %lu.\n", v, *pos, s->count); (*pos)++; //increase my position counter if (*pos >= limit) //are we done? return NULL; *(int*)v += 2; //to the next even value return v; } /** * stop */ static void ct_seq_stop(struct seq_file *s, void *v) { printk(KERN_INFO "Entering stop().\n"); if (v) printk(KERN_INFO "v is %pX.\n", v); else printk(KERN_INFO "v is null.\n"); printk(KERN_INFO "In stop(), even_ptr = %pX.\n", even_ptr); if (even_ptr) { printk(KERN_INFO "Freeing and clearing even_ptr.\n"); kfree(even_ptr); even_ptr = NULL; } else printk(KERN_INFO "even_ptr is already null.\n"); } /** * This structure gathers functions which control the sequential reading */ static struct seq_operations ct_seq_ops = { .start = ct_seq_start, .next = ct_seq_next, .stop = ct_seq_stop, .show = ct_seq_show }; /** * This function is called when a file from /proc is opened */ static int ct_open(struct inode *inode, struct file *file) { return seq_open(file, &ct_seq_ops); }; /** * This structure gathers functions for a /proc-file operations */ static struct file_operations ct_file_ops = { .owner = THIS_MODULE, .open = ct_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release }; /** * This function is called when this module is loaded into the kernel */ static int __init ct_init(void) { proc_create("evens", 0, NULL, &ct_file_ops); return 0; } /** * This function is called when this module is removed from the kernel */ static void __exit ct_exit(void) { remove_proc_entry("evens", NULL); } module_init(ct_init); module_exit(ct_exit); MODULE_LICENSE("GPL"); 


تستخدم وظائف العمل مع ملف تسلسل اثنين من المؤشرات مع وظيفة متداخلة (وهذا أيضا مربكة إلى حد ما). يجب أن يشير أحدهم إلى الكائن الحالي المراد طباعته إلى المخزن المؤقت بواسطة show () - وهو مؤشر v 'في البرنامج. عادة ما يتم استخدام المؤشر الآخر "pos" للإشارة إلى العداد.

بالنسبة لأولئك الذين قد يرغبون في تشغيل البرنامج لأول مرة في وضع kernel ، أعطي مثالاً على Makefile لإنشاء ناجح. بالطبع ، لبناء ناجح ، يجب أن يكون لديك رؤوس مصدر Linux kernel في النظام.

 obj-m += evens.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean 


يتم الاتصال بـ kernel باستخدام الأمر sudo insmod evens.ko ، والتحقق من وظائف / proc / evens-file ، التي ظهرت بعد ذلك ، مع الأمر cat /proc/evens ، وقراءة سجل الأحداث الذي يشرح عمليات النظام باستخدام الأمر sudo cat /var/log/messages .

لتجاوز سعة المخزن المؤقت للصفحة ، قم بتعيين معلمة الحد على قيمة أكبر ، على سبيل المثال ، 200. يمكن إدخال هذه القيمة في نص البرنامج أو استخدامها عند تحميل الوحدة النمطية باستخدام الأمر sudo insmod events.ko limit=200 .

تحليل السجل يمكن أن يفسر النقاط المتبقية غير الواضحة. على سبيل المثال ، قد تلاحظ أنه قبل استدعاء stop () بعد next () أو start () ، يقوم النظام بضبط المتغير `v '. قد تلاحظ أيضًا أنه قبل استدعاء start () بعد stop () ، يطبع النظام محتويات المخزن المؤقت.

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

شفرة المصدر متاحة هنا أيضا.

Source: https://habr.com/ru/post/ar444620/


All Articles