pipで入れたパッケージを一括更新する

python のパッケージをコマンド一行で最新版にアップデートする方法です

コマンド

普段 sudo を使わずに,--user つけてpipを実行している場合

$ pip3 list --format freeze --outdated | while IFS='==' read pkg dummy; do  pip3 install --user --upgrade $pkg ; done

普段 sudo を使ってpipを実行している場合

$ pip3 list --format freeze --outdated | while IFS='==' read pkg dummy; do sudo  pip3 install --upgrade $pkg ; done

これだけです

注意

上記の例ではpip3を使っています.実際にコマンドを実行する際には,使っている環境に合わせて pip-3 とか pip とかに書き換えて使用してください

解説

pip コマンドと bash を組み合わせています

更新が必要なパッケージを pip3 コマンドで調べます

$  pip3 list --format freeze --outdated

例えば以下のような出力が得られます

Mako==1.0.7

これを bash の read コマンドで読み取ります

説明のために読み取った値を表示するだけのコードを書いておきます

$ pip3 list --format freeze --outdated | while   IFS='==' read pkg dummy; do  echo "pkg:[$pkg]   dummy:[$dummy]"; done

変数IFSを使って区切り文字として"=="を指定しています.この場合 read コマンドは,"Make==1.0.7"という入力行にたいして,最初のトークン"Mako"を変数pkgに,残りのトークン"1.0.7"を変数dummyに格納します

これで変数pkg にパッケージ名が入るので,あとは pip3 install --user --upgrade $pkg でパッケージを更新していきます

PyGObjectのインストールエラーを回避する方法

pipコマンドで PyGObjectをインストールしようとすると

$ pip3 install  PyGObject

以下のエラーが出る場合がある

