ホームページ > 運用・保守 > Linuxの運用と保守 > Linuxカーネルには割り込み機能はありますか?

Linuxカーネルには割り込み機能はありますか?

青灯夜游
リリース: 2022-11-11 14:40:54
オリジナル
2510 人が閲覧しました

Linux カーネルには割り込み機能があります。 Linuxカーネルでは、割り込みを使用したい場合は割り込みを申請する必要があり、request_irq()関数を使用して割り込みを申請します。割り込み使用後は、free_irq()関数で対応する割り込みを解放する必要があります。 ) 関数; 他に、enable_irq() と disable_irq() もあり、これらは指定された割り込みを有効または無効にするために使用されます。

Linuxカーネルには割り込み機能はありますか?

#このチュートリアルの動作環境: linux7.3 システム、Dell G3 コンピューター。

1.Linux 割り込み

1.1 Linux 割り込み API 関数

request_irq 関数

Linux カーネルでは、割り込みを使用したい場合は割り込みを申請する必要があります。割り込みの申請には request_irq 関数を使用します。request_irq 関数はスリープを引き起こす可能性があるため、割り込みでは request_irq 関数を使用できませんスリープを禁止するコンテキストまたはその他のコード セグメント。 request_irq 関数は割り込みをアクティブ (有効) にするため、手動で割り込みを有効にする必要はありません。 request_irq 関数のプロトタイプは次のとおりです。割り込みを申請する割り込み番号。
Linuxカーネルには割り込み機能はありますか?handler
: 割り込み処理関数。割り込みが発生したときにこの割り込み処理関数が実行されます。 flags
: 割り込みフラグ。ファイル include/linux/interrupt.h 内のすべての割り込みフラグを表示できます。
name: 割り込み名、設定後、/proc/interrupts ファイルで対応する割り込み名を確認できます。
Linuxカーネルには割り込み機能はありますか?dev
: flags が IRQF_SHARED に設定されている場合、dev はさまざまな割り込みを区別するために使用されます。通常、dev はデバイス構造体に設定され、dev は割り込み処理関数の 2 番目の部分に渡されます。 irq_handler_t パラメータ。 戻り値
: 0 は割り込み適用成功、それ以外の負の値は割り込み適用失敗、-EBUSY が返された場合は割り込み適用済みを意味します。
free_irq

割り込みを使用する場合はrequest_irq関数で申請し、使用完了後はfree_irq関数で該当の割り込みを解放する必要があります。割り込みが共有されていない場合、free_irq は割り込みハンドラーを削除し、割り込みを無効にします。 free_irq 関数のプロトタイプは次のとおりです。

関数のパラメータと戻り値の意味は次のとおりです。

irq
: 解放される割り込み。 Linuxカーネルには割り込み機能はありますか?
dev
: 割り込みが共有 (IRQF_SHARED) に設定されている場合、このパラメーターは特定の割り込みを区別するために使用されます。共有割り込みは、最後の割り込みハンドラーが解放されたときにのみ無効になります。 戻り値
: なし。
割り込み処理関数

request_irq 関数を使用して割り込みを申請する場合、割り込み処理関数の設定が必要です。割り込み処理関数の形式は次のとおりです。


Linuxカーネルには割り込み機能はありますか?割り込み有効化および無効化機能

