Linuxドライバー開発ノート(5):ユーザー層とカーネル層をつなぐドライバーのファイル操作セットの原理とデモ

記事がオリジナル記事の場合、転載する際は出典元を明記してください
この記事のブログアドレス:https ://hpzwl.blog.csdn .net/article/details/134561660

赤太男のネットワーク技術ブログ記事集: 開発技術集 (Qt 実践技術、Raspberry Pi、3D、OpenCV、OpenGL、ffmpeg、OSG、マイクロコントローラー、ソフトウェアとハ​​ードウェアの組み合わせなど) が継続的に更新されています。 ..

Linuxシステム移植とドライバ開発コラム

前の記事: 「Linux ドライバー開発ノート (4): デバイス ドライバーの概要、さまざまなデバイス ドライバーについての知識、およびさまざまなデバイスの ubuntu 開発のデモ
次の記事: 乞うご期待...


序文

  ドライバーが記述された後、ユーザー層はシステム関数呼び出しを使用して関連ドライバーを操作し、システム カーネルとの関連付けを実現します この記事は主に、ドライバーによってユーザー プログラミングがカーネルとどのように対話できるかを理解することを目的としています。


その他のデバイス ファイル操作セット

cd /usr/src/linux-headers-4.18.0-15
vi include/linux/fs.h

  検索対象 (vi は「/」を直接使用):
  

struct file_operations {
    
    
        struct module *owner;
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
        ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
        ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
        int (*iterate) (struct file *, struct dir_context *);
        int (*iterate_shared) (struct file *, struct dir_context *);
        __poll_t (*poll) (struct file *, struct poll_table_struct *);
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        unsigned long mmap_supported_flags;
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *, fl_owner_t id);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, loff_t, loff_t, int datasync);
        int (*fasync) (int, struct file *, int);
        int (*lock) (struct file *, int, struct file_lock *);
        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
        int (*check_flags)(int);
        int (*setfl)(struct file *, unsigned long);
        int (*flock) (struct file *, int, struct file_lock *);
        ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
        ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
        ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
                        loff_t, size_t, unsigned int);
        int (*setlease)(struct file *, long, struct file_lock **, void **);
        long (*fallocate)(struct file *file, int mode, loff_t offset,
                          loff_t len);
        void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
        unsigned (*mmap_capabilities)(struct file *);
#endif

        int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
                        u64);
        ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *,
                        u64);
} __randomize_layout;

  たとえば、読み取り関数では、ドライバーを開いてシステム読み取りを使用します。デバイスドライバーのハンドルを開くと、読み取り関数が呼び出されます。残りは類推でき、比較的理解しやすいです。 。


Linuxファイル操作セットの意味

概要

  Linux ではすべてがファイルであり、開く、閉じる、読み書きするなどの対応する操作があり、これらの操作はファイルを開いた後のハンドルで表され、その種類に応じて機能が決まります。開かれたハンドルなど。その他のデバイス ドライバーの場合は、その他のデバイス操作ファイルの文字セット内の対応する関数を呼び出して操作を実行します。
  プログラミングするとき、open を使用してデバイス ノード (ファイルまたはデバイス) を開きます。このとき、デバイス ノード ハンドル識別子 fd が返されます (失敗は -1)。 , 次に fd を使用します. 読み取りや書き込みなどの各種操作を削除することは、このデバイス ドライバーに設定されているファイル操作の読み取りや書き込みを呼び出すことと同じになります。
  一般的に使用されるファイル操作は次のとおりです。

オープン機能(実装テスト)

int (*open) (struct inode *, struct file *);

読み取り機能(実装テスト)

ssize_t (*read) (構造体ファイル *, char __user *, size_t, loff_t *)

書き込み関数(実装テスト)

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

投票/選択機能 (この記事には書かれていません)

__poll_t (*poll) (構造体ファイル *、構造体poll_table_struct *);

ioctl 関数 (この記事には書かれていません)

long (*unlocked_ioctl) (構造体ファイル *、unsigned int、unsigned long);

close関数(実装テスト)

int (*release) (struct inode *, struct file *);


