目次: C言語とlibc
会社でpopenで作った子プロセスが残ってしまうが何とかならないか?という相談を受けて、面白そうだったので取り組んでみました。日記でも残しておきます。コード的には特に機密情報はありません。
マニュアルを読みましょう(Manpage of popen)。少しだけ解説するなら、子プロセスの入出力をパイプ経由で送ったり受け取ったりできるライブラリ関数です。
例えばyesコマンドをpopenで実行すると、出力パイプからはy y y y ... という文字列が読みだせます。
非常に便利なpopenですが、子プロセスが終了するかどうかは子プロセス次第、言い換えれば子プロセスを強制的に終了させる方法がないことが欠点です。
例えば、先ほど挙げたyesコマンドは勝手に終了しないコマンドの代表例です。popen関数を呼んだ親プロセスが終了しても、子プロセスのyesコマンドは終了しないまま残ります。
実はpopen関数は既存のライブラリ関数やシステムコールの組み合わせで実現できます。先にコードを載せましょうか。
/* SPDX-License-Identifier: Apache-2.0 */
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
void usage(int argc, char *argv[])
{
	printf("usage:\n"
		"  %s cmdline\n", argv[0]);
}
int main(int argc, char *argv[])
{
	const char *cmdline;
	pid_t pid, pgrp;
	int pipefd[2];
	FILE *fp;
	int r;
	if (argc <= 1) {
		usage(argc, argv);
		return -1;
	}
	
	cmdline = argv[1];
	
	printf("cmdline: %s\n", cmdline);
	// パイプを作成します。パイプに読み書きするファイルディスクリプタ(pipefd)2つが返されます。
	// pipefd[0] が読み出し用、pipefd[1] が書き込み用です。
	r = pipe2(pipefd, 0);
	if (r == -1) {
		perror("pipe2");
		return -1;
	}
	// ファイルディスクリプタをFILE * でラップします。
	// popenはFILE * を返すインタフェースなので、それに合わせるためです。
	fp = fdopen(pipefd[0], "r");
	if (fp == NULL) {
		perror("fdopen");
		return -1;
	}
	// 子プロセスを生成します。
	//    pidには子プロセスの場合は0、親プロセスの場合は子プロセスのプロセスIDが返されます。
	pid = fork();
	if (pid == -1) {
		perror("fork");
		return -1;
	} else if (pid == 0) {
		// child
		// 子プロセスを新たなプロセスグループに移します。
		// 理由はあとでkillを呼ぶときに親プロセスまで巻き添えにしないようにするためです。
		// setpgidを呼ばない場合
		//    プロセスグループA: 親、子、指定したコマンド
		//    kill(プロセスグループA): 親も子もコマンドも全て強制終了してしまう
		// setpgidを呼んだ場合
		//    プロセスグループA: 親
		//    プロセスグループB: 子、指定したコマンド
		//    kill(プロセスグループB): 子とコマンドのみ強制終了
		r = setpgid(0, getpid());
		if (r == -1) {
			perror("setpgrp");
			return -1;
		}
		// 子プロセスの標準出力を閉じ、パイプの書き込み用ファイルディスクリプタを代わりに使います。
		// つまり子プロセスの出力がパイプに書き込まれます。
		r = dup2(pipefd[1], 1);
		if (r == -1) {
			perror("dup(child)");
			return -1;
		}
		// シェルを利用して引数に指定されたコマンドを実行します。
		// シェルを利用する理由はpopenと同じ仕様(コマンド引数を1つの文字列で渡す)にしたいからです。
		// シェルを挟まない場合は、複数の文字列に分割して渡す必要があります。
		r = execl("/bin/sh", "sh", "-c", cmdline, (char *)NULL);
		if (r == -1) {
			perror("execl(child)");
			return -1;
		}
		// not reach here
		return -1;
	}
	// parent
	// パイプから読みだすと子プロセスが標準出力に出そうとした文字列が読める
	char buf[10];
	memset(buf, 0, sizeof(buf));
	fread(buf, 1, sizeof(buf) - 1, fp);
	printf("read from pipe: %s\n", buf);
	printf("sleep 5\n");
	sleep(5);
	// 子プロセスのプロセスグループを取得します。
	//    pidには子プロセスのプロセスIDが返されます。
	//    forkの部分も参照してください。
	printf("getpgid() pid:%d\n", (int)pid);
	pgrp = getpgid(pid);
	if (pgrp == -1) {
		perror("getpgid");
		return -1;
	}
	// 子プロセスのプロセスグループを強制終了します。
	printf("kill(SIGTERM) pgrp:%d\n", (int)pgrp);
	r = kill(-pgrp, SIGTERM);
	if (r == -1) {
		perror("killpg");
		return -1;
	}
	// 子プロセスが終了するまで待ちます。
	printf("wait child pid:%d\n", (int)pid);
	int wstat;
	r = waitpid(-pid, &wstat, 0);
	if (r == -1) {
		perror("waitpid");
		return -1;
	}
	if (r != pid) {
		fprintf(stderr, "kill %d but terminated pid %d, why?\n", (int)pid, (int)r);
	}
	printf("done!!\n");
	return 0;
}
そこそこ長いですね。
コードを見ると目がチカチカする方向けにコメントだけ抜き出しました。動きが分かりやすいと思います。
子プロセスはこんな動きです。
親プロセスはこんな動きです。
プロセスの親子関係はこうなります。
|-a.out,536208 yes | `-sh,536209 -c yes | `-yes,536210
もしコマンドがさらに孫、ひ孫プロセスを生成しても、プロセスグループが一緒である限りkillが効くはずです。
今回紹介した実装はpopenの完全な上位互換ではありません。理由としては入力側を扱えないこと、popenのようなAPIとして使えないこと、が挙げられますが、拡張は容易だと思います。
 この記事にコメントする
 この記事にコメントする