Package gobject-introspection-1.0 was not found in the pkg-config search path.
Perhaps you should add the directory containing `gobject-introspection-1.0.pc'
to the PKG_CONFIG_PATH environment variable
No package 'gobject-introspection-1.0' found
Command '('pkg-config', '--print-errors', '--exists', 'gobject-introspection-1.0 >= 1.46.0')' returned non-zero exit status 1.
  
Try installing it with: 'sudo apt install libgirepository1.0-dev'
----------------------------------------
ERROR: Failed building wheel for PyGObject
Running setup.py clean for PyGObject
Failed to build PyGObject
ERROR: Could not build wheels for PyGObject which use PEP 517 and cannot be installed directly

エラーメッセージを読むと

Try installing it with: 'sudo apt install libgirepository1.0-dev'

と書いてあるように,aptで

$ sudo apt install libgirepository1.0-dev

libgirepository1.0-dev というパッケージをインストールしてから,再度

$ pip3 install  PyGObject

を実行すればこのエラーは回避できる

Windowsのドライブ(vfat)で rsync を正しく使う方法

vfat形式のドライブ(USBメモリなど)や,sambaなどでネットワーク経由でマウントしたストレージに対して rsync を使う場合は,いくつかオプションつけないと rsync の本来の性能が発揮されません.

rsyncの特徴,例えば差分コピーによる高速ファイルコピーなどを期待している場合は,次のようにオプションを付与すべきです.

rsync -a --no-o --no-p --no-g --safe-links --modify-window 1 --stats コピー元 コピー先

各オプションの意味は以下の通りです

--no-o
no owners の略です.vfatは owner つまり所有者の情報を保持できないので,このオプションで所有者情報を無視するようにします
--no-g
no groups の略です.vfatは group の情報も保持できないので,無視します.
--no-p
no permissions の略です.vfatはパーミッション情報も保持できないので,無視します.
--modify-window 1
rsyncはファイルの新旧を比較する際にファイルのタイムスタンプ情報を調べます.ところが vfat のタイムスタンプは2秒単位でしか変化しない(=タイムスタンプは2秒の解像度しか持ちません)ので,"--modify-window 1"をつけることで1秒未満のタイムスタンプのズレは無視するように設定します.
--safe-links
vfatはシンボリックリンクの機能を持っていません."--safe-links"をつけると,コピー元に含まれるシンボリックリンクはコピー対象外になります(無視されます)

TeXでページ番号を左上に表示する

TeXでページ番号を左上に表示する場合は fancyhdr パッケージを使います

fancyhdr の使い方

\usepackage{fancyhdr}

 \pagestyle{fancy}
 \rhead{\thepage}

フッターのページ番号を消すにはさらに

 \cfoot{ }

とします

fancyhdrの仕組み

fancyhdrパッケージを使うとヘッダーとフッターを自由に変更できます

\pagestyle{fancy}
\lhead{ヘッダーの左に表示する文字列}
\chead{ヘッダーの中央に表示する文字列}
\rhead{ヘッダーの右に表示する文字列}
\lfoot{フッターの左に表示する文字列}
\cfoot{フッターの中央に表示する文字列}
\rfoot{フッターの右に表示する文字列}

ページ左上に,現在のページ数と総ページ数を表示する

たとえば総ページ数を取得する lastpage*1 と組み合わせると次のような形で左上に,現在のページ番号/総ページ数,を表示できます

 \usepackage{lastpage}
 \pagestyle{fancy}
  \lhead{}
  \rhead{}
  \cfoot{\thepage{}/{}\pageref{LastPage}}

1ページ目のページ番号が消せない

\maketitle を使っている場合,つまり1ページ目にタイトルがあると,fancyhdr が上手く機能しません.この挙動は \maketitle が内部で \thispagestyle{} を使っているために生じます.

この場合 \maketitle 直後に \thispagestyle{fancy}を追加するとfancyhdr が動作するようになります

\maketitle
\thispagestyle{fancy}

[改訂第7版]LaTeX2ε美文書作成入門

[改訂第7版]LaTeX2ε美文書作成入門

LATEXはじめの一歩―Windows 10/8/7対応 (やさしいプログラミング)

LATEXはじめの一歩―Windows 10/8/7対応 (やさしいプログラミング)

Linuxカーネルのソースコードを読む方法(その2)

Linux カーネルソースコードを読んだのでメモを公開します.Linuxカーネルを読む際の参考になれば幸いです.

今回のお題: kernel.panic

今回はカーネル内部のエラー処理(カーネルパニック)の部分です.ソースコードのバージョンは linux-5.0.1 です.

カーネルパニック

ユーザ空間のプログラムは,NULLポインタアクセスなどで例外をおこすと,例外ハンドラが起動します.例外ハンドラは,プログラムをコアダンプさせたり,強制終了させたりします.

同じようにカーネル空間で例外が発生すると,例外ハンドラが起動します.復旧不可能な致命的なエラーの場合は,カーネルはデバック情報をコンソールに出力して,動作を停止させます.これがカーネルパニックです.

カーネルパニックを意図的に発生させる

デバッグなどの目的で意図的にカーネルパニックを発生させることもできます.sysreqの機能を使います.つまりroot権限で,コマンドライン

$ echo c > /proc/sysrq-trigger

を実行します

キーボードで sysreq + c を押しても同じことができます.つまり[Alt]+[Print Screen]+[c]を同時に押します

カーネルパニック後に自動再起動させる方法

Webサーバーなどで不運にもカーネルパニックが発生した場合は,とりあえずサーバーを自動再起動させるという運用方法が考えられます.
(運用ポリシーとして,障害時はサーバーを止めたままにするか,自動再起動させるか,どちらが良いかは意見が分かれるところです)

この設定はカーネルの起動オプションや,sysctl で設定できます

以下の内容で /etc/sysctl.d/99-panic.conf みたいなファイルを作成するだけです

kernel.panic = 90

この場合カーネルパニックから90秒後にシステムは自動再起動します

デバイスドライバ開発時は,kernel.panic = 10ぐらいにしておいて,再起動した場合は /var/log 以下のログを眺める,という使い方も出来ます

ソースコード

カーネルパニックの処理は,ソースコードとしては kernel/panic.c あたりで実装されています

kernel.panic などのオプション設定の定義は kernel/panic.c の687行目あたりにあります

687:
core_param(panic, panic_timeout, int, 0644);
core_param(panic_print, panic_print, ulong, 0644);
core_param(pause_on_oops, pause_on_oops, int, 0644);
core_param(panic_on_warn, panic_on_warn, int, 0644);

kernel.panic = 90 と設定した場合は panic_timeout というint型の変数に90という値が格納されます.

panic_timeout 変数の定義は46行目

int panic_timeout = CONFIG_PANIC_TIMEOUT;
EXPORT_SYMBOL_GPL(panic_timeout);

です.このように 初期値は CONFIG_PANIC_TIMEOUT つまり カーネルのconfigで設定しています

念の為 CONFIG_PANIC_TIMEOUT の値を確認します. 設定ファイルは .config なので grep するだけです

$ cd path/to/カーネルのソースコードのディレクトリ
$ grep CONFIG_PANIC_TIMEOUT .config
CONFIG_PANIC_TIMEOUT=60

私のカーネルは60秒でビルドされていました

パニック時の処理

panic(const char *fmt, ...) という関数で実装されています

161:
void panic(const char *fmt, ...) {
	static char buf[1024];
	va_list args;
	long i, i_next = 0, len;
	int state = 0;
	int old_cpu, this_cpu;
	bool _crash_kexec_post_notifiers = crash_kexec_post_notifiers;

	/*
	 * Disable local interrupts. This will prevent panic_smp_self_stop
	 * from deadlocking the first cpu that invokes the panic, since
	 * there is nothing to prevent an interrupt handler (that runs
	 * after setting panic_cpu) from invoking panic() again.
	 */
	local_irq_disable();

コメントに書いてある通りで,panic 時には割り込みを禁止状態にして,他の処理は完全は中断されるようになっています.

244:
	/*
	 * Run any panic handlers, including those that might need to
	 * add information to the kmsg dump output.
	 */
	atomic_notifier_call_chain(&panic_notifier_list, 0, buf);

	/* Call flush even twice. It tries harder with a single online CPU */
	printk_safe_flush_on_panic();
	kmsg_dump(KMSG_DUMP_PANIC);

その後 ksmg dumpのフック関数をコールして,最後に kmsg_dump をコールしてデバッグ情報をコンソールに出力しています.

287:
	if (panic_timeout > 0) {
		/*
		 * Delay timeout seconds before rebooting the machine.
		 * We can't use the "normal" timers since we just panicked.
		 */
		pr_emerg("Rebooting in %d seconds..\n", panic_timeout);

		for (i = 0; i < panic_timeout * 1000; i += PANIC_TIMER_STEP) {
			touch_nmi_watchdog();
			if (i >= i_next) {
				i += panic_blink(state ^= 1);
				i_next = i + 3600 / PANIC_BLINK_SPD;
			}
			mdelay(PANIC_TIMER_STEP);
		}
	}
	if (panic_timeout != 0) {
		/*
		 * This will not be a clean reboot, with everything
		 * shutting down.  But if there is a chance of
		 * rebooting the system it will be rebooted.
		 */
		emergency_restart();
	}

panic_timeout > 0 の場合は,PANIC_TIMER_STEP ミリ秒刻みでウエイトを入れながら panic_blinck()を繰り返しコールしています.おそらく画面の点滅表示処理です

#define PANIC_TIMER_STEP 100

PANIC_TIMER_STEPは100ミリ秒でした

そして panic_timeout秒経過したら emergency_restart() をコールして強制終了をおこないます

panic()はどこから呼ばれているか?

カーネルソースコードを "panic(" で検索すると直ぐに判りますが,至るところで panic() をコールするようになっています.

例えばカーネル起動時の処理 init/main.c では3箇所 panic() を呼び出す処理が書かれています

662:
	/*
	 * HACK ALERT! This is early. We're enabling the console before
	 * we've done PCI setups etc, and console_init() must be aware of
	 * this. But we do want output early, in case something goes wrong.
	 */
	console_init();
	if (panic_later)
		panic("Too many boot %s vars at `%s'", panic_later,
		      panic_param);

