MasaのITC Life

夢は起業家!全てにおいて凡人だけど頑張ることだけはいっちょ前!

プログラミング

Cで見るシグナルハンドラとkillコマンドについて(非同期シグナルセーフ関数も)

投稿日:



シグナルハンドラについて

簡単にまとめておこうと思います。



そもそもシグナルとは何なのか・・・

シグナルは、あるイベント発生時に

それをプロセスに通知します。



何気なく使用している

"Ctrl + c"や"Ctrl + z"なども 

すべてシグナルを発信しています。



killコマンドも実態は、

シグナルを発信させるコマンドです。



自身のプロセスに起因するシグナルを

同期シグナルと言い、

外部的要因によるシグナルを

非同期シグナルと言います。



シグナルの名前には全て、

"SIG"が付いています。



シグナルの概要が分かったところで、

具体的には何があるのでしょうか。



kill -l
でシグナルを簡単に

表示させることが出来ます。



またC言語は標準で、

signal.hに定義されているので、

それを覗いてみてもよいでしょう。



もちろんシグナル1つ1つに

何らかの意味があります。



killコマンドに

オプションで番号を指定することで、

該当するシグナルをプロセスに

対して送信することが出来ます。



ちなみにkillコマンドを

オプションなしで使用すると、

シグナル15番のSIGTERM

プロセスに送信されます。



よく強制終了させるために

使われるkill -9 は、

シグナル9番 SIGTSTP

ターミナルの強制停止です。



シグナルはイベントの通知です。

イベントが何か発生した際に、



そのイベント対して対処するコードを

プログラムに実装出来ます。

それがシグナルハンドラです。

シグナルがプロセスに届くと、

シグナルハンドラが割り込処理を行います。



しかしシグナルハンドラには、

処理できる内容に制限があります。

※後述します。



今回はシグナル2番 SIGINT

割り込みイベントが発生した際に、

プログラムを終了させます。



割り込みシグナルは、

Ctrl + C で、そのシグナルを

発生させることが出来ます。

もしくは、kill -2 コマンドです。



ソースはこちらです。



getpid()
でプロセスIDを

標準出力しています。

killコマンドでプロセスIDを

指定しやすいようにするためです。



それではこのコードを使って、

割り込みプログラムを終了させていきます。



killコマンドを送っても、

Ctrl + Cを押しても

プログラムは終了しました。



ちなみにシグナル2番SIGINTに対する

処理を実装していない場合は、

Ctrl + C を押しても、

プログラムは終了しません。


非同期シグナルセーフ関数とsig_atomic_t型変数



シグナルハンドラの中身を

書くときは注意が必要です。

それが非同期シグナルセーフ関数と

volatile修飾したsig_atomic_t型変数です。



非同期シグナルセーフ関数は、

ある種の関数グループです。



シグナルハンドラ割り込み処理で

安全に扱えるのは、

これに該当する関数のみです。



詳しい説明は長くなるので割愛しますが、

非同期シグナルセーフ関数でない

関数を用いると予期せぬエラーの

メモリ破壊などの原因になってしまいます。



非同期シグナルセーフ関数には、

以下に示す関数が該当します。


_Exit()

_exit()

abort()

accept()

access()

aio_error()

aio_return()

aio_suspend()

alarm()

bind()

cfgetispeed()

cfgetospeed()

cfsetispeed()

cfsetospeed()

chdir()

chmod()

chown()

clock_gettime()

close()

connect()

creat()

dup()

dup2()

execle()

execve()

fchmod()

fchown()

fcntl()

fdatasync()

fork()

fpathconf()

fstat()

fsync()

ftruncate()

getegid()

geteuid()

getgid()

getgroups()

getpeername()

getpgrp()

getpid()

getppid()

getsockname()

getsockopt()

getuid()

kill()

link()

listen()

lseek()

lstat()

mkdir()

mkfifo()

open()

pathconf()

pause()

pipe()

poll()

posix_trace_event()

pselect()

raise()

read()

readlink()

recv()

recvfrom()

recvmsg()

rename()

rmdir()

select()

sem_post()

send()

sendmsg()

sendto()

setgid()

setpgid()

setsid()

setsockopt()

setuid()

shutdown()

sigaction()

sigaddset()

sigdelset()

sigemptyset()

sigfillset()

sigismember()

sleep()

signal()

sigpause()

sigpending()

sigprocmask()

sigqueue()

sigset()

sigsuspend()

sockatmark()

socket()

socketpair()

stat()

symlink()

sysconf()

tcdrain()

tcflow()

tcflush()

tcgetattr()

tcgetpgrp()

tcsendbreak()

tcsetattr()

tcsetpgrp()

time()

timer_getoverrun()

timer_gettime()

timer_settime()

times()

umask()

uname()

unlink()

utime()

wait()

waitpid()

write()

 

 


ちなみに最も良く使うであろう

printf関数は入っておりません。

なのでシグナルハンドラ内での使用は

控えた方が良いです。




最後にsig_atomic_t型変数です。

普通の変数を使用するより

こちらの変数を使用した方が安全です。



このアトミック(atomic)というのは、

何かの処理をする際に、

他から割り込みをされないことを保証

ということを意味しています。



さらにこの変数に

volatile修飾子を付けましょう。



volatile修飾子というのは、

よく組み込み系で使われるそうです。



volatileは、

変数へのアクセス最適化を

あえて無効化
します。



プログラム上で変数を何回も

呼び出していても実際は

メモリから呼び出されていません。

レジスタに留めておいたものを使います。



つまり最適化してある場合は

変数を使いまわしています。



volatile修飾子はこの最適化を

意図的に無効するものです。






以上です。



上記の非同期シグナルセーフ関数と

sig_atomic型変数について

"なぜ"ということについて

あまり言及しませんでした。



こちらに詳しい説明が載っているので、

良ければ参考にしてみて下さい。



非同期シグナルセーフ関数について
https://www.jpcert.or.jp/sc-rules/c-sig30-c.html

sig_atomic型変数について
https://www.jpcert.or.jp/sc-rules/c-sig31-c.html



最後まで読んでいただきありがとうございました。


-プログラミング

Copyright© MasaのITC Life , 2023 All Rights Reserved Powered by STINGER.