C言語でLinuxにおけるリアルタイムスケジューリングRMとEDFの実装を教えて!
こういった悩みにお答えします.
本記事の信頼性
- リアルタイムシステムの研究歴12年.
- 東大教員の時に,英語でOS(Linuxカーネル)の授業.
- 2012年9月~2013年8月にアメリカのノースカロライナ大学チャペルヒル校(UNC)コンピュータサイエンス学部で客員研究員として勤務.C言語でリアルタイムLinuxの研究開発.
- プログラミング歴15年以上,習得している言語: C/C++,Python,Solidity/Vyper,Java,Ruby,Go,Rust,D,HTML/CSS/JS/PHP,MATLAB,Assembler (x64,ARM).
- 東大教員の時に,C++言語で開発した「LLVMコンパイラの拡張」,C言語で開発した独自のリアルタイムOS「Mcube Kernel」をGitHubにオープンソースとして公開.
- 2020年1月~現在はアメリカのノースカロライナ州チャペルヒルにあるGuarantee Happiness LLCのCTOとしてECサイト開発やWeb/SNSマーケティングの業務.2022年6月~現在はアメリカのノースカロライナ州チャペルヒルにあるJapanese Tar Heel, Inc.のCEO兼CTO.
- 最近は自然言語処理AIとイーサリアムに関する有益な情報発信に従事.
- (AI全般を含む)自然言語処理AIの論文の日本語訳や,AIチャットボット(ChatGPT,Auto-GPT,Gemini(旧Bard)など)の記事を50本以上執筆.アメリカのサンフランシスコ(広義のシリコンバレー)の会社でプロンプトエンジニア・マネージャー・Quality Assurance(QA)の業務委託の経験あり.
- (スマートコントラクトのプログラミングを含む)イーサリアムや仮想通貨全般の記事を200本以上執筆.イギリスのロンドンの会社で仮想通貨の英語の記事を日本語に翻訳する業務委託の経験あり.
こういった私から学べます.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!
目次
C言語でLinuxにおけるリアルタイムスケジューリングRMとEDFの実装
C言語でLinuxにおけるリアルタイムスケジューリングRMとEDFの実装を紹介します.
結構難しい内容なので,じっくり読み進めて下さい.
RMとEDFの理論や比較を知りたいあなたはこちらからどうぞ.
本記事では,以下の記事の内容を理解していることを前提とします.
sched_attr構造体とsched_setattr/sched_getattrシステムコール
1 2 3 4 5 |
int sched_setattr(pid_t pid, struct sched_attr *attr, unsigned int flags); int sched_getattr(pid_t pid, struct sched_attr *attr, unsigned int size, unsigned int flags); |
sched_setattr/sched_getattrシステムコールは,スケジューリングポリシーと属性の設定と取得を行います.
これらのシステムコールは非標準のLinuxによる拡張であることに注意して下さい.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
struct sched_attr { __u32 size; __u32 sched_policy; __u64 sched_flags; /* SCHED_NORMAL, SCHED_BATCH */ __s32 sched_nice; /* SCHED_FIFO, SCHED_RR */ __u32 sched_priority; /* SCHED_DEADLINE */ __u64 sched_runtime; __u64 sched_deadline; __u64 sched_period; /* Utilization hints */ __u32 sched_util_min; __u32 sched_util_max; }; |
sched_attr構造体は,指定したスレッドの新しいスケジューリングポリシーと属性を定義した構造体で,sched_setattr/sched_getattrシステムコールの第2引数で利用します.
- size:この構造体のサイズ
- sched_policy:ポリシー(SCHED_*)
- sched_flags:フラグ
- sched_nice:nice値(SCHED_OTHER,SCHED_BATCH)
- sched_priority:静的優先度(SCHED_FIFO,SCHED_RR)
- sched_runtime:SCHED_DEADLINEの実行時間
- sched_deadline:SCHED_DEADLINEの相対デッドライン
- sched_period:SCHED_DEADLINEの周期
- sched_util_min:SCHED_DEADLINEのCPU利用率の最小値
- sched_util_max:SCHED_DEADLINEのCPU利用率の最大値
標準ライブラリのヘッダファイルsched.hにsched_setattr/sched_getattrシステムコールのプロトタイプ宣言とsched_attr構造体の定義があるという記載ですが,Ubuntu 22.04 LTSのヘッダファイルにはありません.
※sched_setattr/sched_getattrシステムコールは非標準のLinuxによる拡張であるからだと考えられます.
また,linux/sched/types.hヘッダファイルにsched_setattr/sched_getattrシステムコールのプロトタイプ宣言とsched_attr構造体の定義はありますが,linux/sched/types.hヘッダファイルをインクルードすると標準ライブラリのヘッダファイルsched.hとの整合性が取れていないため,ビルドエラーが発生します.
なので,自作コードでsched_attr構造体の定義とsched_setattr/sched_getattrシステムコールのプロトタイプ宣言が必要になりますので注意しましょう!
LinuxにおけるリアルタイムスレッドはCPUを100%利用できない!?
Linuxにおけるリアルタイムスレッドは,デフォルトではCPUを100%利用できない設定になっています.
catコマンドで実行すると以下のようになります.
- sched_rt_period_usは1,000,000マイクロ秒(1秒)
- sched_rt_runtime_usは950,000マイクロ秒(0.95秒)
1 2 3 4 |
$ cat /proc/sys/kernel/sched_rt_period_us 1000000 $ cat /proc/sys/kernel/sched_rt_runtime_us 950000 |
つまり,リアルタイムスレッドは1秒あたり0.95秒までしか実行できません(1秒あたりのCPUの実行割合は95%).
この理由として,リアルタイムスレッドが暴走した場合の安全装置としての役割があるからです.(コアが1つしかない場合,そのコアでリアルタイムスレッドが暴走したらOSが制御不能になり,強制的に再起動しなければなりません.)
参考までに,上記の値はLinuxカーネルのkernel/core/sched.cファイルのsysctl_sched_rt_periodとsysctl_sched_rt_runtime変数に設定されています.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/* * period over which we measure -rt task CPU usage in us. * default: 1s */ unsigned int sysctl_sched_rt_period = 1000000; ... /* * part of the period that we allow rt tasks to run in us. * default: 0.95s */ int sysctl_sched_rt_runtime = 950000; |
もしリアルタイムスレッドでCPUを100%利用したい場合は以下のコマンドを実行しましょう.
ここで,「sched_rt_runtime_us=-1」は無制限の意味です.
1 |
$ sudo sysctl -w kernel.sched_rt_runtime_us=-1 |
sched_rt_runtime_usを表示してみると,-1になっていることがわかります.
※この設定をした場合,実行するリアルタイムスレッドは注意して下さい.
1 2 |
$ cat /proc/sys/kernel/sched_rt_runtime_us -1 |
LinuxにおけるRMとEDFの実装
LinuxにおけるRMとEDFの実装を紹介します.
RMはSCHED_FIFOスケジューリングポリシー,EDFはSCHED_DEADLINEスケジューリングポリシーを利用して実装します.
ここで,SCHED_FIFO/SCHED_DEADLINEスケジューリングポリシーは,管理者権限でないと利用できないことに注意して下さい(sudoコマンドで実行).
RMとEDFでスケジューリングするタスクセットは以下になります.
- タスク数:2(RMを考慮してタスクIDが小さいほど周期が短いことを想定)
- タスク1:実行時間0.6秒,周期1秒
- タスク2:実行時間0.5秒,周期1.5秒
- 実行時間は安全マージンを考慮して80%を実行(0.6秒の場合は0.6 * 80% = 0.48秒)
- タスクセットの実行時間は3秒
- RMの場合はタスクの実行するCPUはCPU 0で固定(migrate_thread_to_cpu関数でCPU 0にマイグレーション)
- EDFはSCHED_DEADLINEの特性上,affinityマスクを設定できません(理由はこちら).特定のコアでSCHED_DEADLINEを実行したい場合は,以下の方法でLinuxが利用するコア数を1にしましょう!
- VMでLinuxをゲストOSとして利用している場合,VMでLinuxが利用するコアを1コアに設定
- ホストOSとしてLinuxを利用している場合,BIOSでLinuxが利用するコアを1コアに設定
- CPUのホットプラグで動的にプロセッサを無効して1コアに設定
CPUのホットプラグで動的にプロセッサを無効に設定する方法を知りたいあなたはこちらからどうぞ.
実装方法は以下の2種類を紹介します.
- 1プロセス,マルチスレッドの実装
- マルチプロセスの実装(コマンドラインでタスク数分のプログラムを実行)
コード一式は,こちらからrm_and_edf.zipをダウンロードして,解凍して下さい.
1 |
$ unzip rm_and_edf.zip |
解凍したrm_and_edf.zipファイルの構成は以下になります.
- Makefile:以下のRMとEDFの2種類の実装(合計4種類)をビルドするMakefile
- fifo.c:RMの1プロセス,マルチスレッドの実装
- fifo2.c:RMのマルチプロセスの実装(コマンドラインでタスク数分のプログラムを実行)
- deadline.c:EDFの1プロセス,マルチスレッドの実装
- deadline2.c:EDFのマルチプロセスの実装(コマンドラインでタスク数分のプログラムを実行)
以下のようにrm_and_edfディレクトリに移動してmakeでビルドすると,上記の4つのプログラムの実行ファイル(fifo,fifo2,deadline,deadline2)が作成されます.
1 2 3 4 5 6 |
$ cd rm_and_edf $ make gcc -o fifo fifo.c -Wall -lpthread -lm gcc -o deadline deadline.c -Wall -lpthread -lm gcc -o fifo2 fifo2.c -Wall -lpthread -lm gcc -o deadline2 deadline2.c -Wall -lpthread -lm |
1プロセス,マルチスレッドのRMの実装
1プロセス,マルチスレッドのRMの実装は以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <linux/types.h> #include <sys/syscall.h> #include <sys/sysinfo.h> #include <sys/mman.h> #include <pthread.h> #include <math.h> struct sched_attr { __u32 size; __u32 sched_policy; __u64 sched_flags; /* SCHED_NORMAL, SCHED_BATCH */ __s32 sched_nice; /* SCHED_FIFO, SCHED_RR */ __u32 sched_priority; /* SCHED_DEADLINE (nsec) */ __u64 sched_runtime; __u64 sched_deadline; __u64 sched_period; /* Utilization hints */ __u32 sched_util_min; __u32 sched_util_max; }; int sched_setattr(pid_t pid, const struct sched_attr *attr, unsigned int flags) { return syscall(__NR_sched_setattr, pid, attr, flags); } int sched_getattr(pid_t pid, struct sched_attr *attr, unsigned int size, unsigned int flags) { return syscall(__NR_sched_getattr, pid, attr, size, flags); } struct task { pid_t id; unsigned long wcet; unsigned long period; }; #define TASK_NUM 2 #define ARGS_NUM 2 #define DURATION_SEC 3 #define MSEC2NSEC(msec) ((msec) * 1000 * 1000L) #define SEC2NSEC(sec) ((sec) * 1000 * 1000 * 1000L) #define SAFETY_RATIO 0.8 int migrate_thread_to_cpu(pid_t tid, int target_cpu) { cpu_set_t *cpu_set; size_t size; int nr_cpus; int ret; if ((nr_cpus = get_nprocs()) == -1) { perror("get_nprocs()"); exit(1); } if (target_cpu < 0 || target_cpu >= nr_cpus) { fprintf(stderr, "Error: target_cpu %d is in [0,%d).\n", target_cpu, nr_cpus); exit(2); } if ((cpu_set = CPU_ALLOC(nr_cpus)) == NULL) { perror("CPU_ALLOC"); exit(3); } size = CPU_ALLOC_SIZE(nr_cpus); CPU_ZERO_S(size, cpu_set); CPU_SET_S(target_cpu, size, cpu_set); if ((ret = sched_setaffinity(tid, size, cpu_set)) == -1) { perror("sched_setaffinity"); exit(4); } CPU_FREE(cpu_set); return ret; } void set_next_release_time(struct timespec *tp, struct task *task) { tp->tv_nsec += MSEC2NSEC(task->period); while (tp->tv_nsec >= SEC2NSEC(1)) { tp->tv_nsec -= SEC2NSEC(1); tp->tv_sec++; } } void timespec_diff(struct timespec *result, const struct timespec *end, const struct timespec *begin) { if (end->tv_nsec - begin->tv_nsec < 0) { result->tv_sec = end->tv_sec - begin->tv_sec - 1; result->tv_nsec = end->tv_nsec - begin->tv_nsec + 1000000000; } else { result->tv_sec = end->tv_sec - begin->tv_sec; result->tv_nsec = end->tv_nsec - begin->tv_nsec; } } void *do_rm(void *args) { struct timespec *tp = (struct timespec *)((void **) args)[0]; struct task *task = (struct task *)((void **) args)[1]; struct timespec now, next = {tp->tv_sec, tp->tv_nsec}; struct sched_attr attr, attr2; struct timespec th_now, th_next, diff; int ret; unsigned int flags = 0; int job = 1; attr.size = sizeof(attr); attr.sched_policy = SCHED_FIFO; attr.sched_flags = 0; attr.sched_nice = 0; attr.sched_priority = sched_get_priority_max(SCHED_FIFO) - task->id; attr.sched_runtime = attr.sched_period = attr.sched_deadline = 0; // not used attr.sched_util_min = 0; attr.sched_util_max = ceil((double)(task->wcet * 100) / task->period); if ((ret = sched_setattr(0, &attr, flags)) < 0) { perror("sched_setattr"); exit(1); } if ((ret = sched_getattr(0, &attr2, sizeof(struct sched_attr), flags)) < 0) { perror("sched_getattr"); exit(2); } if (attr2.sched_policy == SCHED_FIFO) { printf("sched_policy is SCHED_FIFO.\n"); } else { fprintf(stderr, "sched_policy is not SCHED_FIFO.\n"); exit(3); } printf("sleep until begin time: cpu = %d\n", sched_getcpu()); // sleep until begin time. if (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next, NULL) != 0) { perror("clock_nanosleep"); exit(4); } do { printf("SCHED_FIFO thread is released [id = %d]: job = %d\n", task->id, job++); if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &th_now) != 0) { perror("clock_gettime"); exit(5); } do { if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &th_next) != 0) { perror("clock_gettime"); exit(6); } timespec_diff(&diff, &th_next, &th_now); } while (SEC2NSEC(diff.tv_sec) + diff.tv_nsec <= MSEC2NSEC(task->wcet) * SAFETY_RATIO); set_next_release_time(&next, task); if (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next, NULL) != 0) { perror("clock_nanosleep"); exit(7); } if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) { perror("clock_getitme"); exit(8); } } while (now.tv_sec <= tp->tv_sec + DURATION_SEC); return NULL; } int main(void) { pthread_t threads[TASK_NUM]; struct task tasks[TASK_NUM] = {{1, 600, 1000}, {2, 500, 1500}}; struct timespec tp; void *args[TASK_NUM][ARGS_NUM]; int i; pid_t tid = gettid(); // migrate thread to CPU 0. migrate_thread_to_cpu(tid, 0); if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) { perror("mlockall"); exit(1); } if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0) { perror("clock_getitme"); exit(2); } // begin time is current time + 1.X [s] (at least 1 second waiting from current time). tp.tv_sec += 2; tp.tv_nsec = 0; for (i = 0; i < TASK_NUM; i++) { args[i][0] = &tp; args[i][1] = &tasks[i]; pthread_create(&threads[i], NULL, do_rm, args[i]); } for (i = 0; i < TASK_NUM; i++) { pthread_join(threads[i], NULL); } if (munlockall() == -1) { perror("mlockall"); exit(3); } return 0; } |
実行結果は以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 |
$ sudo ./fifo sched_policy is SCHED_FIFO. sleep until begin time: cpu = 0 sched_policy is SCHED_FIFO. sleep until begin time: cpu = 0 SCHED_FIFO thread is released [id = 1]: job = 1 SCHED_FIFO thread is released [id = 2]: job = 1 SCHED_FIFO thread is released [id = 1]: job = 2 SCHED_FIFO thread is released [id = 2]: job = 2 SCHED_FIFO thread is released [id = 1]: job = 3 SCHED_FIFO thread is released [id = 1]: job = 4 SCHED_FIFO thread is released [id = 2]: job = 3 |
マルチプロセスのRMの実装(コマンドラインでタスク数分のプログラムを実行)
マルチプロセスのRMの実装(コマンドラインでタスク数分のプログラムを実行)は以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <linux/types.h> #include <sys/syscall.h> #include <sys/sysinfo.h> #include <sys/mman.h> #include <pthread.h> #include <math.h> struct sched_attr { __u32 size; __u32 sched_policy; __u64 sched_flags; /* SCHED_NORMAL, SCHED_BATCH */ __s32 sched_nice; /* SCHED_FIFO, SCHED_RR */ __u32 sched_priority; /* SCHED_DEADLINE (nsec) */ __u64 sched_runtime; __u64 sched_deadline; __u64 sched_period; /* Utilization hints */ __u32 sched_util_min; __u32 sched_util_max; }; int sched_setattr(pid_t pid, const struct sched_attr *attr, unsigned int flags) { return syscall(__NR_sched_setattr, pid, attr, flags); } int sched_getattr(pid_t pid, struct sched_attr *attr, unsigned int size, unsigned int flags) { return syscall(__NR_sched_getattr, pid, attr, size, flags); } struct task { pid_t id; unsigned long wcet; unsigned long period; }; #define TASK_NUM 2 #define ARGS_NUM 2 #define DURATION_SEC 3 #define MSEC2NSEC(msec) ((msec) * 1000 * 1000L) #define SEC2NSEC(sec) ((sec) * 1000 * 1000 * 1000L) #define SAFETY_RATIO 0.8 int migrate_thread_to_cpu(pid_t tid, int target_cpu) { cpu_set_t *cpu_set; size_t size; int nr_cpus; int ret; if ((nr_cpus = get_nprocs()) == -1) { perror("get_nprocs()"); exit(1); } if (target_cpu < 0 || target_cpu >= nr_cpus) { fprintf(stderr, "Error: target_cpu %d is in [0,%d).\n", target_cpu, nr_cpus); exit(2); } if ((cpu_set = CPU_ALLOC(nr_cpus)) == NULL) { perror("CPU_ALLOC"); exit(3); } size = CPU_ALLOC_SIZE(nr_cpus); CPU_ZERO_S(size, cpu_set); CPU_SET_S(target_cpu, size, cpu_set); if ((ret = sched_setaffinity(tid, size, cpu_set)) == -1) { perror("sched_setaffinity"); exit(4); } CPU_FREE(cpu_set); return ret; } void set_next_release_time(struct timespec *tp, struct task *task) { tp->tv_nsec += MSEC2NSEC(task->period); while (tp->tv_nsec >= SEC2NSEC(1)) { tp->tv_nsec -= SEC2NSEC(1); tp->tv_sec++; } } void timespec_diff(struct timespec *result, const struct timespec *end, const struct timespec *begin) { if (end->tv_nsec - begin->tv_nsec < 0) { result->tv_sec = end->tv_sec - begin->tv_sec - 1; result->tv_nsec = end->tv_nsec - begin->tv_nsec + 1000000000; } else { result->tv_sec = end->tv_sec - begin->tv_sec; result->tv_nsec = end->tv_nsec - begin->tv_nsec; } } void *do_rm(void *args) { struct timespec *tp = (struct timespec *)((void **) args)[0]; struct task *task = (struct task *)((void **) args)[1]; struct timespec now, next = {tp->tv_sec, tp->tv_nsec}; struct sched_attr attr, attr2; struct timespec th_now, th_next, diff; int ret; unsigned int flags = 0; int job = 1; attr.size = sizeof(attr); attr.sched_policy = SCHED_FIFO; attr.sched_flags = 0; attr.sched_nice = 0; attr.sched_priority = sched_get_priority_max(SCHED_FIFO) - task->id; attr.sched_runtime = attr.sched_period = attr.sched_deadline = 0; // not used attr.sched_util_min = 0; attr.sched_util_max = ceil((double)(task->wcet * 100) / task->period); if ((ret = sched_setattr(0, &attr, flags)) < 0) { perror("sched_setattr"); exit(1); } if ((ret = sched_getattr(0, &attr2, sizeof(struct sched_attr), flags)) < 0) { perror("sched_getattr"); exit(2); } if (attr2.sched_policy == SCHED_FIFO) { printf("sched_policy is SCHED_FIFO.\n"); } else { fprintf(stderr, "sched_policy is not SCHED_FIFO.\n"); exit(3); } printf("sleep until begin time: cpu = %d\n", sched_getcpu()); // sleep until begin time. if (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next, NULL) != 0) { perror("clock_nanosleep"); exit(4); } do { printf("SCHED_FIFO thread is released [id = %d]: job = %d\n", task->id, job++); if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &th_now) != 0) { perror("clock_gettime"); exit(5); } do { if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &th_next) != 0) { perror("clock_gettime"); exit(6); } timespec_diff(&diff, &th_next, &th_now); } while (SEC2NSEC(diff.tv_sec) + diff.tv_nsec <= MSEC2NSEC(task->wcet) * SAFETY_RATIO); set_next_release_time(&next, task); if (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next, NULL) != 0) { perror("clock_nanosleep"); exit(7); } if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) { perror("clock_getitme"); exit(8); } } while (now.tv_sec <= tp->tv_sec + DURATION_SEC); return NULL; } int main(int argc, char *argv[]) { pthread_t thread; struct task task; struct timespec tp; void *args[ARGS_NUM]; pid_t tid = gettid(); if (argc != 4) { fprintf(stderr, "Usage: %s id wcet period\n", argv[0]); exit(1); } task.id = atoi(argv[1]); task.wcet = atoi(argv[2]); task.period = atoi(argv[3]); if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) { perror("mlockall"); exit(2); } // migrate thread to CPU 0. migrate_thread_to_cpu(tid, 0); if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0) { perror("clock_getitme"); exit(3); } // begin time is current time + 1.X [s] (at least 1 second waiting from current time). tp.tv_sec += 2; tp.tv_nsec = 0; args[0] = &tp; args[1] = &task; pthread_create(&thread, NULL, do_rm, args); pthread_join(thread, NULL); if (munlockall() == -1) { perror("mlockall"); exit(4); } return 0; } |
実行結果の例は以下になります.(プロセスIDは実行時により変動)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ sudo ./fifo2 1 600 1000 &; sudo ./fifo2 2 500 1500 [1] 7574 sched_policy is SCHED_FIFO. sleep until begin time: cpu = 0 sched_policy is SCHED_FIFO. sleep until begin time: cpu = 0 SCHED_FIFO thread is released [id = 1]: job = 1 SCHED_FIFO thread is released [id = 2]: job = 1 SCHED_FIFO thread is released [id = 1]: job = 2 SCHED_FIFO thread is released [id = 2]: job = 2 SCHED_FIFO thread is released [id = 1]: job = 3 SCHED_FIFO thread is released [id = 1]: job = 4 SCHED_FIFO thread is released [id = 2]: job = 3 [1] + done sudo -E ./fifo2 1 600 1000 |
1プロセス,マルチスレッドのEDFの実装
1プロセス,マルチスレッドのEDFの実装は以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <linux/types.h> #include <sys/syscall.h> #include <sys/sysinfo.h> #include <sys/mman.h> #include <pthread.h> #include <sched.h> #include <math.h> //#include <linux/sched/types.h> struct sched_attr { __u32 size; __u32 sched_policy; __u64 sched_flags; /* SCHED_NORMAL, SCHED_BATCH */ __s32 sched_nice; /* SCHED_FIFO, SCHED_RR */ __u32 sched_priority; /* SCHED_DEADLINE (nsec) */ __u64 sched_runtime; __u64 sched_deadline; __u64 sched_period; /* Utilization hints */ __u32 sched_util_min; __u32 sched_util_max; }; int sched_setattr(pid_t pid, const struct sched_attr *attr, unsigned int flags) { return syscall(__NR_sched_setattr, pid, attr, flags); } int sched_getattr(pid_t pid, struct sched_attr *attr, unsigned int size, unsigned int flags) { return syscall(__NR_sched_getattr, pid, attr, size, flags); } struct task { pid_t id; unsigned long wcet; unsigned long period; }; #define TASK_NUM 2 #define ARGS_NUM 2 #define DURATION_SEC 3 #define MSEC2NSEC(msec) ((msec) * 1000 * 1000L) #define SEC2NSEC(sec) ((sec) * 1000 * 1000 * 1000L) #define SAFETY_RATIO 0.8 void timespec_diff(struct timespec *result, const struct timespec *end, const struct timespec *begin) { if (end->tv_nsec - begin->tv_nsec < 0) { result->tv_sec = end->tv_sec - begin->tv_sec - 1; result->tv_nsec = end->tv_nsec - begin->tv_nsec + 1000000000; } else { result->tv_sec = end->tv_sec - begin->tv_sec; result->tv_nsec = end->tv_nsec - begin->tv_nsec; } } void *do_edf(void *args) { struct timespec *tp = (struct timespec *)((void **) args)[0]; struct task *task = (struct task *)((void **) args)[1]; struct timespec now, next = {tp->tv_sec, tp->tv_nsec}; struct sched_attr attr, attr2; struct timespec th_now, th_next, diff; int ret; unsigned int flags = 0; int job = 1; attr.size = sizeof(attr); attr.sched_policy = SCHED_DEADLINE; attr.sched_flags = 0; attr.sched_nice = 0; attr.sched_priority = 0; // not used attr.sched_runtime = MSEC2NSEC(task->wcet); attr.sched_period = attr.sched_deadline = MSEC2NSEC(task->period); attr.sched_util_min = 0; attr.sched_util_max = ceil((double)(task->wcet * 100) / task->period); if ((ret = sched_setattr(0, &attr, flags)) < 0) { perror("sched_setattr"); exit(1); } if ((ret = sched_getattr(0, &attr2, sizeof(struct sched_attr), flags)) < 0) { perror("sched_getattr"); exit(2); } if (attr2.sched_policy == SCHED_DEADLINE) { printf("sched_policy is SCHED_DEADLINE.\n"); } else { fprintf(stderr, "sched_policy is not SCHED_DEADLINE.\n"); exit(3); } printf("sleep until begin time: cpu = %d\n", sched_getcpu()); // sleep until begin time. clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next, NULL); do { printf("SCHED_DEADLINE thread is released [id = %d]: job = %d\n", task->id, job++); clock_gettime(CLOCK_THREAD_CPUTIME_ID, &th_now); do { clock_gettime(CLOCK_THREAD_CPUTIME_ID, &th_next); timespec_diff(&diff, &th_next, &th_now); } while (SEC2NSEC(diff.tv_sec) + diff.tv_nsec <= MSEC2NSEC(task->wcet) * SAFETY_RATIO); sched_yield(); if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) { perror("clock_getitme"); exit(4); } } while (now.tv_sec <= tp->tv_sec + DURATION_SEC); return NULL; } int main(void) { pthread_t threads[TASK_NUM]; struct task tasks[TASK_NUM] = {{1, 600, 1000}, {2, 500, 1500}}; struct timespec tp; void *args[TASK_NUM][ARGS_NUM]; int i; if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) { perror("mlockall"); exit(1); } if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0) { perror("clock_getitme"); exit(2); } // begin time is current time + 1.X [s] (at least 1 second waiting from current time). tp.tv_sec += 2; tp.tv_nsec = 0; for (i = 0; i < TASK_NUM; i++) { args[i][0] = &tp; args[i][1] = &tasks[i]; pthread_create(&threads[i], NULL, do_edf, args[i]); } for (i = 0; i < TASK_NUM; i++) { pthread_join(threads[i], NULL); } if (munlockall() == -1) { perror("mlockall"); exit(3); } return 0; } |
実行結果の例は以下になります.(実行するCPU IDは実行時により変動)
1 2 3 4 5 6 7 8 9 10 11 12 |
$ sudo ./deadline sched_policy is SCHED_DEADLINE. sleep until begin time: cpu = 3 sched_policy is SCHED_DEADLINE. sleep until begin time: cpu = 4 SCHED_DEADLINE thread is released [id = 2]: job = 1 SCHED_DEADLINE thread is released [id = 1]: job = 1 SCHED_DEADLINE thread is released [id = 1]: job = 2 SCHED_DEADLINE thread is released [id = 2]: job = 2 SCHED_DEADLINE thread is released [id = 1]: job = 3 SCHED_DEADLINE thread is released [id = 2]: job = 3 SCHED_DEADLINE thread is released [id = 1]: job = 4 |
マルチプロセスのEDFの実装(コマンドラインでタスク数分のプログラムを実行)
マルチプロセスのEDFの実装(コマンドラインでタスク数分のプログラムを実行)は以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <linux/types.h> #include <sys/syscall.h> #include <sys/sysinfo.h> #include <sys/mman.h> #include <pthread.h> #include <sched.h> #include <math.h> //#include <linux/sched/types.h> struct sched_attr { __u32 size; __u32 sched_policy; __u64 sched_flags; /* SCHED_NORMAL, SCHED_BATCH */ __s32 sched_nice; /* SCHED_FIFO, SCHED_RR */ __u32 sched_priority; /* SCHED_DEADLINE (nsec) */ __u64 sched_runtime; __u64 sched_deadline; __u64 sched_period; /* Utilization hints */ __u32 sched_util_min; __u32 sched_util_max; }; int sched_setattr(pid_t pid, const struct sched_attr *attr, unsigned int flags) { return syscall(__NR_sched_setattr, pid, attr, flags); } int sched_getattr(pid_t pid, struct sched_attr *attr, unsigned int size, unsigned int flags) { return syscall(__NR_sched_getattr, pid, attr, size, flags); } struct task { pid_t id; unsigned long wcet; unsigned long period; }; #define TASK_NUM 2 #define ARGS_NUM 2 #define DURATION_SEC 3 #define MSEC2NSEC(msec) ((msec) * 1000 * 1000L) #define SEC2NSEC(sec) ((sec) * 1000 * 1000 * 1000L) #define SAFETY_RATIO 0.8 void timespec_diff(struct timespec *result, const struct timespec *end, const struct timespec *begin) { if (end->tv_nsec - begin->tv_nsec < 0) { result->tv_sec = end->tv_sec - begin->tv_sec - 1; result->tv_nsec = end->tv_nsec - begin->tv_nsec + 1000000000; } else { result->tv_sec = end->tv_sec - begin->tv_sec; result->tv_nsec = end->tv_nsec - begin->tv_nsec; } } void *do_edf(void *args) { struct timespec *tp = (struct timespec *)((void **) args)[0]; struct task *task = (struct task *)((void **) args)[1]; struct timespec now, next = {tp->tv_sec, tp->tv_nsec}; struct sched_attr attr, attr2; struct timespec th_now, th_next, diff; int ret; unsigned int flags = 0; int job = 1; attr.size = sizeof(attr); attr.sched_policy = SCHED_DEADLINE; attr.sched_flags = 0; attr.sched_nice = 0; attr.sched_priority = 0; // not used attr.sched_runtime = MSEC2NSEC(task->wcet); attr.sched_period = attr.sched_deadline = MSEC2NSEC(task->period); attr.sched_util_min = 0; attr.sched_util_max = ceil((double)(task->wcet * 100) / task->period); if ((ret = sched_setattr(0, &attr, flags)) < 0) { perror("sched_setattr"); exit(1); } if ((ret = sched_getattr(0, &attr2, sizeof(struct sched_attr), flags)) < 0) { perror("sched_getattr"); exit(2); } if (attr2.sched_policy == SCHED_DEADLINE) { printf("sched_policy is SCHED_DEADLINE.\n"); } else { fprintf(stderr, "sched_policy is not SCHED_DEADLINE.\n"); exit(3); } printf("sleep until begin time: cpu = %d\n", sched_getcpu()); // sleep until begin time. clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next, NULL); do { printf("SCHED_DEADLINE thread is released [id = %d]: job = %d\n", task->id, job++); clock_gettime(CLOCK_THREAD_CPUTIME_ID, &th_now); do { clock_gettime(CLOCK_THREAD_CPUTIME_ID, &th_next); timespec_diff(&diff, &th_next, &th_now); } while (SEC2NSEC(diff.tv_sec) + diff.tv_nsec <= MSEC2NSEC(task->wcet) * SAFETY_RATIO); sched_yield(); if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) { perror("clock_getitme"); exit(4); } } while (now.tv_sec <= tp->tv_sec + DURATION_SEC); return NULL; } int main(int argc, char *argv[]) { pthread_t thread; struct task task; struct timespec tp; void *args[ARGS_NUM]; if (argc != 4) { fprintf(stderr, "Usage: %s id wcet period\n", argv[0]); exit(1); } task.id = atoi(argv[1]); task.wcet = atoi(argv[2]); task.period = atoi(argv[3]); if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) { perror("mlockall"); exit(2); } if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0) { perror("clock_getitme"); exit(3); } // begin time is current time + 1.X [s] (at least 1 second waiting from current time). tp.tv_sec += 2; tp.tv_nsec = 0; args[0] = &tp; args[1] = &task; pthread_create(&thread, NULL, do_edf, args); pthread_join(thread, NULL); if (munlockall() == -1) { perror("mlockall"); exit(4); } return 0; } |
実行結果の例は以下になります.(プロセスIDや実行するCPU IDは実行時により変動)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ sudo ./deadline2 1 600 1000 &; sudo ./deadline2 2 500 1500 [1] 7636 sched_policy is SCHED_DEADLINE. sleep until begin time: cpu = 2 sched_policy is SCHED_DEADLINE. sleep until begin time: cpu = 9 SCHED_DEADLINE thread is released [id = 1]: job = 1 SCHED_DEADLINE thread is released [id = 2]: job = 1 SCHED_DEADLINE thread is released [id = 1]: job = 2 SCHED_DEADLINE thread is released [id = 2]: job = 2 SCHED_DEADLINE thread is released [id = 1]: job = 3 SCHED_DEADLINE thread is released [id = 1]: job = 4 SCHED_DEADLINE thread is released [id = 2]: job = 3 [1] + done sudo -E ./deadline2 1 600 1000 |
まとめ
C言語でLinuxにおけるリアルタイムスケジューリングRMとEDFの実装を紹介しました.
具体的には,RMとEDFで,「1プロセス,マルチスレッド」と「マルチプロセス」の実装(合計4種類)を解説しました.
これらの4種類をゼロから実装するのは結構大変ですので,是非参考にして下さい.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!