これはカーネルコマンドライン引数(起動時のオプション)を解析した後の処理です.コマンドライン引数が多すぎると"something goes wrong"ということでパニックを起こします

1079:
	/*
	 * We try each of these until one succeeds.
	 *
	 * The Bourne shell can be used instead of init if we are
	 * trying to recover a really broken machine.
	 */
	if (execute_command) {
		ret = run_init_process(execute_command);
		if (!ret)
			return 0;
		panic("Requested init %s failed (error %d).",
		      execute_command, ret);
	}
	if (!try_to_run_init_process("/sbin/init") ||
	    !try_to_run_init_process("/etc/init") ||
	    !try_to_run_init_process("/bin/init") ||
	    !try_to_run_init_process("/bin/sh"))
		return 0;

	panic("No working init found.  Try passing init= option to kernel. "
	      "See Linux Documentation/admin-guide/init.rst for guidance.");

これはカーネルの初期化が終了して,システムのinit プロセス(PIDが0のプロセス)を起動する部分です.

initプロセスの実行ファイル(/bin/systemd とか /bin/sh)が指定されていない場合,またはそれが存在しない場合は,どうしようも無いのでパニックを起こします


Linuxカーネルのソースコードを読む方法(その1)

Linux カーネルソースコードを読んだのでメモを公開します.Linuxカーネルを読む際の参考になれば幸いです.

