魏東山 Linuxドライバー入門実験教室(1) こんにちはドライバー

序文

(1) Wei Dongshan 氏から Linux を学びましょう。彼のスピーチは非常に簡潔なので、多くの人が理解できないからです。続いて、魏東山ドライバー実験教室の第1回ハロープログラムを紹介します。

(2) 注意、このチュートリアルを見る前にビデオを終了してください。この記事は初心者向けです!さらに詳しく知りたい場合は、他のブログを検索してください。

(3) gitee ウェアハウスGitHub ウェアハウス

コード

まずコードをアップロードし、コードにコメントを追加します。コメントを見ただけで理解できるのであれば、時間をかけて下を向く必要はありません。具体的なコードは私の倉庫にあります。

ドライバーコード

/* 说明 : 
 	*1,本代码是学习韦东山老师的驱动入门视频所写,增加了注释。
 	*2,采用的是UTF-8编码格式,如果注释是乱码,需要改一下。
 	*3,这是驱动层代码
 * 作者 : CSDN风正豪
*/



#include <linux/mm.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/mman.h>
#include <linux/random.h>
#include <linux/init.h>
#include <linux/raw.h>
#include <linux/tty.h>
#include <linux/capability.h>
#include <linux/ptrace.h>
#include <linux/device.h>
#include <linux/highmem.h>
#include <linux/backing-dev.h>
#include <linux/shmem_fs.h>
#include <linux/splice.h>
#include <linux/pfn.h>
#include <linux/export.h>
#include <linux/io.h>
#include <linux/uio.h>
#include <linux/module.h>
#include <linux/uaccess.h>

static int major; //主设备号,用于最后的驱动卸载

/*
 *传入参数 :
	 *node :
	 *filp :
 *返回参数 : 如果成功返回0
*/
static int hello_open (struct inode *node, struct file *filp)
{
	/*__FILE__ :表示文件
	 *__FUNCTION__ :当前函数名
	 *__LINE__ :在文件的哪一行
	*/
    printk("%s %s %d\n",__FILE__,__FUNCTION__, __LINE__);
    return 0;
}

/*
 *传入参数 :
	 *filp :要读的文件
	 *buf :读的数据放在哪里
	 *size :读多大数据
	 *offset :偏移值(一般不用)
 *返回参数 :读到的数据长度	
*/
static	ssize_t hello_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
	/*__FILE__ :表示文件
	 *__FUNCTION__ :当前函数名
	 *__LINE__ :在文件的哪一行
	*/
    printk("%s %s %d\n",__FILE__,__FUNCTION__, __LINE__);
    return size;
}

/*
 *传入参数 :
	 *filp :要写的文件
	 *buf :写入的数据来自于buf
	 *size :写多大数据
	 *offset :偏移值(一般不用)
 *返回参数 :写的数据长度	
*/
static	ssize_t hello_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
	/*__FILE__ :表示文件
	 *__FUNCTION__ :当前函数名
	 *__LINE__ :在文件的哪一行
	*/
    printk("%s %s %d\n",__FILE__,__FUNCTION__, __LINE__);
    return size;
}

/*作用 : 应用程序关闭的时候调用这个函数
 *传入参数 :
	 *node :
	 *filp :要关闭的文件
 *返回参数 :成功返回0	
*/
static	int hello_release (struct inode *node, struct file *filp)
{
	/*__FILE__ :表示文件
	 *__FUNCTION__ :当前函数名
	 *__LINE__ :在文件的哪一行
	*/
    printk("%s %s %d\n",__FILE__,__FUNCTION__, __LINE__);
    return 0;
}

//1,构造 file_operations 
static const struct file_operations  hello_drv = {
    .owner      = THIS_MODULE,
	.read		= hello_read,
	.write		= hello_write,
	.open		= hello_open,
    .release    = hello_release
};

//2,注册驱动(注意,我们在入口函数中注册)

//在命令行输入insmod命令,就是注册驱动程序。之后就会进入这个入口函数
//3,入口函数
static int  hello_init(void)
{
	/*将hello_drv这个驱动放在内核的第n项,中间传入的名字不重要,第三个是要告诉内核的驱动
	 *因为我们不知道第n项是否已经存放了其他驱动,就可以放在第0项,然后让系统自动往后遍历存放到空的地方
	 *major为最终存放的第n项,等下卸载程序需要使用。如果不卸载程序,可以不管这个
	*/
    major = register_chrdev(0,"100ask_hello",&hello_drv);
	//如果成功注册驱动,打印
	printk("insmod success!\n");
    return 0;
}