一般的に使用される割り込みの使用法と無効化関数は次のとおりです。
Linuxカーネルには割り込み機能はありますか?
enable_irq と disable_irq は、指定された割り込みを有効または無効にするために使用されます。irq は無効にする割り込み番号です。 disable_irq 関数は、実行中の割り込み処理関数が完了するまでリターンしないため、新たな割り込みが発生しないこと、および実行を開始したすべての割り込みハンドラが終了していることを確認する必要があります。この場合、別の割り込み無効化関数を使用できます。
Linuxカーネルには割り込み機能はありますか?
disable_irq_nosync この関数は、呼び出された直後に戻り、現在の割り込みハンドラーの実行が完了するのを待ちません。上記の 3 つの関数はすべて、特定の割り込みを有効または無効にします。場合によっては、STM32 を学習するときにグローバル割り込みをオフにすると言われる、現在のプロセッサの割り込みシステム全体をオフにする必要がある場合があります。この場合、次の 2 つを使用できます。関数:
Linuxカーネルには割り込み機能はありますか?
local_irq_enable は現在のプロセッサ割り込みシステムを有効にするために使用され、local_irq_disable は現在のプロセッサ割り込みシステムを無効にするために使用されます。タスク A が local_irq_disable を呼び出して 10 秒間グローバル割り込みをオフにした場合、タスク B が 2 秒間オフになったときに実行を開始します。また、タスク B は local_irq_disable を呼び出して 3 秒間グローバル割り込みをオフにします。3 秒後、タスク B は local_irq_enable 関数を呼び出しますグローバル割り込みをオンにします。このとき、2 3 = 5 秒が経過し、グローバル割り込みがオンになります。このとき、タスク A のグローバル割り込みを 10 秒間オフにしたいという願望は打ち砕かれ、タスク A は「怒る」ことになります。結果は非常に深刻です。おそらくシステムはタスク A によってすべて破壊されるでしょう。この問題を解決するには、Bタスクがlocal_irq_enable関数でグローバル割り込みをONにするだけではなく、割り込み状態を以前の状態に戻すという単純かつ大雑把な方法ではなく、他タスクの気持ちを考慮すると以下の2つの関数を利用する必要があります。 :
Linuxカーネルには割り込み機能はありますか?

1.2 上半分と下半分

資料によっては、上半分と下半分を上半分と呼ぶこともあります。 . 下半分と下半分は同じ意味です。 request_irq で割り込みを申請する際に登録した割り込みサービス関数は、割り込み処理の上半分に属し、割り込みが発生している限り割り込み処理関数が実行されます。割り込み処理関数はできるだけ早く実行する必要があり、短いほど良いことは誰もが知っていますが、現実は往々にして残酷です。割り込み処理機能。たとえば、静電容量式タッチ スクリーンは割り込みを通じてタッチ イベントの発生を SOC に通知し、SOC は割り込みに応答し、IIC インターフェイスを通じてタッチ座標値を読み取り、システムに報告します。しかし、IIC の最大速度はわずか 400Kbit/S であることは誰もが知っているため、割り込み中に IIC を介してデータを読み取ると時間が無駄になります。 IIC を介してタッチ データを読み取る操作を一時的に実行でき、割り込み処理関数は割り込みにのみ応答し、その後割り込みフラグ ビットをクリアします。このとき、割り込み処理プロセスは 2 つの部分に分かれています:
上部: 上部は割り込み処理関数であり、これらの処理プロセスは高速であり、時間のかからない処理は 1 回で完了できます。上部。
下半分: 割り込み処理プロセスに時間がかかる場合は、これらの時間のかかるコードが取り出され、実行のために下半分に渡されるため、割り込み処理関数の出入りが高速になります。
したがって、Linux カーネルが割り込みを上半分と下半分に分割する主な目的は、割り込み処理関数のファストインとファストアウトを実現することです。割り込み処理関数の上部です。上半分でデータをメモリにコピーし、下半分でデータの特定の処理を行うなど、残りの作業はすべて下半分で行うことができます。どのコードが上半分に属し、どのコードが下半分に属するかについては明確な規定はなく、すべて実際に使用して判断するため、ドライバ作成者の腕が問われます。参考になる点をいくつか挙げておきます。
① 処理対象のコンテンツが他の割り込みによって中断されたくない場合は、上半分に配置できます。
②. 処理するタスクが時間に敏感な場合は、上半分に配置できます。
③. 処理するタスクがハードウェア関連の場合は上半分に配置できます
④. 上記 3 点以外のタスクは下半分に優先的に配置します。
下半分のメカニズム:

ソフト割り込み