今回のお題: dmesg コマンド

今回は dmesg コマンドに関係する部分を読みました.printkや /dev/kmsg 等が関係する部分です.

ソースコードのバージョンは linux-4.20.3 です.

/bin/dmesg の仕組み

/bin/dmesg は Linux カーネルが出力したログを見るためのコマンドです.

Linuxカーネルはログを出力するために printk() という関数を用意しています.printk()は printf() のカーネル版です.カーネルの中でprintk()で文字列を出力すると,その文字列はカーネル内部にあるリングバッファに記録されます.printk()は様々なところで使用されています.たとえばカーネルモジュール(デバイスドライバ)等は printk()を使って,デバイスの認識状況,デバッグ情報,動作ログを,逐次出力しています.

/bin/dmesg はこのバッファを読むための userland 側のツールです.userland(ユーザ側)からはカーネルのメモリ空間は直接アクセスできません.そこでカーネルは /dev/kmsg というデバイスファイルを用意して,ファイルとしてリングバッファをアクセスできるような仕組みを用意しています.

kernel/printk/printk.c を読む

printk()のソースコードは kernel/printk/printk.c にあります.リングバッファの実装,/dev/kmsg の実装も同じファイルにあります.

リングバッファは 1747行目で定義されています.

1747:
/*
 * Continuation lines are buffered, and not committed to the record buffer
 * until the line is complete, or a race forces it. The line fragments
 * though, are printed immediately to the consoles to ensure everything has
 * reached the console in case of a kernel crash.
 */
static struct cont {
        char buf[LOG_LINE_MAX];
        size_t len;                     /* length == 0 means unused buffer */
        struct task_struct *owner;      /* task of first print*/
        u64 ts_nsec;                    /* time of first print */
        u8 level;                       /* log level of first message */
        u8 facility;                    /* log facility of first message */
        enum log_flags flags;           /* prefix, newline flags */
} cont;

このリングバッファは,ユーザ空間側には /dev/kmsg としてマッピングされています.993行目に file_operations 構造体があります.

993:
const struct file_operations = {
        .open = devkmsg_open,
        .read = devkmsg_read,
        .write_iter = devkmsg_write,
        .llseek = devkmsg_llseek,
        .poll = devkmsg_poll,
        .release = devkmsg_release,
};

つまり /bin/dmesg は /dev/kmsg を開いて,ファイルとしてリングバッファの中身を読み出していることになります.

/bin/dmesg は先ず open(2) システムコールで /dev/kmsg をopenします.openシステムコールが発行されると,カーネルは file_operations の設定に従って devkmsg_open関数を起動します.

devkmsg_open()関数は 945行目にあります.