//在命令行输入rmmod命令,就是注册驱动程序。之后就会进入这个出口函数
//4,出口函数
static void  hello_exit(void)
{
	//卸载驱动程序
	//第一个参数是主设备号,第二个是名字
    unregister_chrdev(major,"100ask_hello");
	//如果成功卸载驱动,打印
	printk("rmmod success!\n");
}


module_init(hello_init); //确认入口函数
module_exit(hello_exit); //确认出口函数

/*最后我们需要在驱动中加入 LICENSE 信息和作者信息,其中 LICENSE 是必须添加的,否则的话编译的时候会报错,作者信息可以添加也可以不添加
 *这个协议要求我们代码必须免费开源,Linux遵循GPL协议,他的源代码可以开放使用,那么你写的内核驱动程序也要遵循GPL协议才能使用内核函数
 *因为指定了这个协议,你的代码也需要开放给别人免费使用,同时可以根据这个协议要求很多厂商提供源代码
 *但是很多厂商为了规避这个协议,驱动源代码很简单,复杂的东西放在应用层
*/
MODULE_LICENSE("GPL"); //指定模块为GPL协议
MODULE_AUTHOR("CSDN:qq_63922192");  //表明作者,可以不写



アプリケーション層のコード

/* 说明 : 
 	*1,本代码是学习韦东山老师的驱动入门视频所写,增加了注释。
 	*2,采用的是UTF-8编码格式,如果注释是乱码,需要改一下。
 	*3,这是应用层代码
 * 作者 : CSDN风正豪
*/



#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

/* 作用:输入三个参数表示向/dev/xxx文件中写入数据,输入非三个数据,表示向/dev/xxx文件中读取数据。
 * 写 : ./hello_test /dev/xxx 100ask
 * 读 : ./hello_test /dev/xxx
 */
int main(int argc,char** argv)
{
    int fd,len;    //fd:存放文件描述符;len:存放写入字符个数
    char buf[100]; //存放文件中的字符
	//如果输入参数小于2个,打印本程序使用方法
    if(argc < 2)
    {
        printf("Usage:\n");
        printf("%s <dev> [string]\n",argv[0]);
        return -1;
    }

    //打开/dev/xxx文件(设备节点),返回一个文件描述符,我们之后可以直接根据这个文件描述符来操作设备节点,间接操作驱动
    fd = open(argv[1],O_RDWR);
	//如果打开失败
    if(fd < 0)
    {
        printf("can not open file %s\n",argv[1]);
        return -1;
    }

    //如果输入参数为3个,表示写入数据
    if(argc == 3)
    {
		//因为strlen计算字符串长度不会包括'\0',所以需要+1
        len = write(fd,argv[2],strlen(argv[2])+1);
		//打印出写入字符个数
        printf("write ret = %d\n",len);
    }
    //否则为读取数据
    else
    {
		//读入100个字符
        len =read(fd,buf,100);
		//无论传入多少个数据,最多都只会读100个字符
        buf[99] = '\0';
		//打印读取到的字符
        printf("read str : %s\n",buf);
    }

    //关闭文件
    close(fd);
    return 0;
}

予備知識

(1) 習ったばかりなので、発言にズレがあるかもしれません。間に合うように修正してください、ありがとう。

(2) まず、通常、Linux ターミナル上で実行可能ファイルを開き、その実行可能ファイルによってプログラムが実行されます。では、この実行可能ファイルは何をするのでしょうか?

(3) 実行可能ファイルは、最初にアプリケーション層でプログラムを読み込みます。その中には多くのライブラリ関数が含まれます。ライブラリ関数はカーネルに属します。そして、カーネルはドライバー層プログラムを呼び出します。最後のドライバー層は特定のハードウェアを制御します。

<1>実はアプリケーションプログラムからライブラリまでは比較的理解しやすく、例えばC言語を初めて学習したときはprintfやscanfなどの関数を使いました。そしてそれらの関数はライブラリにあります。

<2>ライブラリをシステムカーネルに接続することはできますが、具体的にどうすればよいのかわかりません。

