Fitur karakteristik pemrograman modern adalah penggunaan jaringan global sebagai sumber informasi referensi, khususnya, sumber pola untuk memecahkan masalah yang tidak diketahui atau sedikit diketahui untuk programmer tertentu. Pendekatan semacam ini menghemat banyak waktu dan seringkali memberikan hasil yang cukup kualitatif. Namun, solusi yang dituangkan dalam jaringan meskipun biasanya benar, tidak selalu memperhitungkan semua seluk-beluk penyelesaian masalah, yang mengarah pada penampilan dalam kode sumber bagian yang biasanya bekerja dengan benar, tetapi dalam keadaan yang tidak cukup standar menjadi sumber kejutan yang tidak menyenangkan.
Pertimbangkan topik menggunakan file urutan dalam kernel Linux, file tersebut dianggap sebagai mekanisme yang paling nyaman untuk mencetak dari mode kernel. Namun dalam praktiknya, menggunakannya dengan benar jauh lebih sulit daripada yang Anda kira.
Banyak materi tentang topik ini tersedia online. Yang terbaik adalah kode sumber dari kernel itu sendiri yang memiliki komentar yang cukup rinci. Masalah dengan sumber informasi ini adalah volumenya. Jika Anda tidak tahu persis apa yang harus dicari, lebih baik jika Anda hanya memiliki waktu terbatas, jangan mencoba sama sekali. Bagi saya, ketika saya menjadi tertarik dengan topik ini, Google memberikan beberapa sumber informasi yang tampaknya sangat baik berkaitan dengan pencarian saya: buku terkenal
Panduan Pemrograman Modul Kernel Linux dan
serangkaian artikel oleh Rob Day . Sumber-sumber ini tidak baru, tetapi sangat solid.
Pertama-tama mari kita pertimbangkan secara lebih rinci kapan wajar menggunakan file urutan. Situasi yang paling umum adalah membuat file Anda sendiri di sistem file / proc. Dengan membaca file dari sistem ini, Anda bisa mendapatkan berbagai informasi tentang peralatan yang digunakan, drivernya, RAM, proses, dll.
Tampaknya cetakan apa pun adalah tugas paling sederhana dalam pemrograman. Tetapi bekerja dalam mode kernel OS memaksakan banyak pembatasan yang mungkin tampak benar-benar tak terbayangkan bagi pengembang perangkat lunak aplikasi. Dalam mode kernel, ukuran buffer cetak dibatasi oleh ukuran halaman memori virtual. Untuk arsitektur x86 itu adalah empat kilobyte. Oleh karena itu, program yang baik saat mencetak data dalam jumlah besar pertama-tama harus mencapai pengisian buffer secara maksimal, lalu mencetaknya, dan kemudian mengulangi iterasi ini hingga data untuk pencetakan benar-benar habis. Anda tentu saja dapat mencetak karakter demi karakter, yang akan sangat menyederhanakan segalanya, tetapi kita berbicara tentang program yang bagus.
Sumber yang disebutkan di atas sedikit lebih buruk dari yang diharapkan. Dalam buku itu misalnya, beberapa informasi ternyata secara umum tidak benar dan itulah yang mendorong saya untuk menulis catatan ini. Adalah umum untuk mempertimbangkan bahwa informasi yang diberikan dalam bentuk skema-gambar adalah yang paling mudah untuk dipahami dan digunakan. Namun dalam buku ini gambar yang berhubungan dengan subjek tidak benar. Menggunakan skema semacam itu dapat menyebabkan kesalahan serius, meskipun contoh dalam buku ini berfungsi dengan benar dan mengikuti skema ini. Ini disebabkan oleh fakta bahwa dalam contoh ini, hanya beberapa byte yang dicetak pada saat / proc / iter diakses. Jika Anda menggunakannya sebagai templat untuk mencetak teks yang lebih besar dari satu halaman memori, akan ada kejutan. Seri artikel yang disebutkan di atas tidak mengandung kesalahan yang jelas, tetapi tidak melaporkan beberapa detail yang penting untuk memahami topik tersebut.
Jadi, pertama mari kita pertimbangkan skema yang benar tentang cara bekerja dengan file urutan.