当初、Linux カーネルは、「BH」と呼ばれる下半分を実装するための「下半分」メカニズムを提供していました。その後、「BH」メカニズムを置き換えるためにソフト割り込みとタスクレットが導入されました。ソフト割り込みとタスクレットを使用して BH を置き換えることができます。Linux カーネルのバージョン 2.5 以降、BH は廃止されました。 Linux カーネルは、softirq_action 構造体を使用してソフト割り込みを表します。softirq_action 構造体は、ファイル include/linux/interrupt.h で定義されています。内容は次のとおりです。
Linuxカーネルには割り込み機能はありますか?
合計 10 個が定義されていますkernel/softirq.c ファイル 以下に示すソフト割り込み:
Linuxカーネルには割り込み機能はありますか?
NR_SOFTIRQS は列挙型で、ファイル include/linux/interrupt.h で定義され、次のように定義されます:
Linuxカーネルには割り込み機能はありますか?
ご覧のとおり、合計 10 個の Softirq があるため、NR_SOFTIRQS は 10 となり、配列 Softirq_vec には 10 個の要素があります。 Softirq_action 構造体の action メンバー変数は、ソフト割り込みのサービス関数です。配列 Softirq_vec はグローバル配列なので、すべての CPU (SMP システムの場合) がアクセスできます。各 CPU には独自のトリガーおよび制御メカニズムがあり、実行のみが行われます。それ自体によってトリガーされるソフト割り込み。ただし、各 CPU によって実行されるソフト割り込みサービス関数は実際には同じであり、それらはすべて配列 Softirq_vec で定義されたアクション関数です。ソフト割り込みを使用するには、まず open_softirq 関数を使用して、対応するソフト割り込み処理関数を登録する必要があります。open_softirq 関数のプロトタイプは次のとおりです。 be Enabled はサンプルコード 51.1 にあります。2.3 から 1 つを選択します。
Linuxカーネルには割り込み機能はありますか?action
: ソフト割り込みに対応する処理関数です。 戻り値
: 戻り値はありません。 ソフト割り込みを登録した後、raise_softirq 関数を通じてトリガーする必要があります。raise_softirq 関数のプロトタイプは次のとおりです:
ソフト割り込みはコンパイル時に静的に登録する必要があります。 Linux カーネルは、softirq_init 関数を使用してソフト割り込みを初期化します。softirq_init 関数は、kernel/softirq.c ファイルで定義されています。関数の内容は次のとおりです:

Linuxカーネルには割り込み機能はありますか?
tasklet
Linuxカーネルには割り込み機能はありますか?

タスクレットは、ソフト割り込みを使用して実装されるもう 1 つの下位半分のメカニズムです。ソフト割り込みとタスクレットの間では、タスクレットを使用することをお勧めします。 Linux カーネル構造体

の 489 行目の func 関数がタスクレットで実行される処理関数であり、ユーザ定義関数の内容は割り込み処理関数に相当します。タスクレットを使用する場合は、最初にタスクレットを定義してから、tasklet_init 関数を使用してタスクレットを初期化する必要があります。taskled_init 関数のプロトタイプは次のとおりです。 #関数のパラメータと戻り値の意味は以下のとおりです。

t
: 初期化対象のタスクレット Linuxカーネルには割り込み機能はありますか?
func
: タスクレットの処理関数。 Linuxカーネルには割り込み機能はありますか?
dataLinuxカーネルには割り込み機能はありますか?: func 関数に渡すパラメータ

戻り値 : 戻り値はありません。 マクロ DECLARE_TASKLET を使用して、タスクレットの定義と初期化を一度に完了することもできます。DECLARE_TASKLET は include/linux/interrupt.h ファイルに定義されており、次のように定義されています:
ここで、 name は定義するタスクレットの名前、この name は tasklet_struct 型の時間変数、func はタスクレット処理関数、data は func 関数に渡されるパラメータです。
上部、つまり割り込み処理関数内で tasklet_schedule 関数を呼び出すことで、適切なタイミングでタスクレットを実行させることができます。 tasklet_schedule 関数のプロトタイプは次のとおりです。タスクレットの参考使用例は以下の通りです。


#Work QueueLinuxカーネルには割り込み機能はありますか?