<3>ドライバーを作成しました。このプロセスは登録と呼ばれるものであることをカーネルに伝える必要があります。ドライバーを登録すると、ドライバーに関する情報がカーネル内に存在し、上位層のアプリケーションがそれを呼び出すことができるようになります。

(4) それは無知だと思われますか? 私も無知です。ただし、心配しないでください。2つのプログラムを作成する必要があることを知っておく必要があります。1 つはドライバー層用、もう 1 つはアプリケーション層用です。最後に、アプリケーション層が使用できるようにする前に、ドライバー層をカーネルに登録する必要があります。それそれ以外は放っておいてください。

(5) アプリケーション層で読み取り関数を呼び出します。これはドライバー層の読み取り関数に対応します。write関数はwrite関数に対応します。open関数はopen関数に対応します。close 関数は release 関数に対応します (なぜ異なるのかはわかりません)。

(6) Linux アプリケーションからドライバーを呼び出すプロセスを簡単に理解したら、プログラム作成プロセス全体の実行方法を知る必要があります。なぜこのようなプロセスになるのかについては、思い出すだけです。これらは人によって定められているものなので、深く学んでからさらに勉強しても遅くはなく、今は主に着手中です。

 

ドライバーコードの説明

プロセスの紹介

(1)次に、ドライバーの作成プロセスを紹介しますが、ここまで読んでも、誰もがまだ混乱しています。大丈夫、コードに沿って一つずつ説明していけば誰でも簡単に理解できるはずです

(2) プロセス: (注意してください、一部の人が説明するプロセスは私のものと異なるように見えるかもしれません。慎重に比較する必要があります。そうすれば、言い方が違うだけで、実際には同じであることがわかるでしょう)

<1>まず、ドライバーの管理に使用される file_operations タイプの構造体を記述する必要があります。ドライバーをカーネルに登録した後、アプリケーション層でこのドライバーを呼び出します。この構造を通じて、ドライバー内の開く、書き込み、読み取りなどの機能を直接操作できます。

<2> drv_open/drv_read/drv_write などの対応する関数を実装し、file_operations 構造体を埋めます。このように、アプリケーション層で open、write、read、その他の関数を呼び出すときは、このドライバーを呼び出します。

この時点で、ドライバーがたくさんあるのに、オープンがどのドライバーに対応しているかをどうやって知ることができるのかと疑問に思う人もいるかもしれません。非常に単純ですが、アプリケーション層プログラムを作成するとき、最初のパラメータとしてデバイス番号を渡す必要がありますか? システムはデバイス番号に応じてどのドライバを呼び出すかを判断します。

<3> file_operations 構造体をカーネルに伝えます: register_chrdev。ドライバーを作成しましたが、カーネルはそれを認識しません。じゃあ何をすればいいの?彼を登録すると、カーネルは、このドライバーを使用するとデバイス番号が割り当てられることを理解します。その後、アプリケーション層はデバイス番号に従ってドライバー層を呼び出すことができます。

<4>現時点で、この構造物を誰が登録するのかという疑問を持つ人もいます。したがって、登録するにはエントリ関数が必要であり、このエントリ関数はドライバーのインストール時に呼び出されます。

<5>エントリ関数がある場合は、出口関数が必要です。ドライバをアンロードするときに、出口関数は unregister_chrdev を呼び出します。

<6>最後に、GPL 契約に参加する必要があります。Linux は GPL 契約に準拠しているため、他の Linux ドライバー層機能を使用する必要がある場合は、GPL 契約に準拠し、オープン ソース コードを必要とする必要があります。この契約によれば、Linux を使用しているすべてのメーカーにドライバー層のソース コードの提供を求めることができ、また他のメーカーもドライバー層のコードの開示を求めることができ、これは相互です。しかし、多くのメーカーはこの取り決めを避けるために、ドライバーのソースコードを非常にシンプルにし、アプリケーション層に複雑なものを入れています。著者名の追加については、書いても書かなくても構いません。

コードごとの分析

(1) 最初のステップでは、file_operations 構造体を作成する必要があります。この構造体の static const struct file_operations 部分は、この方法で記述および規定する必要があります。最後に、この構造体の名前は hello_drv でなくても構いません。自由に選択できますが、 register_chrdev はこの構造体名を使用して後で登録します

static const struct file_operations  hello_drv = {

};