Untuk bekerja dengan file seperti itu, Anda harus membuat fungsi start (), stop (), next () dan show (). Nama-nama fungsi ini dapat berupa apa saja, saya memilih kata-kata terpendek yang sesuai dengan makna dengan tindakan fungsi. Ketika fungsi-fungsi tersebut hadir dan terhubung dengan benar ke sistem kernel, mereka mulai bekerja secara otomatis ketika mengakses file yang terkait dengannya di direktori / proc. Yang paling membingungkan adalah penggunaan fungsi stop (), yang dapat dipanggil dalam tiga konteks berbeda. Memanggilnya setelah mulai () berarti mengakhiri pekerjaan cetak. Memanggilnya setelah show () berarti bahwa operasi cetak terakhir ke buffer (biasanya fungsi seq_printf digunakan untuk ini) meluap buffer halaman dan operasi cetak ini dibatalkan. Panggilannya setelah next () adalah kasus paling menarik yang terjadi saat mencetak beberapa data ke buffer berakhir dan Anda harus menyelesaikan pekerjaan atau menggunakan data baru. Sebagai contoh, misalkan file kita di direktori / proc, ketika mengaksesnya, pertama menghasilkan beberapa informasi tentang perangkat blok, dan kemudian pada karakter. Pertama, fungsi start () menginisialisasi pencetakan untuk perangkat blok, dan berikutnya () dan, mungkin, fungsi show () menggunakan data inisialisasi ini untuk mencetak informasi langkah-demi-langkah tentang perangkat blok. Ketika semuanya sudah siap, setelah panggilan terakhir ke berikutnya (), panggilan untuk berhenti () akan dibuat, setelah itu mulai () dipanggil, yang kali ini sudah harus memulai pencetakan lebih lanjut untuk perangkat karakter.
Saya memberikan contoh yang sedikit dimodifikasi (isi file evens.c) dari artikel oleh Rob Day. Saya harus mengganti panggilan fungsi, yang tidak ada di kernel modern dengan padanan sebenarnya. Komentarnya juga sedikit berubah.
#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");
Fungsi untuk bekerja dengan file urutan menggunakan dua petunjuk dengan fungsi yang tumpang tindih (ini juga agak membingungkan). Salah satunya harus menunjuk ke objek saat ini untuk dicetak ke buffer oleh show () - itu adalah `v'-pointer dalam program. Pointer lain `pos 'biasanya digunakan untuk menunjuk ke counter.
Bagi mereka yang mungkin baru pertama kali ingin menjalankan program mereka dalam mode kernel, saya memberikan contoh Makefile untuk build yang berhasil. Tentu saja, untuk membangun yang sukses, Anda harus memiliki header sumber kernel Linux di sistem.
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
Menghubungkan ke kernel dilakukan dengan perintah
sudo insmod evens.ko
, memeriksa fungsi file / proc / evens, yang muncul setelah ini, dengan perintah
cat /proc/evens
, membaca log peristiwa yang menjelaskan operasi sistem dengan perintah
sudo cat /var/log/messages
.
Untuk melimpah buffer halaman, atur parameter batas ke nilai yang lebih besar, misalnya, 200. Nilai ini dapat dimasukkan ke dalam teks program atau digunakan saat memuat modul dengan perintah
sudo insmod events.ko limit=200
.
Analisis log dapat menjelaskan poin-poin tidak jelas yang tersisa. Sebagai contoh, Anda mungkin memperhatikan bahwa sebelum memanggil stop () setelah next () atau start (), sistem nol variabel `v '. Anda juga dapat memperhatikan bahwa sebelum memanggil start () setelah stop (), sistem mencetak isi buffer.
Saya akan berterima kasih jika seseorang melaporkan ketidakakuratan yang ditemukan dalam catatan saya atau apa pun yang harus disebutkan.
Kode sumber juga tersedia di
sini .