ワーク キューは、下位​​半分の別の実行メソッドです。ワーク キューは、プロセス コンテキストで実行されます。ワーク キューは、実行のために延期される作業をカーネル スレッドに渡します。ワーク キューはプロセス内で動作するため、コンテキストに応じて、作業キューによりスリープまたは再スケジュールが可能になります。したがって、延期したい作業がスリープできる場合はワーク キューを選択できますが、それ以外の場合はソフト割り込みまたはタスクレットのみを選択できます。
Linux カーネルは work_struct 構造体を使用してジョブを表現します。その内容は次のとおりです (条件付きコンパイルは省略されます):
Linuxカーネルには割り込み機能はありますか?
これらのジョブはワーク キューに編成され、ワー​​ク キューが表現されます。 workqueue_struct 構造体によると、その内容は次のようになります (省略) 条件付きコンパイル):
Linuxカーネルには割り込み機能はありますか?
Linux カーネルは、ワーク キュー内の各ジョブを処理するためにワーカー スレッドを使用します。
Linuxカーネルには割り込み機能はありますか?
サンプル コード 51.1.2.10 からわかるように、各ワーカーにはワーク キューがあり、ワーカー スレッドがすべての作業を処理します。独自の作業キューに入れられます。実際のドライバ開発ではワーク(work_struct)を定義するだけでよく、ワークキューやワーカースレッドについては基本的には気にする必要はありません。ワークの作成は非常に簡単で、work_struct 構造体変数を定義し、INIT_WORK マクロを使用してワークを初期化するだけです。INIT_WORK マクロは次のように定義されます:
Linuxカーネルには割り込み機能はありますか?
Linuxカーネルには割り込み機能はありますか?
Linuxカーネルには割り込み機能はありますか?

1.3 デバイスツリー割り込み情報ノード

デバイスツリーを使用する場合、デバイスツリーに割り込み属性情報を設定する必要があります Linuxカーネルは、割り込み属性情報をデバイスツリーに読み込みます。デバイスツリーで割り込みを設定します。割り込みコントローラーのデバイス ツリー バインディング情報については、ドキュメント Documentation/devicetree/bindings/arm/gic.txt を参照してください。 imx6ull.dtsi ファイルを開きます。intc ノードは I.MX6ULL の割り込みコントローラ ノードです。ノードの内容は次のとおりです:
Linuxカーネルには割り込み機能はありますか?
Line 2、互換性のある属性値は「arm,cortex-a7」です- gic」を実行し、Linux カーネル ソース コードで「arm,cortex-a7-gic」を検索して、GIC 割り込みコントローラ ドライバ ファイルを見つけます。
行 3、#interrupt-cells は、#address-cells、#size-cells と同じです。この割り込みコントローラの下にあるデバイスのセル サイズを示します。デバイスの場合、割り込み属性は割り込み情報を記述するために使用されます。#interrupt-cells は、割り込み属性のセル サイズ、つまり 1 つのデバイスにいくつのセルがあるかを示しますメッセージ。各セルは 32 ビットの整数値です。ARM によって処理される GIC の場合、合計 3 つのセルがあります。これら 3 つのセルの意味は次のとおりです:
最初のセル: 割り込みタイプ、0 は SPI 割り込みを意味し、1 は SPI 割り込みを意味しますPPI 割り込み。
2 番目のセル: 割り込み番号。SPI 割り込みの場合、割り込み番号の範囲は 0 ~ 987、PPI 割り込みの場合、割り込み番号の範囲は 0 ~ 15 です。
3 番目のセル: flag, bit[3:0] は割り込みトリガーのタイプを示します。1 の場合は立ち上がりエッジ トリガーを示し、2 の場合は立ち下がりエッジ トリガーを示します。4 の場合は、 8 の場合はローレベルトリガーを意味します。 bit[15:8] は PPI 割り込みの CPU マスクです。
4 行目では、割り込みコントローラー ノードが空で、現在のノードが割り込みコントローラーであることを示しています。
gpio の場合、gpio ノードは割り込みコントローラーとしても使用できます。たとえば、imx6ull.dtsi ファイルの gpio5 ノードの内容は次のとおりです:
Linuxカーネルには割り込み機能はありますか?
Line 4、interrupts gpio5 の場合、割り込みソース情報は 2 つあり、割り込みタイプは両方とも SPI、トリガー レベルは IRQ_TYPE_LEVEL_HIGH です。違いは割り込みソースにあり、1 つは 74、もう 1 つは 75 です。「IMX6ULL リファレンス マニュアル」の「第 3 章 割り込みと DMA イベント」の章を開いて、図 50.1.3.1 に示す表 3-1 を見つけてください。
Linuxカーネルには割り込み機能はありますか?
図 50.1.3.1 からわかるように、GPIO5 は合計 2 つの割り込み番号 (1 つは 74、もう 1 つは 75) を使用します。このうち、74 は GPIO5_IO00 ~ GPIO5_IO15 の下位 16 IO に対応し、75 は GPIO5_IO16 ~ GPIOI5_IO31 の上位 16 IO に対応します。 8 行目、interrupt-controller は、gpio5 ノードが割り込みコントローラーでもあり、gpio5 のすべての IO
割り込みを制御するために使用されることを示します。
9行目、#interrupt-cellsを2に変更します。
imx6ull-alientek-emmc.dts ファイルを開き、次の内容を見つけます:
Linuxカーネルには割り込み機能はありますか?
Linuxカーネルには割り込み機能はありますか?
Linuxカーネルには割り込み機能はありますか?

