Linuxでリアルタイム・プロセスを作るには…?

Linuxにはリアルタイム・プロセスという、他のプロセスよりも優先してCPUスケジューリングを割り当てるプロセスを設定することができます。
詳しくは、このへんとかMANPAGEを見て頂くとして…。

お手軽に任意のコマンドをリアルタイム・プロセスにするような方法ってないんでしたっけ?感じとしては nice コマンドのように、

% nice ./heavy_process

とかいう使い方がしたいのですが…。
ぐぐったりしたんですが見当たらなかったので、上記のMANPAGEを参考に自分で書いてしまいました。

後日追記:同等以上のことがchrtコマンドで変更可能でした。ど忘れ…ということで下記プログラムは実用上の意味ありません。。。

使い方は、下記プログラムを rtsched.c として保存し、

% gcc -Os rtsched.c -o rtsched

とかやってコンパイルして、rootになって

% sudo ./rtsched rr 1           # /bin/sh をポリシー:SCHED_RR、リアルタイム優先度:1 で起動
% sudo ./rtsched fifo 1         # /bin/sh をポリシー:SCHED_FIFO、リアルタイム優先度:1 で起動
% sudo ./rtsched fifo 5 ls hoge # lsコマンドをポリシー:SCHED_FIFO、リアルタイム優先度:5 で、hogeを引数にして起動
% sudo ./rtsched rr 5 /bin/zsh  # zshをポリシー:SCHED_RR、リアルタイム優先度:5 で起動

といった具合です(リアルタイムプロセスは通常、rootじゃないと作れません。*1)。

第1引数にポリシーとして、rrかfifoのどちらかを指定します。
第2引数は優先度で、1〜99です。リアルタイムプロセス同士で、高いものほど優先されるはずです。
第3引数以降は、起動するコマンドと、そのコマンドに渡す引数です。コマンドを省略すると、/bin/shを起動します。スケジューリング・ポリシーは子プロセスにも継承されますので、このシェルから起動したプロセスもリアルタイムプロセスになります。

なお、第3引数のコマンドは、絶対パス相対パスであればそのまま実行しようとしますが、lsなどコマンド名を指定すると、その在処をPATH環境変数から適当に解決します。ただし一つのパスがあまりに長かったりすると予期しない動作をするとかバグるかもしれないので利用時にはご注意を。

あと、無限ループするようなプロセスを起動してしまうとCPUを占有されてしまい、そのプロセスをkillすることすら不可能になります。。。まあマルチコアな環境なら何とかなりますけど(もちろんコア数以上に無限ループするプロセスを起動してしまうとハングしますが)。

以下、rtsched.cのソースコードです。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sched.h>
#include <limits.h>
#include <errno.h>

int main(int argc, char **argv)
{
	struct sched_param sp;
	int policy;
	int ret;
	char path[PATH_MAX];
	char *cmd, *p, *p2;
	int len;
	
	if (argc < 3) {
		fprintf(stderr, "usage: %s rr/fifo <prio> [<command> [<arg> ...]]\n", argv[0]);
		exit(1);
	}

	if (strcasecmp(argv[1], "rr") == 0)
		policy = SCHED_RR;
	else if (strcasecmp(argv[1], "fifo") == 0)
		policy = SCHED_FIFO;
	else
		policy = SCHED_OTHER;

	sp.sched_priority = atoi(argv[2]);
	ret = sched_setscheduler(0, policy, &sp);
	if (ret) {
		perror("sched_setscheduler");
		return ret;
	}
	
	if (argc == 3) {
		ret = execl("/bin/sh", NULL);
		perror("execl");
		return ret;
	}
	
	cmd = argv[3];
	if (index(cmd, '/')) {
		ret = execv(cmd, argv+3);
		perror("execv");
		return ret;
	}
	
	ret = execv(cmd, argv+3);

	p2 = getenv("PATH");
	while (p = p2) {
		p2 = index(p, ':');
		len = p2 ? p2 - p : strlen(p);
		if (len < PATH_MAX-2) { // ignore too long path
			memcpy(path, p, len);
			path[len] = '/';
			path[len+1] = 0;
			strncat(path, cmd, PATH_MAX-1);
			ret = execv(path, argv+3);
		}
		if (p2) p2++; // next directory in the PATH
	}
	
	fprintf(stderr, "%s: command not found\n", cmd);
	return errno;
	
}

Linuxカーネルを2.6.23にしてから、どうもsynergycの動作がカクカクとまるので、CFSのせいかと思ってリアルタイム・プロセスにしたら非常に快適になりましたとさ、というお話(ちょっとオーバーな解決方法だが…)。CFSについてはIBMの解説が詳しい。IBMすげ〜。

*1:カーネル2.6.12以降でulimitのRLIMIT_RTPRIOが1以上に指定されている場合は、それ以下の優先度であればroot以外でも作れます。詳しくはMANPAGEを。