(2)

<1> file_operations 構造体を作成した後、要件に従ってこの構造体にパラメータを記述する必要があります。ファイルの場合、オープン、書き込み、読み取り、リリースが必ず必要になります。次に、これらのパラメータにデータを書き込みます。このデータは関数ポインタです。(初心者の方は「 .member 」という構造体が何を意味するのか分からないかもしれません。C言語構造体の詳細説明の代入部分を参照してください。) .owner = THIS_MODULE については、これが存在する必要があり、私は存在しません。その理由が非常に明確です。

<2>ドライバーを作成するときは、印刷関数が printf ではなく printk であることに注意する必要がありますなぜ?カーネルには C 言語ライブラリを使用する方法がないためです

<3>カーネル印刷情報を入力する必要があることに注意してください: echo "7 4 1 7" > /proc/sys/kernel/printk を実行して、カーネル印刷を開きますほとんどのカーネル印刷はデフォルトでオンになっていますが、それでも注意が必要です。

カーネルの印刷情報を表示したくない場合は、単に echo "7 4 1 7" > /proc/sys/kernel/printk と入力します(わからなくても大丈夫です、後ほどデモンストレーションで説明します)

/*
 *传入参数 :
	 *node :
	 *filp :
 *返回参数 : 如果成功返回0
*/
static int hello_open (struct inode *node, struct file *filp)
{
	/*__FILE__ :表示文件
	 *__FUNCTION__ :当前函数名
	 *__LINE__ :在文件的哪一行
	*/
    printk("%s %s %d\n",__FILE__,__FUNCTION__, __LINE__);
    return 0;
}

/*
 *传入参数 :
	 *filp :要读的文件
	 *buf :读的数据放在哪里
	 *size :读多大数据
	 *offset :偏移值(一般不用)
 *返回参数 :读到的数据长度	
*/
static	ssize_t hello_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
	/*__FILE__ :表示文件
	 *__FUNCTION__ :当前函数名
	 *__LINE__ :在文件的哪一行
	*/
    printk("%s %s %d\n",__FILE__,__FUNCTION__, __LINE__);
    return size;
}

/*
 *传入参数 :
	 *filp :要写的文件
	 *buf :写入的数据来自于buf
	 *size :写多大数据
	 *offset :偏移值(一般不用)
 *返回参数 :写的数据长度	
*/
static	ssize_t hello_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
	/*__FILE__ :表示文件
	 *__FUNCTION__ :当前函数名
	 *__LINE__ :在文件的哪一行
	*/
    printk("%s %s %d\n",__FILE__,__FUNCTION__, __LINE__);
    return size;
}

/*作用 : 应用程序关闭的时候调用这个函数
 *传入参数 :
	 *node :
	 *filp :要关闭的文件
 *返回参数 :成功返回0	
*/
static	int hello_release (struct inode *node, struct file *filp)
{
	/*__FILE__ :表示文件
	 *__FUNCTION__ :当前函数名
	 *__LINE__ :在文件的哪一行
	*/
    printk("%s %s %d\n",__FILE__,__FUNCTION__, __LINE__);
    return 0;
}

static const struct file_operations  hello_drv = {
    .owner      = THIS_MODULE,
	.read		= hello_read,
	.write		= hello_write,
	.open		= hello_open,
    .release    = hello_release
};

(3)

<1>このとき、カーネルに file_operations 構造体を伝える必要があります。register_chrdev() 関数を呼び出して、カーネルに file_operations 構造を知らせます。

<2> Wei Dongshan のビデオを見たことがあれば、彼の最初のステップに従ってマスター デバイス番号を決定する必要があることがわかるでしょう。ただし、 register_chrdev() の最初のパラメータに 0 を入力すると、システムは自動的にメジャー デバイス番号を決定し、メジャー デバイス番号を返します。

<3> register_chrdev() の 2 番目のパラメータには、任意の名前を付けることができます。私は魏東山先生の言うことに従っていただけで、それを変えるのが面倒でした。

<4> register_chrdev() の 3 番目のパラメータ。これは、前に定義した file_operations 構造体に関連します。上記で定義したものは static const struct file_operations hello_drv; 構造体名は hello_drv であるため、3 番目のパラメーターは input hello_drv です。