今までお世話になった(なっている)ヘッドフォンたちをまとめておきます。
密閉タイプ。
オープンタイプ。
 この記事にコメントする
 この記事にコメントする
IT系の職業に就いている人であれば、恐らく一度は目にしたことがあるInterfaceという雑誌があります。今回12月号に記事を寄稿しました。記事の見本は 2022年12月号 - Interface - CQ出版から見ることができます。
会社と出版社にご縁があったそうで、春頃から不定期連載が始まりました。特に会社の製品の宣伝はせず、RISC-V CPU開発に関してハードウェアからソフトウェアまで広く易しく扱う内容となっています。連載は全10回の予定、執筆者は会社の技術者で分担しており1人1記事ずつ担当しています。たぶん。
連載はちょうど真ん中で5回目となります。記事の内容はRISC-Vのツールチェーンの入門編で4ページほどの短い記事です。記事の内容はここでは公開できない(しばらく時間が経ったらOKらしいです)ので、もし気になるようでしたら雑誌をご覧くださいませ。
 この記事にコメントする
 この記事にコメントする
医療保険の給付金が無事給付されました。給付金は、入院一時金 + (入院日数9日扱いx日ごとの給付) = 合計10万円くらいでした。
給付金の申請をしたのは久しぶりだったため、給付申請に添付する書類をミスって2回くらい返ってきました。住友生命にはお手数をお掛けをしました。ま、最終的に給付されたので良いでしょう……。
私と奥さんがCOVID-19に感染したのは8月です。8月はCOVID-19の自宅療養=入院扱い、となる時期としてはほぼ最後(10月から入院扱いではなくなった)でした。COVID-19に感染したのは不幸でしたが、医療保険の給付にギリギリ滑り込めたことは不幸中の幸いと言えるでしょう。
最近COVID-19に感染する人が急増し、生保各社が給付金支払いを渋り始めました。住友生命、日本生命は10月から医療保険の給付対象から自宅療養を外しています。他社も恐らく同様でしょう。
9月だろうが10月だろうがCOVID-19の自宅療養が必要なことに変わりはないのに、生命保険会社から給付金支払いが増えて嫌です、もう払いませんなんて梯子外しをされるとは思いませんでしたね……。
改めて生命・医療保険の契約内容を眺めましたが、加入している意味が薄れてきていますね。夫婦共働きで子供もいないので生命保険はもう不要でしょう。医療保険だけなら共済保険などの方が良いですし。じゃあ今すぐ解約だ!というほどの負担ではないですけどね……少なくとも5年後の契約更新をしないのは確実です。掛け金がさらに倍になるのでさすがに高すぎます。
 この記事にコメントする
 この記事にコメントする
おそらく10年前くらいに買ったシーリングライトの蛍光灯が切れてしまいました。交換用の蛍光灯を検索したところ既に廃番で、2世代ほど世代交代していました。蛍光灯は長持ちですねえ。
今日買ったユーザーが次買うのは3世代先の製品だと考えると、開発は難しいですね。安易に口金の形を変えたりすると「交換しようとしたが嵌らないよ(泣」というクレームの嵐になりそうですし。設計に失敗したら新型で置き換えるまで尋常じゃない時間掛かります。
要らないお世話ですけど、たった数千円なのにこんな低い交換頻度で儲かるんだろうか?とも思います。2回目交換する前にシーリングライト本体が壊れそうですよね。
 この記事にコメントする
 この記事にコメントする
| < | 2022 | > | ||||
| << | < | 10 | > | >> | ||
| 日 | 月 | 火 | 水 | 木 | 金 | 土 | 
| - | - | - | - | - | - | 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 | - | - | - | - | - | 
 25年10月27日
 25年10月27日
 23年4月10日
 23年4月10日
 25年10月15日
 25年10月15日
 25年10月18日
 25年10月18日
 22年5月5日
 22年5月5日
 25年10月19日
 25年10月19日
 23年4月11日
 23年4月11日
 06年4月22日
 06年4月22日
 25年10月17日
 25年10月17日
 25年10月6日
 25年10月6日
 25年10月13日
 25年10月13日
 20年10月23日
 20年10月23日
 25年10月12日
 25年10月12日
 20年8月29日
 20年8月29日
 19年1月13日
 19年1月13日
 18年10月13日
 18年10月13日
 18年9月3日
 18年9月3日
 18年8月20日
 18年8月20日
 18年7月23日
 18年7月23日
 18年7月22日
 18年7月22日
 wiki
 wiki Linux JM
 Linux JM Java API
 Java API 2002年
 2002年 2003年
 2003年 2004年
 2004年 2005年
 2005年 2006年
 2006年 2007年
 2007年 2008年
 2008年 2009年
 2009年 2010年
 2010年 2011年
 2011年 2012年
 2012年 2013年
 2013年 2014年
 2014年 2015年
 2015年 2016年
 2016年 2017年
 2017年 2018年
 2018年 2019年
 2019年 2020年
 2020年 2021年
 2021年 2022年
 2022年 2023年
 2023年 2024年
 2024年 2025年
 2025年 過去日記について
 過去日記について アクセス統計
 アクセス統計 サーバ一覧
 サーバ一覧 サイトの情報
 サイトの情報合計: 
本日: