Cを使用したLinuxシステムコールチュートリアル

Linux System Call Tutorial With C



の前回の記事で Linuxシステムコール 、私はシステムコールを定義し、プログラムでそれらを使用する理由について説明し、それらの長所と短所を掘り下げました。 C内のアセンブリで簡単な例を挙げました。それは要点を示し、電話をかける方法を説明しましたが、生産的なことは何もしませんでした。必ずしもスリリングな開発演習ではありませんが、それは要点を示しています。

この記事では、実際のシステムコールを使用して、Cプログラムで実際の作業を行います。まず、システムコールを使用する必要があるかどうかを確認してから、ファイルコピーのパフォーマンスを大幅に向上させることができるsendfile()呼び出しを使用した例を示します。最後に、Linuxシステムコールを使用する際に覚えておくべきいくつかのポイントについて説明します。







C開発のキャリアのある時点でシステムコールを使用することは避けられませんが、高性能または特定のタイプの機能を対象としている場合を除き、主要なLinuxディストリビューションに含まれるglibcライブラリおよびその他の基本ライブラリが大部分を処理します。あなたの要望。



glibc標準ライブラリは、システム固有のシステムコールを必要とする関数を実行するための、クロスプラットフォームで十分にテストされたフレームワークを提供します。たとえば、fscanf()、fread()、getc()などを使用してファイルを読み取ることも、read()Linuxシステムコールを使用することもできます。 glibc関数は、より多くの機能(つまり、より優れたエラー処理、フォーマットされたIOなど)を提供し、glibcがサポートするすべてのシステムで機能します。



一方、妥協のないパフォーマンスと正確な実行が重要な場合があります。 fread()が提供するラッパーはオーバーヘッドを追加しますが、マイナーではありますが、完全に透過的ではありません。さらに、ラッパーが提供する追加機能が必要ない場合もあります。その場合は、システムコールを利用するのが最適です。





システムコールを使用して、glibcでまだサポートされていない関数を実行することもできます。 glibcのコピーが最新の場合、これはほとんど問題になりませんが、新しいカーネルを使用して古いディストリビューションで開発するには、この手法が必要になる場合があります。

免責事項、警告、および潜在的な迂回路を読んだので、次にいくつかの実用的な例を掘り下げてみましょう。



どのCPUを使用していますか?

ほとんどのプログラムがおそらく尋ねようとは思わない質問ですが、それでも有効な質問です。これは、glibcで複製できず、glibcラッパーでカバーされていないシステムコールの例です。このコードでは、syscall()関数を介してgetcpu()呼び出しを直接呼び出します。 syscall関数は次のように機能します。