<5>返されたメインデバイス番号は、後でプログラムをアンインストールするときに使用する必要があるため、受け取る必要があります。ただし、アンインストールする予定がない場合は、このメジャー デバイス番号を受け取らなくても大丈夫です

(4)

<1> file_operations 構造体をカーネルに伝えた後、ドライバーを登録する必要がありますが、このエントリー関数にはカーネルに伝えてエントリー関数を確認するためのマクロmodule_init (xxx)が必要です。xxx はエントリ関数の名前です。

<2>そこで問題は、ドライバーが登録されていることがいつわかるのかということです。非常に簡単です。Linux ターミナルで insmod hello_drv.ko プログラムを入力すると、システムは hello_init() 関数を呼び出します。

//在命令行输入insmod命令,就是注册驱动程序。之后就会进入这个入口函数
//3,入口函数
static int  hello_init(void)
{
	/*将hello_drv这个驱动放在内核的第n项,中间传入的名字不重要,第三个是要告诉内核的驱动
	 *因为我们不知道第n项是否已经存放了其他驱动,就可以放在第0项,然后让系统自动往后遍历存放到空的地方
	 *major为最终存放的第n项,等下卸载程序需要使用。如果不卸载程序,可以不管这个
	*/
    major = register_chrdev(0,"100ask_hello",&hello_drv);
	//如果成功注册驱动,打印
	printk("insmod success!\n");
    return 0;
}

module_init(hello_init); //确认入口函数

(5) 入口プログラムがありますが、出口プログラムもなければなりません。このとき、exit 関数を確認するために module_exit (XXX) が必要です。

//在命令行输入rmmod命令,就是注册驱动程序。之后就会进入这个出口函数
//4,出口函数
static void  hello_exit(void)
{
	//卸载驱动程序
	//第一个参数是主设备号,第二个是名字
    unregister_chrdev(major,"100ask_hello");
	//如果成功卸载驱动,打印
	printk("rmmod success!\n");
}

module_exit(hello_exit); //确认出口函数

(6) 最後に、GPL 契約と作成者名を追加します。GPL 契約は必須であり、追加する必要があります。作成者の名前はランダムです。

/*最后我们需要在驱动中加入 LICENSE 信息和作者信息,其中 LICENSE 是必须添加的,否则的话编译的时候会报错,作者信息可以添加也可以不添加
 *这个协议要求我们代码必须免费开源,Linux遵循GPL协议,他的源代码可以开放使用,那么你写的内核驱动程序也要遵循GPL协议才能使用内核函数
 *因为指定了这个协议,你的代码也需要开放给别人免费使用,同时可以根据这个协议要求很多厂商提供源代码
 *但是很多厂商为了规避这个协议,驱动源代码很简单,复杂的东西放在应用层
*/
MODULE_LICENSE("GPL"); //指定模块为GPL协议
MODULE_AUTHOR("CSDN:qq_63922192");  //表明作者,可以不写

コマンドライン操作の説明

コマンドライン操作プロセス

(1) コマンドライン入力処理:

<1>上記のドライバー コードの説明によれば、ドライバー コードが記述された後は、カーネルはドライバー コードを認識しないため、登録する必要があることがわかります。登録方法は 2 つあります。1 つ目は、Linux カーネルの起動時にドライバーが自動的に実行されるように、ドライバーを Linux カーネルにコンパイルする方法です。2 つ目は、ドライバーをモジュールにコンパイルし (Linux でのモジュールの拡張子は .ko です)、 Linux カーネルの起動後に「insmod」コマンドを使用してドライバー モジュールをロードしますドライバーをデバッグするときは、通常、ドライバーをモジュールにコンパイルすることを選択します。これにより、ドライバーを変更した後、Linux コード全体をコンパイルするのではなく、ドライバー コードのみをコンパイルするだけで済みます。また、デバッグ時には、システム全体を再起動することなく、ドライバー モジュールをロードまたはアンロードするだけで済みます。つまり、ドライバーをモジュールとしてコンパイルする最大の利点は、開発に便利であることですドライバーの開発が完了し、問題が確認されたら、ドライバーを Linux カーネルにコンパイルすることができます。
<2>登録が成功したかどうかを確認する必要があるため、 cat /proc/devices: と入力して、現在使用されているデバイス番号を表示する必要があります。lsmod を使用して、カーネルにすでにロードされているドライバーを表示します