1.4 割り込みを取得します。番号

ドライバーを作成するときは、割り込み番号を使用する必要があります。割り込み番号を使用します。割り込み情報はデバイス ツリーに書き込まれているため、対応するデバイス番号は割り込みから抽出できます。 irq_of_parse_and_map 関数を使用して属性を取得します。関数のプロトタイプは次のとおりです:
Linuxカーネルには割り込み機能はありますか?
関数パラメータと戻り値の意味は次のとおりです:
dev: デバイス ノード。
index: インデックス番号、interrupts 属性には複数の割り込み情報が含まれる場合があり、index で取得する情報を指定します。
戻り値: 割り込み番号。
GPIO を使用する場合、gpio_to_irq 関数を使用して、gpio に対応する割り込み番号を取得できます。関数のプロトタイプは次のとおりです:
Linuxカーネルには割り込み機能はありますか?

2. ドライバー コード

#include <linux/types.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/ide.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/errno.h> 
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <asm/mach/map.h>
#include <linux/timer.h>
#include <linux/jiffies.h>

#define IMX6UIRQ_CNT            1               /* 设备号个数 */
#define IMX6UIRQ_NAME           "irqDev"        /* 名字 */
#define KEY0VALUE               0X01            /* KEY0 按键值 */
#define INVAKEY                 0XFF            /* 无效的按键值 */
#define KEY_NUM                 1               /* 按键数量 */

/* 可能会有好多按键,通过结构体数组来描述按键 */
/* 中断 IO 描述结构体 */
struct irq_keydesc {
    int gpio;                               /* gpio */
    int irqnum;                             /* 中断号 */
    unsigned char value;                    /* 按键对应的键值 */
    char name[10];                          /* 名字 */
    irqreturn_t (*handler)(int, void *);    /* 中断服务函数 */
};

/* irq设备结构体 */
struct imx6uirq_dev {
    dev_t               devid;          /* 设备号 */
    struct cdev         cdev;           /* 字符设备 */
    struct class        *class;         /* 类 */
    struct device       *device;        /* 设备 */
    int                 major;          /* 注设备号 */
    int                 minor;          /* 次设备号 */
    struct device_node  *nd;            /* 设备节点 */

    atomic_t            keyvalue;       /* 有效的按键键值 */
    atomic_t            releasekey;     /* 标记是否完成一次完成的按键*/
    struct timer_list   timer;          /* 定义一个定时器*/
    struct irq_keydesc  irqkeydesc[KEY_NUM]; /* 按键描述数组 */
    unsigned char       curkeynum;      /* 当前的按键号 */
};

struct imx6uirq_dev  irqDev; /* 定义LED结构体 */

/* @description : 中断服务函数,开启定时器,延时 10ms,
* 定时器用于按键消抖。
* 两个参数是中断处理函数的必须写法
* @param - irq : 中断号
* @param - dev_id : 设备结构。
* @return : 中断执行结果
*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;

    /* 采用定时器削抖,如果再定时器时间内还是这个值,说明是真的按下了,在定时器中断中处理 */
    /* 这里设置为0是简易处理,因为只有一个按键 */
    /* 有其他按键要再建一个中断处理函数,并把curkeynum改成相应的按键值 */
    /* 注意不能所有按键用一个中断函数,第一是一起按的时候会出错 */
    /* 第二,无法用curkeynum判断使用的是第几个按键 */
    dev->curkeynum = 0;
    /* 传递给定时器的参数,注意要强转,在中断处理函数里面再转回来 */
    dev->timer.data = (volatile long)dev_id;
    /* mod_timer会启动定时器,第二个参数是要修改的超时时间 */
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
    return IRQ_RETVAL(IRQ_HANDLED);
}

 /* @description : 定时器服务函数,用于按键消抖,定时器到了以后
* 再次读取按键值,如果按键还是处于按下状态就表示按键有效。
* @param – arg : 设备结构变量
* @return : 无
*/
void timer_function(unsigned long arg)
{
    unsigned char value;
    unsigned char num;
    struct irq_keydesc *keydesc;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg; 

    /* 因为只有一个按键,这里是0 */
    num = dev->curkeynum;
    keydesc = &dev->irqkeydesc[num]; 
    value = gpio_get_value(keydesc->gpio); /* 读取 IO 值 */

    if(value == 0){ /* 按下按键 */
        atomic_set(&dev->keyvalue, keydesc->value);
    }
    else{ /* 按键松开 */
        /* 这种情况是按下再松开的松开,使用keyValue加上releaseKey */
        /* 没按下的话, releasekey一直为0*/
        atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
        atomic_set(&dev->releasekey, 1); /* 标记松开按键 */ 
    } 
}

/*
* @description : 按键 IO 初始化
* @param : 无
* @return : 无
*/
static int keyio_init(void)
{
    unsigned char i = 0;

    int ret = 0;

    /* 1.获取key节点 */
    irqDev.nd = of_find_node_by_path("/key");
    if (irqDev.nd== NULL){
        printk("key node not find!\r\n");
        return -EINVAL;
    }
    /* 对每个按键都提取 GPIO */
    for (i = 0; i < KEY_NUM; i++) {
        irqDev.irqkeydesc[i].gpio = of_get_named_gpio(irqDev.nd, "key-gpios", i);
        if (irqDev.irqkeydesc[i].gpio < 0) {
            printk("can&#39;t get key%d\r\n", i);
        }
    }

    /* 初始化 key 所使用的 IO,并且设置成中断模式 */
    for (i = 0; i < KEY_NUM; i++) {
        /* 先对每一个IO命名 */
        /* 先对命名清0 */
        memset(irqDev.irqkeydesc[i].name, 0, sizeof(irqDev.irqkeydesc[i].name)); 
        /* 给IO命名 */
        sprintf(irqDev.irqkeydesc[i].name, "KEY%d", i); 
        /* 请求GPIO */
        gpio_request(irqDev.irqkeydesc[i].gpio, irqDev.irqkeydesc[i].name);
        
        /* 设置GPIO为输入 */
        gpio_direction_input(irqDev.irqkeydesc[i].gpio); 

        /* 获取中断号,以下为两个方法,都可以获取到 */
        /* 从interrupts属性里面获取 */
        /* 注意i是根据设备树里面设置了多少个就是多少个,都会获取到 */
        /* 下面的方法是通用的获取中断号的函数 */
        irqDev.irqkeydesc[i].irqnum = irq_of_parse_and_map(irqDev.nd, i);
#if 0
        /* 此方法是gpio获取中断号的方法 */
        irqDev.irqkeydesc[i].irqnum = gpio_to_irq(irqDev.irqkeydesc[i].gpio);
#endif
        printk("key%d:gpio=%d, irqnum=%d\r\n", i, irqDev.irqkeydesc[i].gpio,
                                                  irqDev.irqkeydesc[i].irqnum);
    }

    /* 2. 按键中断初始化 */
    /* 设置中断处理函数和按键初始值 */
    /* 因为只有一个key0.,所以这里也没用循环 */
    irqDev.irqkeydesc[0].handler = key0_handler;
    irqDev.irqkeydesc[0].value = KEY0VALUE;
     /* 申请中断 */
    for (i = 0; i < KEY_NUM; i++) {
        /* request_irq参数
         * 中断号,中断函数,中断触发类型,中断名字,传递给中断处理函数的参数(第二个),这里传的结构体
         * */
        ret = request_irq(irqDev.irqkeydesc[i].irqnum, irqDev.irqkeydesc[i].handler,
                        IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
                        irqDev.irqkeydesc[i].name, &irqDev);
        if(ret < 0){
            printk("irq %d request failed!\r\n", irqDev.irqkeydesc[i].irqnum);
            return -EFAULT;
        }
    }

    /* 3. 创建定时器 */
    init_timer(&irqDev.timer);
    irqDev.timer.function = timer_function;
    /* 注意下面不能让定时器运行,因为要按下按键之后再运行 */
    /* 启动定时器通过mod_timer启动,通常在初始化阶段的定时器用的是add_timer */
    return 0;
}