ドライバーテンプレートの準備

  まず、前の registerMiscDev ドライバーをコピーし、その名前を testFileOpts: に変更します。

cd ~/work/drive
cp -arf registerMiscDev testFileOpts
cd testFileOpts
make clean
mv registerMiscDev testFileOpts.c

  ここに画像の説明を挿入します

   その後、makefile を修正(obj-m モジュール名を変更)すると、テンプレートが完成します。

gedit Makefile  

  ここに画像の説明を挿入します

  以下は、testFileOpts.c ファイルに基づいて、さまざまなデバイスを登録し、.c ファイルを変更します。

gedit testFileOpts.c

  ここに画像の説明を挿入します

#include <linux/init.h>
#include <linux/module.h>

#include <linux/miscdevice.h>
#include <linux/fs.h>

struct file_operations misc_fops = {
    
    
  .owner = THIS_MODULE,
};

struct miscdevice misc_dev = {
    
    
    .minor = MISC_DYNAMIC_MINOR, // 这个宏是动态分配次设备号,避免冲突
    .name = "register_hongPangZi_testFileOpt", // 设备节点名称
    .fops = &misc_fops,  // 这个变量记住,自己起的,步骤二使用
};

static int registerMiscDev_init(void)
{
    
     
    int ret;
    // 在内核里面无法使用基础c库printf,需要使用内核库printk
    printk("Hello, I’m hongPangZi, registerMiscDev_init\n");	
    ret = misc_register(&misc_dev);
    if(ret < 0)
    {
    
    
        printk("Failed to misc_register(&misc_dev)\n");	
        return -1;
    } 
    return 0;
}

static void registerMiscDev_exit(void)
{
    
    
    misc_deregister(&misc_dev);
    printk("bye-bye!!!\n");
}

MODULE_LICENSE("GPL");

module_init(registerMiscDev_init);
module_exit(registerMiscDev_exit);

その他の機器に共通操作セットオープン機能を追加 デモ

  呼び出された関数が記述されていない場合、エラーは報告されず、他の操作は発生しないため、すべての関数を記述する必要はないことに注意してください。

ステップ 1: open 関数を実装する

// int (*open) (struct inode *, struct file *);
int misc_open(struct inode * pInode, struct file * pFile)
{
    
    
    printk("int misc_open(struct inode * pInode, struct file * pFile)");
    return 0;
}

  ここに画像の説明を挿入します

ステップ 2: (キー) ファイル操作セット ポインタへの割り当て

  ここに画像の説明を挿入します

ステップ 3: ドライバーをコンパイルしてロードする

  まずコンパイルしてみます。
  ここに画像の説明を挿入します

  次にドライバーをロードします。

sudo insmod tesFileOpts.ko

  ここに画像の説明を挿入します

  ここに画像の説明を挿入します

  ここに画像の説明を挿入します

  これでデバイスノードの登録は成功です。

ステップ 4: プログラム内で open device ノード open を呼び出す

  この手順は C 言語プログラミングです。Linux システム関数を使用してデバイス ノードを開きます:
  新しいファイルを作成します:

vi test.c

  コードを入力します:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char **argv)
{
    
    
  int fd;
  const char devPath[] = "/dev/register_hongPangZi_testFileOpt";
  fd = open(devPath, O_RDWR);
  if(fd < 0)
  {
    
    
    printf("fialed to open %s\n", devPath);
    return -1;
  } else{
    
    
    printf("Succeed to open %s\n", devPath);
  }
  return 0;
}

  ここに画像の説明を挿入します

  編集:

gcc test.c

  ここに画像の説明を挿入します

  デフォルトの出力は a.out なので、以下のように実行します。
  ここに画像の説明を挿入します

  Ubuntu ではデバイスの管理者権限が必要なため実行できません。管理者権限で実行するには:
  ここに画像の説明を挿入します

  カーネルのプリントアウトを見てください(ここにはプリントアウトはありません。「ピット 1 に入る」を確認してください)。
  ここに画像の説明を挿入します

  この時点で、ユーザー プログラミング層からは、デバイス ノードにアクセスしてカーネル層関数を呼び出す方法は基本的に明らかです。


補足その他機能デモ

読み取りと書き込みの補足

#include <linux/init.h>
#include <linux/module.h>

#include <linux/miscdevice.h>
#include <linux/fs.h>

// int (*open) (struct inode *, struct file *);
int misc_open(struct inode * pInode, struct file * pFile)
{
    
    
    printk("int misc_open(struct inode * pInode, struct file * pFile\n)");
    return 0;
}

// int (*release) (struct inode *, struct file *);
int misc_release(struct inode * pInde, struct file * pFile)
{
    
    
    printk("int misc_release(struct inode * pInde, struct file * pFile\n)");
    return 0;
}

// ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t misc_read(struct file * pFile, char __user * pUser, size_t size, loff_t *pLofft)
{
    
    
    printk("ssize_t misc_read(struct file * pFile, char __user * pUser, size_t size, loff_t *pLofft)\n");
    return 0;
}

// ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t misc_write(struct file * pFile, const char __user * pUser, size_t size, loff_t *pLofft)
{
    
    
    printk("ssize_t misc_write(struct file * pFile, const char __user * pUser, size_t size, loff_t *pLofft)\n");
    return 0;
}

struct file_operations misc_fops = {
    
    
  .owner = THIS_MODULE,
  .open = misc_open,
  .release = misc_release,
  .read = misc_read,
  .write = misc_write,
};

struct miscdevice misc_dev = {
    
    
    .minor = MISC_DYNAMIC_MINOR, // 这个宏是动态分配次设备号,避免冲突
    .name = "register_hongPangZi_testFileOpt", // 设备节点名称
    .fops = &misc_fops,  // 这个变量记住,自己起的,步骤二使用
};

static int registerMiscDev_init(void)
{
    
     
    int ret;
    // 在内核里面无法使用基础c库printf,需要使用内核库printk
    printk("Hello, I’m hongPangZi, registerMiscDev_init\n");	
    ret = misc_register(&misc_dev);
    if(ret < 0)
    {
    
    
        printk("Failed to misc_register(&misc_dev)\n");	
        return -1;
    } 
    return 0;
}

static void registerMiscDev_exit(void)
{
    
    
    misc_deregister(&misc_dev);
    printk("bye-bye!!!\n");
}

MODULE_LICENSE("GPL");
module_init(registerMiscDev_init);
module_exit(registerMiscDev_exit);

test.c テスト ドライバーのソース コードを変更する

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char **argv)
{
  int fd;
  char buf[32] = {0};

  const char devPath[] = "/dev/register_hongPangZi_testFileOpt";
  fd = open(devPath, O_RDWR);
  if(fd < 0)
  {
    printf("Failed to open %s\n", devPath);
    return -1;
  }else{
    printf("Succeed to open %s\n", devPath);
  }

  read(fd, buf, sizeof(buf));
  write(fd, buf, sizeof(buf));

  close(fd);
  printf("exit\n");
  fd = -1;
  return 0;
}

出力を表示する

  ここに画像の説明を挿入します


罠にはまる

落とし穴 1: カーネルが open 関数を出力しない

質問

  プログラムは別のノードを開きますが、open 関数は出力されません。

理由

  ファイル操作セットにはオープン機能が割り当てられていません。

解決する

  ここに画像の説明を挿入します

落とし穴 2: dmesg にはクローズ リリース印刷がありません

質問

  ここに画像の説明を挿入します

テスト

  ここに画像の説明を挿入します

  ここに画像の説明を挿入します

  dmesgを勉強したのですが、出てきませんでした。よくわかりませんでした。後でドライバーの上司に聞いたら、改行の問題ではないかと教えてもらい、後から追加したらうまくいきました。

解決

  ここに画像の説明を挿入します
  ここに画像の説明を挿入します


前の記事: 「Linux ドライバー開発ノート (4): デバイス ドライバーの概要、さまざまなデバイス ドライバーについての知識、およびさまざまなデバイスの ubuntu 開発のデモ
次の記事: 乞うご期待...


この記事のブログ アドレス:https://hpzwl.blog.csdn.net/article/details/134561660

おすすめ

転載: blog.csdn.net/qq21497936/article/details/134561660