<3>デバイスノードを作成します。ドライバーを正常にロードするには、対応するデバイス ノード ファイルを /dev ディレクトリに作成する必要があり、アプリケーション プログラムはデバイス ノード ファイルを操作することによって特定のデバイスの操作を完了します

<4>デバイス ノードが作成されたら、 application /dev/xxx (xxx は任意の名前のデバイス ノード)の形式に従ってアプリケーション層からドライバー層を呼び出す

<5>使用した後、アンインストールする必要がある場合は、「 rmmod driver」

(2) これで、アプリケーションがデバイス ノードを介してドライバーを呼び出す方法がわかりました。それでは、要件について話しましょう。コマンドラインに3つのパラメータを入力するとデータを書き込むことになると考えます。パラメータが 2 つ入力されている場合は、データを読み取ることを意味します。

メイクファイルコード

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o hello_test hello_test.c 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
	rm -f hello_test

obj-m	+= hello_drv.o

アプリケーション層コードの説明

(1) 要件がわかったら運用を行います。ただし、基礎ができていない人がいるのを防ぐために、main関数のargcとargvが何なのかを説明します。

(2) コマンドラインに ./hello_test /dev/xxx 100ask と入力すると、これら 3 つが argv の 2 次ポインタに格納されます。このargv[0] には ./hello_test が格納され、 argv[1] には /dev/xxx が格納され、 argv[2] には 100ask が格納されますそして、argc は に渡されたいくつかのパラメータを格納します。たとえば、今では 3 つのパラメータが渡されています。しかし、パラメータが 3 つあることをどうやって知ることができるのかと尋ねる人もいるでしょう。これは非常に単純で、パラメータをスペースで区切ります。

(3)

<1> 入力パラメータが 2 つ未満の場合、このアプリケーションの使用方法を出力します。同時にプログラムを終了します。

<2>open を使用してこのデバイス ノードを開き、ファイル記述子を返します。このファイル記述子に従ってこのデバイス ノードを操作できます。同時に、アプリケーション層が open 関数を使用するため、カーネルはドライバー層の hello_open() 関数を呼び出します。

<3> ファイルがオープンされていない場合、open 関数は -1 を返します。fd が -1 であるかどうかを判断し、-1 であればプログラムを終了し、デバイス ノードがオープンされていないことを通知します。

<4> 次に、3 つのパラメータが渡されたことを判断する必要があります。パラメータが 3 つある場合、それはデータの書き込みを意味し、アプリケーション層が write 関数を使用するため、カーネルは hello_write() 関数を呼び出します。それ以外の場合は、データの読み取りを意味し、アプリケーション層が読み取り関数を呼び出すため、カーネルは hello_read() 関数を呼び出します。

⑤ 最後にclose関数を呼び出してファイルを閉じます。対応するカーネルは hello_release 関数を呼び出します。

/* 说明 : 
 	*1,本代码是学习韦东山老师的驱动入门视频所写,增加了注释。
 	*2,采用的是UTF-8编码格式,如果注释是乱码,需要改一下。
 	*3,这是应用层代码
 * 作者 : CSDN风正豪
*/



#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

/* 作用:输入三个参数表示向/dev/xxx文件中写入数据,输入非三个数据,表示向/dev/xxx文件中读取数据。
 * 写 : ./hello_test /dev/xxx 100ask
 * 读 : ./hello_test /dev/xxx
 */
int main(int argc,char** argv)
{
    int fd,len;    //fd:存放文件描述符;len:存放写入字符个数
    char buf[100]; //存放文件中的字符
	//如果输入参数小于2个,打印本程序使用方法
    if(argc < 2)
    {
        printf("Usage:\n");
        printf("%s <dev> [string]\n",argv[0]);
        return -1;
    }

    //打开/dev/xxx文件(设备节点),返回一个文件描述符,我们之后可以直接根据这个文件描述符来操作设备节点,间接操作驱动
    fd = open(argv[1],O_RDWR);
	//如果打开失败
    if(fd < 0)
    {
        printf("can not open file %s\n",argv[1]);
        return -1;
    }

    //如果输入参数为3个,表示写入数据
    if(argc == 3)
    {
		//因为strlen计算字符串长度不会包括'\0',所以需要+1
        len = write(fd,argv[2],strlen(argv[2])+1);
		//打印出写入字符个数
        printf("write ret = %d\n",len);
    }
    //否则为读取数据
    else
    {
		//读入100个字符
        len =read(fd,buf,100);
		//无论传入多少个数据,都只会读100个字符
        buf[99] = '\0';
		//打印读取到的字符
        printf("read str : %s\n",buf);
    }

    //关闭文件
    close(fd);
    return 0;
}