static int imx6uirq_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &irqDev;
    return 0;
}

static int imx6uirq_release(struct inode *inode, struct file *filp)
{
    //struct imx6uirq_dev  *dev = (struct imx6uirq_dev  *)filp->private_data;
    return 0;
}

 /*
* @description : 从设备读取数据
* @param – filp : 要打开的设备文件(文件描述符)
* @param – buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param – offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char keyvalue = 0;     /* 按键值 */
    unsigned char releasekey = 0;   /* 标记是否一次完成 */
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

    keyvalue = atomic_read(&dev->keyvalue);
    releasekey = atomic_read(&dev->releasekey);

    if (releasekey) { /* 有按键按下 */
        if (keyvalue & 0x80) {
            keyvalue &= ~0x80; /* 因为中断中或了一个0x80,这里面去掉0x80 */
            ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
        } else {
            goto data_error;
        }
        atomic_set(&dev->releasekey, 0); /* 按下标志清零 */
    } else { /* 没有按下 */
        goto data_error;
    }
    return 0;

data_error:
    return -EINVAL;
}

/* 字符设备操作集 */
static const struct file_operations imx6uirq_fops = {
    .owner = THIS_MODULE,
    .open = imx6uirq_open,
    .release = imx6uirq_release,
    .read = imx6uirq_read
};

/* 模块入口函数 */
static int __init imx6uirq_init(void)
{
    /* 定义一些所需变量 */
    int ret = 0;

    /* 1. 注册字符设备驱动 */
    irqDev.major = 0;
    if(irqDev.major) {
        irqDev.devid = MKDEV(irqDev.major, 0);
        ret = register_chrdev_region(irqDev.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME );  
    } else {
        alloc_chrdev_region(&irqDev.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME );
        irqDev.major = MAJOR(irqDev.devid);
        irqDev.minor = MINOR(irqDev.devid);
    }
    if(ret < 0){
        goto fail_devid;
    }
    printk("Make devid success! \r\n");
    printk("major = %d, minor = %d \r\n", irqDev.major, irqDev.minor);

    /* 2. 初始化cdev */
    irqDev.cdev.owner = THIS_MODULE;
    cdev_init(&irqDev.cdev, &imx6uirq_fops);
    ret = cdev_add(&irqDev.cdev, irqDev.devid, IMX6UIRQ_CNT);
    if (ret < 0){
        goto fail_cdev;
    } else {
        printk("Cdev add sucess! \r\n");
    }

    /* 3. 自动创建设备节点 */
    irqDev.class = class_create(THIS_MODULE, IMX6UIRQ_NAME );
    if(IS_ERR(irqDev.class)) {
        ret = PTR_ERR(irqDev.class);
        goto fail_class;
    } else {
        printk("Class create sucess! \r\n");
    }

    irqDev.device = device_create(irqDev.class, NULL, irqDev.devid, NULL, IMX6UIRQ_NAME );
    if(IS_ERR(irqDev.device)) {
        ret = PTR_ERR(irqDev.device);
        goto fail_device;
    } else {
        printk("Device create sucess! \r\n");
    }

    /* 4.初始化按键 */
    atomic_set(&irqDev.keyvalue, INVAKEY);
    atomic_set(&irqDev.releasekey, 0);
    keyio_init();

    printk("irqDev init! \r\n");
    return 0;

/* 错误处理 */
fail_device:
    class_destroy(irqDev.class);
fail_class:
    cdev_del(&irqDev.cdev);
fail_cdev:
    unregister_chrdev_region(irqDev.devid, IMX6UIRQ_CNT);
fail_devid:
    return ret;
}

