次のコンテンツでは、Linuxカーネルの知識ポイントについて説明します。
Linuxカーネルタイマーとは何ですか?
カーネルタイマーは、将来の特定の時点で(jiffiesに基づいて)関数の実行を制御するためにカーネルによって使用されるメカニズムであり、その実装は<linux /timer.h>ファイルとkernel / timer.cファイルにあります。
スケジュールされた機能は非同期で実行する必要があります。これは「ソフトウェア割り込み」に似ており、非プロセスコンテキストにあるため、スケジュール機能は次のルールに準拠する必要
があります。1)現在のポインタがなく、ユーザースペースへのアクセスが許可されていません。プロセスコンテキストがないため、関連するコードは中断されたプロセスとは関係がありません。
2)スリープ(またはスリープを引き起こす可能性のある機能)およびスケジューリングを実行できません。
3)アクセスされるデータ構造は、レース状態を防ぐために同時アクセスから保護する必要があります。
カーネルタイマーのスケジューリング機能が1回実行されると、実行されなくなります(自動ログアウトと同等)が、スケジュールされた関数で自身を再スケジュールすることにより、定期的に実行できます。
SMPシステムでは、キャッシュの局所性を可能な限り取得するために、スケジューリング機能は常にそれを登録したのと同じCPUで実行されます。
hrtimerとは何ですか?
高精度タイマー(hrtimer)について説明する前に、通常のソフトウェアタイマー(timer)について説明しましょう。
通常のソフトウェアタイマー(低解像度タイマーとも呼ばれます)。いわゆる低解像度タイマーとは、jiffies値に基づいてこのタイマーのカウント単位を指します。つまり、精度は1 / HZのみです。カーネル構成のHZは1000です。これは、システムの低解像度タイマーの精度が1msであることを意味します。
目的:低解像度タイマーはほぼ「タイムアウト」用に設計されており、多くの最適化が行われています。目的もなく「タイムアウト」に使用されるこれらのタイマーのほとんどは、タイムアウトが発生する前に取得することを期待しています。正しい結果を出してからタイマーを削除すると、ネットワーク通信やデバイスIOなど、正確な時刻は主な目的ではありません。
単純な低精度タイマーの使用例:
struct timer_list timer;
......
init_timer(&timer);
timer.function = _function;
timer.expires = _expires;
timer.data = _data;
add_timer(&timer);
mod_timer(&timer, jiffies+50);
del_timer(&timer);
高精度タイマーは、カーネルの継続的な進化に伴い、ハードウェアも絶えず開発されており、システム内のタイマーハードウェアの精度はますます高くなっています。これは、高解像度タイマーの出現の条件にもなります。2.6.16以降、高精度タイマーアーキテクチャがカーネルに追加されました。高精度タイマーは、マルチメディアアプリケーション、オーディオデバイスドライバーなど、正確な時間を緊急に必要とするアプリケーションまたはカーネルドライバーに対応するために、ナノ秒のタイミング精度を提供します。
hrtimerはどのように機能しますか?
hrtimerの実装には、timekeeperとclock_event_deviceに依存する特定のハードウェア基盤が必要です。
hrtimerシステムは、タイムキーパーを介して現在の時刻を取得し、有効期限との差を計算し、差に応じてcpuのtick_device(clock_event_device)の次の有効期限を設定する必要があります。時間が経過すると、clock_event_deviceのイベント期限切れのhrtimerは、コールバック関数で処理されます。
void hrtimer_init(struct hrtimer *timer, clockid_t which_clock,
enum hrtimer_mode mode);
int hrtimer_start(struct hrtimer *timer, ktime_t tim,
const enum hrtimer_mode mode);
nt hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim,
unsigned long range_ns, const enum hrtimer_mode mode);
int hrtimer_cancel(struct hrtimer *timer);
u64 hrtimer_forward(struct hrtimer *timer, ktime_t now, ktime_t interval);
static inline u64 hrtimer_forward_now(struct hrtimer *timer,ktime_t interval)
hrtimer期限切れ処理
精度のタイマーシステムは、それらが有効期限タイマーを処理することができる3つの入口を有している:
なし高精度スイッチングモード、割り込みイベントティック各jiffieのクエリ処理。
HRTIMER_SOFTIRQソフト割り込みでのクエリと処理。
高精度モードに切り替えた後、各clock_event_deviceの有効期限イベント割り込みでクエリと処理を行います。
高精度モードに切り替える方法は?
hrtimer_run_pending
tick_check_oneshot_change
ts->check_clocks
ts->nohz_mode
hrtimer_is_hres_enabled
hrtimer_switch_to_hres
hrtimerの原理は何ですか?高精度のタイミングを実現するには?
ハードウェアタイマーに基づいて、一連のhrtimerアーキテクチャが設計され、各コアには独自のhrtimerがあり、個別に保守されます。
原理:高精度タイマーは、タイムキーパーを介して現在時刻を取得し、有効期限との差を計算し、差に応じてcpuのtick_device(clock_event_device)の次の有効期限を設定します。時間が経過すると、期限切れのhrtimerは、clock_event_deviceのイベントコールバック関数で処理されます。
実装方法:高精度時間の原理を理解すると、実装方法を理解できます。まず、ハードウェアでサポートし、hrtimerでカーネルを構成し、高精度タイマーを追加します。常に高精度を達成できるとは限りません。
カーネルhrtimerの構成?
CONFIG_RTC_HCTOSYS
CONFIG_NO_HZ
CONFIG_HIGH_RES_TIMERS
hrTimerソフトウェアアーキテクチャ
struct hrtimer {
struct timerqueue_node node;
ktime_t _softexpires;
enum hrtimer_restart (*function)(struct hrtimer *);
struct hrtimer_clock_base *base;
unsigned long state;
......
};
定时器的到期时间用ktime_t来表示,_softexpires字段记录了时间,定时器一旦到期,function字段指定的回调函数会被调用,该函数的返回值为一个枚举值,它决定了该hrtimer是否需要被重新激活:
enum hrtimer_restart {
HRTIMER_NORESTART, /* Timer is not restarted */
HRTIMER_RESTART, /* Timer must be restarted */
};
state字段用于表示hrtimer当前的状态,有几下几种位组合:
#define HRTIMER_STATE_INACTIVE 0x00 // 定时器未激活
#define HRTIMER_STATE_ENQUEUED 0x01 // 定时器已经被排入红黑树中
#define HRTIMER_STATE_CALLBACK 0x02 // 定时器的回调函数正在被调用
#define HRTIMER_STATE_MIGRATE 0x04 // 定时器正在CPU之间做迁移
hrtimer的到期时间可以基于以下几种时间基准系统:
enum hrtimer_base_type {
HRTIMER_BASE_MONOTONIC, // 单调递增的monotonic时间,不包含休眠时间
HRTIMER_BASE_REALTIME, // 平常使用的墙上真实时间
HRTIMER_BASE_BOOTTIME, // 单调递增的boottime,包含休眠时间
HRTIMER_MAX_CLOCK_BASES, // 用于后续数组的定义
};
和低分辨率定时器一样,处于效率和上锁的考虑,每个cpu单独管理属于自己的hrtimer,为此,专门定义了一个结构hrtimer_cpu_base:
struct hrtimer_cpu_base {
......
struct hrtimer_clock_base clock_base[HRTIMER_MAX_CLOCK_BASES];
};
其中,clock_base数组为每种时间基准系统都定义了一个hrtimer_clock_base结构,它的定义如下:
struct hrtimer_clock_base {
struct hrtimer_cpu_base *cpu_base; // 指向所属cpu的hrtimer_cpu_base结构
......
struct timerqueue_head active; // 红黑树,包含了所有使用该时间基准系统的hrtimer
ktime_t resolution; // 时间基准系统的分辨率
ktime_t (*get_time)(void); // 获取该基准系统的时间函数
ktime_t softirq_time;// 当用jiffies
ktime_t offset; //
};
active字段是一个timerqueue_head结构,它实际上是对rbtree的进一步封装:
struct timerqueue_node {
struct rb_node node; // 红黑树的节点
ktime_t expires; // 该节点代表队hrtimer的到期时间,与hrtimer结构中的_softexpires稍有不同
};
struct timerqueue_head {
struct rb_root head; // 红黑树的根节点
struct timerqueue_node *next; // 该红黑树中最早到期的节点,也就是最左下的节点
};
timerqueue_head结构在红黑树的基础上,增加了一个next字段,用于保存树中最先到期的定时器节点,实际上就是树的最左下方的节点,有了next字段,当到期事件到来时,系统不必遍历整个红黑树,只要取出next字段对应的节点进行处理即可。timerqueue_node用于表示一个hrtimer节点,它在标准红黑树节点rb_node的基础上增加了expires字段,该字段和hrtimer中的_softexpires字段一起,设定了hrtimer的到期时间的一个范围,hrtimer可以在hrtimer._softexpires至timerqueue_node.expires之间的任何时刻到期,我们也称timerqueue_node.expires为硬过期时间(hard),意思很明显:到了此时刻,定时器一定会到期,有了这个范围可以选择,定时器系统可以让范围接近的多个定时器在同一时刻同时到期,这种设计可以降低进程频繁地被hrtimer进行唤醒。经过以上的讨论,我们可以得出以下的图示,它表明了每个cpu上的hrtimer是如何被组织在一起的:
hrtimerが赤黒ツリーアーキテクチャを使用するのはなぜですか?
高解像度タイマーの場合、データ構造には少なくとも次の調整が必要です。(安定した高速検索機能、高速挿入および削除タイマー機能、並べ替え機能)
カーネルの開発者は、基数ツリーなどのさまざまなデータ構造を調査しました。 、ハッシュテーブルなど、最終的には、hrtimerを編成するために赤黒ツリー(rbtree)を選択しました。赤黒ツリーは、ライブラリの形式でカーネルに存在
し、メモリ管理サブシステムとファイルシステムで正常に使用されています。システムの実行中、hrtimerは常に作成および破棄されます。新しいhrtimerが赤黒のツリーに順番に挿入され
ます。ツリーの左端のノードが最も早く期限切れになるタイマーです。カーネルはhrtimer構造を使用して1つを表します。高精度タイマー。
hrtimerはどのように機能しますか?
高精度タイマーシステムには、期限切れのタイマーを処理するための3つのエントリがあります。
高精度モードに切り替えられていない場合、各jiffieティックイベント割り込みでクエリと処理を行います
。HRTIMER_SOFTIRQソフト割り込みでクエリと処理;
高精度モードに切り替えた後、各clock_event_device有効期限イベント割り込みでクエリと処理を行います。
hrtimerのタイミング時間を1ms未満にする方法は?hrtimerタイマーの精度に関しては
、ハードウェアがサポートし、カーネルがサポートしている限り、タイミング時間は1ms未満にすることができます。それ以外の場合、hrtimerはクロックTICKに従って高精度タイマーを呼び出します。
問題の説明:
最初に、カーネルで高解像度タイマーのサポートがオンになり、
ドライバーのhetimerが初期化され、hrtimerのタイミング時間が60usに設定されていますが、実際のオシロスコープのテストでは、タイミング時間が1ms(1000 / HZ)である
ことがわかります。Linuxのコマンドで表示できます。スケジューラーの精度。
cat /proc/timer_list | grep .resolution
ドライバに印刷を追加してみることができます(高精度タイマーが使用できなくなる原因を確認してください)。
int tick_check_oneshot_change(int allow_nohz)
{
struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched);
if (!test_and_clear_bit(0, &ts->check_clocks))
return 0;
printk("%s(%d) ts->check_clocks=%d\n", __func__, __LINE__, ts->check_clocks);
if (ts->nohz_mode != NOHZ_MODE_INACTIVE)
return 0;
printk("%s(%d) ts->nohz_mode=%d\n", __func__, __LINE__, ts->nohz_mode);
if (!timekeeping_valid_for_hres() || !tick_is_oneshot_available())
return 0;
printk("%s(%d)allow_nohz=%d\n", __func__, __LINE__, allow_nohz);
if (!allow_nohz)
return 1;
printk("%s(%d)\n", __func__, __LINE__);
tick_nohz_switch_to_nohz();
return 0;
}