エフェクトデモ

皆さんにドライバー層とアプリケーション層をより深く理解してもらうために、ここではカーネル印刷の効果デモをオンにする方法と、カーネルの効果デモをオフにする方法の 2 つのデモ方法に分けて説明します。印刷。

カーネル印刷効果デモを閉じる

(1) make でプログラムをコンパイルします。これはプログラムを Ubuntu でコンパイルする必要があることに注意してください。

(2)

<1> 開発ボードで、Ubuntu とファイルを共有します: mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt。

<2> その後、カーネルの出力を閉じます: echo 0 4 0 7 > /proc/sys/kernel/printk

(3)

<1> hello_drv.koファイルの存在を確認する

<2>insmod hello_drv.ko ロードドライバー

<3>lsmod はドライバーがインストールされていることを確認します

 (4) ドライバーがインストールされていることがわかったので、ドライバーのデバイス番号を知る必要があります。「cat /proc/devices:」と入力して、現在使用されているデバイス番号を表示します。ドライバー名は、ドライバー層で使用する register_chrdev() 関数の 2 番目のパラメーターに関連しています

(5) デバイスノードを作成します: mknod /dev/xyz c 240 0

<1> 「​​/dev/xyz」は作成するノードファイルです。xyzは任意の名前で構いません。

<2> 「c」はキャラクタデバイスであることを示します

<3> 「240」はデバイスのメインデバイス番号で、cat /proc/devices に従ってドライバを表示すると、この値は 240 100ask_hello となるため、ドライバ 100ask_hello のデバイス番号は 240 になります。

<4> 「0」はデバイスのマイナーデバイス番号です。デフォルトでは 0 を書き込みます。

 (6) コマンドライン呼び出し

(7)

<1> 最後にドライバーを削除します: rmmod driver (ドライバーは hello_drv と呼ばれます)

<2> デバイスノードを削除します: rm /dev/xyz

 

 カーネル印刷効果デモを開く

(1) カーネルの印刷をオンにします: echo "7 4 1 7" > /proc/sys/kernel/printk

 (2)

<1>カーネルの印刷がオンになっているため、おかしなことがたくさんありますが、心配しないでください。Shift+Cを押すだけです。

<2> ドライバー層のエントリ関数を呼び出す insmod hello_drv.ko ローダーを入力すると、ここでカーネルは「insmod success!」を出力します。

//在命令行输入insmod命令,就是注册驱动程序。之后就会进入这个入口函数
//3,入口函数
static int  hello_init(void)
{
	/*将hello_drv这个驱动放在内核的第n项,中间传入的名字不重要,第三个是要告诉内核的驱动
	 *因为我们不知道第n项是否已经存放了其他驱动,就可以放在第0项,然后让系统自动往后遍历存放到空的地方
	 *major为最终存放的第n项,等下卸载程序需要使用。如果不卸载程序,可以不管这个
	*/
    major = register_chrdev(0,"100ask_hello",&hello_drv);
	//如果成功注册驱动,打印
	printk("insmod success!\n");
    return 0;
}

(3)

<1> hello_drv.koファイルの存在を確認する

<2>insmod hello_drv.ko ロードドライバー

<3>lsmod はドライバーがインストールされていることを確認します

(4) デバイスノードを作成します: mknod /dev/xyz c 240 0

 (5)

<1> パラメータを 2 つ入力しただけで、カーネルがデータを正常に出力したことがわかり、そこから hello_open、hello_read、hello_release 関数が呼び出されたことがわかります。

<2> カーネルの出力からわかる 3 つのパラメータを入力し、hello_open、hello_read、hello_release 関数を呼び出します。アプリケーション層に相当します。

 

 (6)

<1>ドライバを削除すると、ようやくカーネル情報が正常に出力されることが分かりました。

<2> デバイスノードを削除します: rm /dev/xyz

おすすめ

転載: blog.csdn.net/qq_63922192/article/details/130001370