/* 模块出口函数 */
static void __exit imx6uirq_exit(void)
{
    unsigned int i = 0;
    /* 删除定时器 */
    del_timer_sync(&irqDev.timer); 

    /* 释放中断 */
    for (i = 0; i < KEY_NUM; i++) {
        free_irq(irqDev.irqkeydesc[i].irqnum, &irqDev);
    }

    /* 1. 释放设备号 */
    cdev_del(&irqDev.cdev);
    /* 2. 注销设备号 */
    unregister_chrdev_region(irqDev.devid, IMX6UIRQ_CNT);
    /* 3. 摧毁设备 */
    device_destroy(irqDev.class, irqDev.devid);
    /* 4.摧毁类 */
    class_destroy(irqDev.class);


    printk("irqDev exit! \r\n");
}

/* 模块入口和出口注册 */
module_init(imx6uirq_init);
module_exit(imx6uirq_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Shao Zheming");
ログイン後にコピー

3. アプリケーション コード

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include "linux/ioctl.h"

/* 
 * argc: 应用程序参数个数
 * argv[]: 参数是什么,具体的参数,说明参数是字符串的形式
 * .chrdevbaseApp <filename> <0:1> 0表示关灯,1表示开灯
 * .chrdevbaseApp /dev/led 0 关灯
 * .chrdevbaseApp /dev/led 1 开灯
 * */


int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        printf("Error Usage!\r\n");
        return -1;
    }
    
    int fd, ret;
    char *filename;
    unsigned char data;

    filename = argv[1];
    fd = open(filename, O_RDWR);
    if(fd < 0)
    {
        printf("file %s open failed! \r\n", filename);
        return -1;
    }

    while (1) {
        ret = read(fd, &data, sizeof(data));
        if (ret < 0) { /* 数据读取错误或者无效 */
        
        } else { /* 数据读取正确 */
            if (data) /* 读取到数据 */
                printf("key value = %#X\r\n", data);
        }
    }

    close(fd);
    return 0;
}
ログイン後にコピー
ログイン後にコピー

4. タスクレットを使用して割り込みの後半を処理します

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include "linux/ioctl.h"

/* 
 * argc: 应用程序参数个数
 * argv[]: 参数是什么,具体的参数,说明参数是字符串的形式
 * .chrdevbaseApp <filename> <0:1> 0表示关灯,1表示开灯
 * .chrdevbaseApp /dev/led 0 关灯
 * .chrdevbaseApp /dev/led 1 开灯
 * */


int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        printf("Error Usage!\r\n");
        return -1;
    }
    
    int fd, ret;
    char *filename;
    unsigned char data;

    filename = argv[1];
    fd = open(filename, O_RDWR);
    if(fd < 0)
    {
        printf("file %s open failed! \r\n", filename);
        return -1;
    }

    while (1) {
        ret = read(fd, &data, sizeof(data));
        if (ret < 0) { /* 数据读取错误或者无效 */
        
        } else { /* 数据读取正确 */
            if (data) /* 读取到数据 */
                printf("key value = %#X\r\n", data);
        }
    }

    close(fd);
    return 0;
}
ログイン後にコピー
ログイン後にコピー

5. ワークキューの処理 下部

#開発方法はタスクレットと同じです
ワークはデバイスの開発構造を推定できるため、ワークは一般的に配置されることに注意してください開発構造内

関連する推奨事項: 「Linux ビデオ チュートリアル

以上がLinuxカーネルには割り込み機能はありますか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート