Linuxカーネルシーケンスファイルの使用

現代のプログラミングの特徴は、グローバルネットワークを参照情報のソースとして使用することです。特に、特定のプログラマーの未知の問題またはあまり知られていない問題を解決するためのテンプレートのソースとして使用されます。 このアプローチは多くの時間を節約し、しばしば非常に高品質の結果をもたらします。 ただし、ネットワークに投稿されたソリューションは、通常は正しいものですが、特定の問題を解決するための微妙な点を常に考慮しているわけではありません。これにより、通常は正しく機能するセクションのコードが表示されますが、標準的な状況ではない場合、不快な驚きの原因になります。



Linuxカーネルでシーケンスファイルを使用するトピックを検討してください。 このようなファイルは、カーネルモードから印刷するための最も便利なメカニズムと見なされます。 しかし、実際には、それらを正しく使用することは、あなたが考えているよりもはるかに困難です。



ネットワークには、このトピックに関する多くの資料があります。 最良のものは、かなり詳細なコメントが付いたカーネル自体のソースです。 ボリューム内のこの情報源の問題。 探すべきものが正確にわからない場合は、限られた時間しかなく、試してはいけません。 トピックに興味を抱いたとき、Googleは一見優れた情報源を見つけました。有名な本The Linux Kernel Module Programming Guide、トピックに関する一連のRob Dayの記事です。 ソースは新しいものではありませんが、非常に堅実です。



シーケンスファイルを使用するのが自然な場合は、まずより詳細に検討します。 最も典型的な状況は、/ procファイルシステムに独自のファイルを作成することです。 このシステムのファイルを読み取ると、使用している機器、そのドライバー、RAM、プロセスなどに関するさまざまな情報を取得できます。



何でも印刷することが、プログラミングの中で最も単純なものであるように思えます。 ただし、OSカーネルモードでの作業には多くの制限があり、アプリケーション開発者にはまったく考えられないように思えるかもしれません。 カーネルモードでは、印刷バッファーのサイズは仮想メモリページのサイズによって制限されます。 x86アーキテクチャの場合、これらは4キロバイトです。 したがって、大容量のデータを印刷する場合の適切なプログラムは、最初にこのバッファーの最大充填量を達成し、それを印刷してから、印刷用のデータが完全になくなるまでこの反復を繰り返します。 もちろん、文字ごとに印刷することができます。これにより、すべてが大幅に簡素化されますが、良いプログラムについて話しています。



上記の情報源は、予想よりわずかに悪いことが判明しました。 たとえば、本では、いくつかの情報が一般的に間違っていることが判明したため、このメモを書くように促されました。 それは、ダイアグラムの形式で情報を読み、使用するのが最も簡単であることが判明しました。 このようなスキームを使用すると、重大なエラーにつながる可能性があります。 本に記載されている例は正しく動作し、このスキームに正確に従っていますが。 これは、この例では、/ proc / iterにアクセスすると、一度に数バイトしか出力されないためです。 メモリのページよりも大きいテキストを印刷するためのテンプレートとして使用すると、驚きが生じます。 上記の一連の記事には明らかなエラーは含まれていませんが、トピックを理解するために重要な詳細については報告していません。



それでは、まずシーケンスファイルの処理方法の正しいスキームを検討しましょう。









このようなファイルを操作するには、start、stop、next、およびshow関数を作成する必要があります。 これらの関数の名前は任意であり、最短で意味のあるものを選択します。 そのような関数が存在し、カーネルシステムに正しく接続されている場合、/ procディレクトリでそれらに関連付けられたファイルにアクセスすると、それらの関数は自動的に動作を開始します。 最も紛らわしいのは、3つの異なるコンテキストで呼び出すことができるstop関数の使用です。 開始後の呼び出しは、データ印刷ジョブの終了を意味します。 showの後に呼び出すと、最後の印刷操作がバッファーに対して実行されたときに(通常、seq_printf関数が使用されます)、ページバッファーがオーバーフローし、この印刷操作がキャンセルされました。 そして、次の呼び出しは、バッファへの1つのデータのみの印刷が終了し、作業を完了するか、新しいデータを使用する必要がある場合に発生する最も興味深いケースです。 たとえば、/ procディレクトリ内のファイルにアクセスすると、最初にブロックデバイスに関する情報が提供され、次にキャラクターデバイスに関する情報が提供されるとします。 最初に、開始関数はブロックデバイスでの印刷を初期化し、次の、場合によっては表示関数はこの初期化データを使用して、ブロックデバイスに必要な情報を段階的に印刷します。 すべての準備が整うと、最後の次の呼び出しの後、停止呼び出しの考慮されたケースが作成され、その後、開始が呼び出されます。この場合、文字デバイスのさらなるリストが既に初期化されているはずです。



Rob Dayの記事から少し変更した例(evens.cファイルの内容)を紹介します。 最新のカーネルにはない関数呼び出しを、現在の同等の関数に置き換える必要がありました。 ロシア語で置き換えられた英語のコメント。



#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; //  ,     module_param(limit, int, S_IRUGO); //,  ,    static int* even_ptr; //  , ,   //   /** * 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; } //   ,        even_ptr = kmalloc(sizeof(int), GFP_KERNEL); if (!even_ptr) //  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)++; //  if (*pos >= limit) //? return NULL; *(int*)v += 2; //    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"); } /** *         */ static struct seq_operations ct_seq_ops = { .start = ct_seq_start, .next = ct_seq_next, .stop = ct_seq_stop, .show = ct_seq_show }; /** *        /proc */ static int ct_open(struct inode *inode, struct file *file) { return seq_open(file, &ct_seq_ops); }; /** *         /proc */ static struct file_operations ct_file_ops = { .owner = THIS_MODULE, .open = ct_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release }; /** *   ,       */ static int __init ct_init(void) { proc_create("evens", 0, NULL, &ct_file_ops); return 0; } /** *   ,       */ static void __exit ct_exit(void) { remove_proc_entry("evens", NULL); } module_init(ct_init); module_exit(ct_exit); MODULE_LICENSE("GPL");
      
      





ファイルシーケンスを操作するための関数は、機能が重複する2つのポインターを使用します(これも少しわかりにくいです)。 それらの1つは、プログラムのshow-v関数によってバッファーに印刷するために現在のオブジェクトを指している必要があり、もう1つ(pos)は通常、カウンターを示すために使用されます。



カーネルモードで初めてプログラムを実行したい人のために、ビルドを成功させるためのMakefileの例を示します。 もちろん、アセンブリを成功させるには、システムにLinuxカーネルソースヘッダーが必要です。



 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
      
      





カーネルへの接続はsudo insmod evens.koコマンドで行われ、この後に出現する/ proc / evensファイルの機能をcat / proc / evensコマンドで確認し、システムの動作を説明するイベントログをsudo cat / var / log / messagesコマンドで読み取ります。



ページバッファをオーバーフローさせるには、limitパラメータを200などの大きな値に設定する必要があります。この値は、プログラムテキストに入力するか、モジュールのロード時に使用できます-sudo insmod evens.ko limit = 200。



ログ分析により、残りの不明瞭な点が明らかになる場合があります。 たとえば、nextまたはstartの後にstopを呼び出す前に、システムがvをゼロにすることがあります。 また、停止後に開始を呼び出す前に、システムがバッファの内容を出力することに気付くかもしれません。



誰かが私の記事で見つけた不正確さ、または他に言及すべきことについて報告してくれたら感謝します。



ここでソースを取得することもできます



All Articles