945:
static int devkmsg_open(struct inode *inode, struct file *file)
{
        struct devkmsg_user *user;
        int err;

        if (devkmsg_log & DEVKMSG_LOG_MASK_OFF)
                return -EPERM;

        /* write-only does not need any file context */
        if ((file->f_flags & O_ACCMODE) != O_WRONLY) {
                err = check_syslog_permissions(SYSLOG_ACTION_READ_ALL,
                                               SYSLOG_FROM_READER);
                if (err)
                        return err;
        }

/dev/kmsg のパーミッションの確認をしているのは check_syslog_permissions()であることが解ります.更に読み進めます.

651:
static int check_syslog_permissions(int type, int source)
{
        /*
         * If this is from /proc/kmsg and we've already opened it, then we've
         * already done the capabilities checks at open time.
         */
        if (source == SYSLOG_FROM_PROC && type != SYSLOG_ACTION_OPEN)
                goto ok;

        if (syslog_action_restricted(type)) {
                if (capable(CAP_SYSLOG))
                        goto ok;
                /*
                 * For historical reasons, accept CAP_SYS_ADMIN too, with
                 * a warning.
                 */
                if (capable(CAP_SYS_ADMIN)) {
                        pr_warn_once("%s (%d): Attempt to access syslog with "
                                     "CAP_SYS_ADMIN but no CAP_SYSLOG "
                                     "(deprecated).\n",
                                 current->comm, task_pid_nr(current));
                        goto ok;
                }
                return -EPERM;
        }
ok:
        return security_syslog(type);
}

syslog_action_restricted()をコールしています

636:
int dmesg_restrict = IS_ENABLED(CONFIG_SECURITY_DMESG_RESTRICT);
 
static int syslog_action_restricted(int type)
{
          if (dmesg_restrict)
                 return 1;

syslog_action_restricte は dmesg_restrict という変数をチェックしています.

dmesg_restrict変数はkernel/printk/printk.cの637行目で定義されています.

IS_ENABLED()はマクロです.このマクロは include/linux/kconfig.h で定義されています.

66:
/*
 * IS_ENABLED(CONFIG_FOO) evaluates to 1 if CONFIG_FOO is set to 'y' or 'm',
 * 0 otherwise.
 */
#define IS_ENABLED(option) __or(IS_BUILTIN(option), IS_MODULE(option))

IS_ENABLED()は,カーネルの設定ファイル .config 内で CONFIG_SECURITY_DMESG_RESTRICT が定義されていれば 1 に定義されていなければ 0 に展開されます.

IS_ENABLED()が決定するのは dmesg_restrict 変数の初期値です.値を変更するには /sbin/sysctl を使う方法などがあります.

参考:dmesg のアクセス制限を外す方法 - pyopyopyo - Linuxとかプログラミングの覚え書き -


また "For historical reasons, accept CAP_SYS_ADMIN too, with a warning."というコメントがあるように,CAP_SYS_ADMIN があればroot権限がなくても dmesg できそうでね.

まとめ

dmesgに関するカーネルソースを読みました.

  • /bin/dmesg は /dev/kmsg をopen(2)している
  • /dev/kmsgの実装はカーネルの中, kernel/printk/printk.c にある
  • dmesg_restrictという変数で dmesg のアクセス制限を On/Off できる.
  • CAP_SYS_ADMIN があればroot権限がなくても dmesg できそう(ただし非奨励)

ということが解りました.

ラズパイのような少メモリ環境で安全にrsyncを実行する方法

組み込みLinuxのような少メモリの環境では気をつけないと rsync が原因でシステムがクラッシュする場合があります.

クラッシュする理由はメモリ不足

rsync はファイルを転送する前に先ずファイルのリストを作ります.リストはメモリ上に一時保存されます.そのサイズは,ディレクトリやファイルが大量にあると,当然増加します.またリストを作る処理は再帰処理になっていて,これがまたメモリを消費します.

通常のPCやLinuxサーバーならメモリやswap領域が潤沢にあるので滅多に問題になりません.しかし組み込みLinuxだとメモリ容量が8MBとか128MBしかない,ということが有ります.ラズパイぐらいの環境だと1GBぐらいのメモリが使えますが,Webカメラなどの実メモリを沢山つかうデバイスを使っていたり,on-memoryのデータベースなどを動かしているとメモリが枯渇する場合があります.問題はメモリが枯渇するとシステムはクラッシュする点です.

なんかよく分からないけど時々 Linuxマシンが固まる.再起動すると直る.原因はcronで仕込んでいたファイルバックアップのrsyncプロセスだった,ということが少メモリ環境では起こりえます.めったに遭遇できない現象ですが,そういう事例もあるということは知っておいて損はありません.

rsync のメモリ使用量を減す方法

普段使っている rsync コマンドに "--no-inc-recursive" をつけるだけです.

$ rsync --no-inc-recursive  -av コピー元 コピー先

これで劇的にメモリ使用量が減ります.デメリットは処理速度が少し落ちる点です.