システムコール((SYS_callarg1arg2..。)。;

最初の引数SYS_callは、システムコールの番号を表す定義です。 sys / syscall.hを含めると、これらが含まれます。最初の部分はSYS_で、2番目の部分はシステムコールの名前です。

呼び出しの引数は、上記のarg1、arg2に入ります。一部の呼び出しにはさらに引数が必要であり、manページから順番に続行されます。ほとんどの引数、特に戻り値の場合、malloc関数を介して割り当てられたchar配列またはメモリへのポインタが必要になることに注意してください。

example1.c

#含む
#含む
#含む
#含む

int主要(()。 {{

署名なしCPUノード;

//システムコールを介して現在のCPUコアとNUMAノードを取得します
//これにはglibcラッパーがないため、直接呼び出す必要があることに注意してください
システムコール((SYS_getcpu CPU ノードヌル)。;

//情報を表示します
printf (('このプログラムは、CPUコア%uおよびNUMAノード%uで実行されています。NSNS'CPUノード)。;

戻る 0;

}

コンパイルして実行するには

gccexample1。NS -o example1
/example1

さらに興味深い結果を得るには、pthreadsライブラリを介してスレッドをスピンし、この関数を呼び出して、スレッドが実行されているプロセッサを確認します。

Sendfile:優れたパフォーマンス

Sendfileは、システムコールを通じてパフォーマンスを向上させる優れた例を提供します。 sendfile()関数は、あるファイル記述子から別のファイル記述子にデータをコピーします。 sendfileは、複数のfread()関数とfwrite()関数を使用するのではなく、カーネルスペースで転送を実行し、オーバーヘッドを削減してパフォーマンスを向上させます。

この例では、64MBのデータをあるファイルから別のファイルにコピーします。 1つのテストでは、標準ライブラリの標準の読み取り/書き込みメソッドを使用します。もう1つは、システムコールとsendfile()呼び出しを使用して、このデータをある場所から別の場所にブラストします。

test1.c(glibc)

#含む
#含む
#含む
#含む

#define BUFFER_SIZE 67108864
#define BUFFER_1'buffer1 '
#define BUFFER_2'buffer2 '

int主要(()。 {{

ファイル*間違い *終わり;

printf (('NS従来のglibc関数を使用したI / Oテスト。NSNS')。;

// BUFFER_SIZEバッファを取得します。
//バッファにはランダムなデータが含まれますが、それについては気にしません。
printf (('64 MBバッファの割り当て: ')。;
char *バッファ= ((char *)。 malloc ((バッファサイズ)。;
printf (('終わりNS')。;

//バッファをfOutに書き込みます
printf (('最初のバッファーへのデータの書き込み:')。;
間違い= fopen ((BUFFER_1 'wb')。;
fwrite ((バッファ のサイズ((char)。バッファサイズ間違い)。;
fclose ((間違い)。;
printf (('終わりNS')。;

printf (('最初のファイルから2番目のファイルへのデータのコピー:')。;
終わり= fopen ((BUFFER_1 'rb')。;
間違い= fopen ((BUFFER_2 'wb')。;
フレッド ((バッファ のサイズ((char)。バッファサイズ終わり)。;
fwrite ((バッファ のサイズ((char)。バッファサイズ間違い)。;
fclose ((終わり)。;
fclose ((間違い)。;
printf (('終わりNS')。;

printf (('バッファの解放:')。;
自由 ((バッファ)。;
printf (('終わりNS')。;

printf (('ファイルの削除:')。;
削除する ((BUFFER_1)。;
削除する ((BUFFER_2)。;
printf (('終わりNS')。;

戻る 0;

}

test2.c(システムコール)

#含む
#含む
#含む
#含む
#含む
#含む
#含む
#含む
#含む

#define BUFFER_SIZE 67108864

int主要(()。 {{

int間違い終わり;

printf (('NSsendfile()および関連するシステムコールを使用したI / Oテスト。NSNS')。;

// BUFFER_SIZEバッファを取得します。
//バッファにはランダムなデータが含まれますが、それについては気にしません。
printf (('64 MBバッファの割り当て: ')。;
char *バッファ= ((char *)。 malloc ((バッファサイズ)。;
printf (('終わりNS')。;


//バッファをfOutに書き込みます
printf (('最初のバッファーへのデータの書き込み:')。;
間違い=開いた(('buffer1'O_RDONLY)。;
書きます((間違い バッファバッファサイズ)。;
選ぶ((間違い)。;
printf (('終わりNS')。;

printf (('最初のファイルから2番目のファイルへのデータのコピー:')。;
終わり=開いた(('buffer1'O_RDONLY)。;
間違い=開いた(('buffer2'O_RDONLY)。;
ファイルを送信((間違い終わり 0バッファサイズ)。;
選ぶ((終わり)。;
選ぶ((間違い)。;
printf (('終わりNS')。;

printf (('バッファの解放:')。;
自由 ((バッファ)。;
printf (('終わりNS')。;

printf (('ファイルの削除:')。;
リンクを解除する(('buffer1')。;
リンクを解除する(('buffer2')。;
printf (('終わりNS')。;

戻る 0;

}

テスト1と2のコンパイルと実行

これらの例を作成するには、ディストリビューションに開発ツールをインストールする必要があります。 DebianとUbuntuでは、これを次の方法でインストールできます。

aptインストールビルドエッセンシャル

次に、次のコマンドでコンパイルします。

gcctest1.c-またtest1&& gcctest2.c-またtest2

両方を実行してパフォーマンスをテストするには、次のコマンドを実行します。

時間/test1&& 時間/test2

次のような結果が得られるはずです。

従来のglibc関数を使用したI / Oテスト。

64 MBバッファの割り当て:完了
最初のバッファへのデータの書き込み:完了
最初のファイルから2番目のファイルへのデータのコピー:完了
バッファの解放:完了
ファイルの削除:完了
実数0分0.397秒
ユーザー0m0.000s
sys 0m0.203s
sendfile()および関連するシステムコールを使用したI / Oテスト。
64 MBバッファの割り当て:完了
最初のバッファへのデータの書き込み:完了
最初のファイルから2番目のファイルへのデータのコピー:完了
バッファの解放:完了
ファイルの削除:完了
実際の0m0.019s
ユーザー0m0.000s
sys 0m0.016s

ご覧のとおり、システムコールを使用するコードは、同等のglibcよりもはるかに高速に実行されます。

覚えておくべきこと

システムコールはパフォーマンスを向上させ、追加機能を提供できますが、欠点がないわけではありません。システムコールが提供するメリットと、プラットフォームの移植性の欠如、場合によってはライブラリ機能と比較して機能が低下することを比較検討する必要があります。

一部のシステムコールを使用する場合は、ライブラリ関数ではなく、システムコールから返されるリソースを使用するように注意する必要があります。たとえば、glibcのfopen()、fread()、fwrite()、およびfclose()関数に使用されるFILE構造は、open()システム呼び出しからのファイル記述子番号(整数として返されます)と同じではありません。これらを混合すると、問題が発生する可能性があります。

一般に、Linuxシステムコールのバンパーレーンはglibc関数よりも少なくなっています。システムコールにはエラー処理とレポートが含まれているのは事実ですが、glibc関数からより詳細な機能を取得できます。

そして最後に、セキュリティについて一言。システムコールはカーネルと直接インターフェースします。 Linuxカーネルには、ユーザーの土地からのシェナニガンに対する広範な保護がありますが、未発見のバグが存在します。システムコールが入力を検証したり、セキュリティの問題から隔離したりすることを信用しないでください。システムコールに渡すデータがサニタイズされていることを確認することをお勧めします。当然、これはAPI呼び出しには良いアドバイスですが、カーネルを操作するときは注意する必要はありません。

Linuxシステムコールの世界へのこの深い潜入を楽しんでいただけたと思います。 Linuxシステムコールの完全なリストについては、マスターリストを参照してください。