signalfd
flagはSFD_CLOEXEC | SFD_NONBLOCK以外は設定できません。anon_inode_getfd()でanon_inodefsからinodeそしてFILE取得し、file->f_flags=flagとします。
sigmaskにSIGKILL/SIGSTOPを追加し、コーディング上の実装故で、受信シグナル属性はビット単位で設定されており、signotset()で受信するシグナル属性を反転します。
引数ufd==-1なら、signalfd_fopsをコールバックとする新規FILEを取得し、そうでないならsignalfdからFILEを取得し、file->private_data=ctx、あるいはctx->sigmask = sigmaskで更新します。
O_NONBLOCKでは、シグナルが無いとエラーになりますが、複数シグナル受信の場合、最初のシグナルが受信できれば、以降nonblock = 1とし、残りのシグナルはウエイトする事で受信します。
SFD_CLOEXEC != O_CLOEXEC及びSFD_NONBLOCK != O_NONBLOCKなら、コンパイル時にエラーとなります。カーネルコンパイル時、_LINUX_SIGNALFD_Hを定義すれば、#define SFD_CLOEXEC O_CLOEXEC/#define SFD_NONBLOCK O_NONBLOCKとされ、_LINUX_SIGNALFD_Hをundefineとすることで、signalfdを独自に実装するとか、ないし実装しない実装を考慮してかと思いますが、この必要性の実効性の有無は理解不能です。
signotset()は、ビット単位で設定してあるシグナル情報を反転するだけですが、_SIG_SET_OPマクロを介しての以下の実装となっています。
シグナル数は64で、longが64ビットなら配列は1、32ビットなら配列は2と、_NSIG_WORDSがシステムに応じた配列数となります。で配列数に応じて各要素をただ単に反転するだけです。
配列数が4/2/1(long型が16/32/64ビット)でない場合、_NSIG_WORDS_is_unsupported_size()がコールされる事になりますが、_NSIG_WORDS_is_unsupported_size()は定義されておらず、inlineしかもswitchは定数で、リンク時にエラーとなります。
sigmaskにSIGKILL/SIGSTOPを追加し、コーディング上の実装故で、受信シグナル属性はビット単位で設定されており、signotset()で受信するシグナル属性を反転します。
引数ufd==-1なら、signalfd_fopsをコールバックとする新規FILEを取得し、そうでないならsignalfdからFILEを取得し、file->private_data=ctx、あるいはctx->sigmask = sigmaskで更新します。
static const struct file_operations signalfd_fops = { .release = signalfd_release, .poll = signalfd_poll, .read = signalfd_read, .llseek = noop_llseek, }; SYSCALL_DEFINE4(signalfd4, int, ufd, sigset_t __user *, user_mask, size_t, sizemask, int, flags) { sigset_t sigmask; struct signalfd_ctx *ctx; BUILD_BUG_ON(SFD_CLOEXEC != O_CLOEXEC); BUILD_BUG_ON(SFD_NONBLOCK != O_NONBLOCK); if (flags & ~(SFD_CLOEXEC | SFD_NONBLOCK)) return -EINVAL; if (sizemask != sizeof(sigset_t) || copy_from_user(&sigmask, user_mask, sizeof(sigmask))) return -EINVAL; sigdelsetmask(&sigmask, sigmask(SIGKILL) | sigmask(SIGSTOP)); signotset(&sigmask); if (ufd == -1) { ctx = kmalloc(sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; ctx->sigmask = sigmask; ufd = anon_inode_getfd("[signalfd]", &signalfd_fops, ctx, O_RDWR | (flags & (O_CLOEXEC | O_NONBLOCK))); if (ufd < 0) kfree(ctx); } else { struct file *file = fget(ufd); if (!file) return -EBADF; ctx = file->private_data; if (file->f_op != &signalfd_fops) { fput(file); return -EINVAL; } spin_lock_irq(¤t->sighand->siglock); ctx->sigmask = sigmask; spin_unlock_irq(¤t->sighand->siglock); wake_up(¤t->sighand->signalfd_wqh); fput(file); } return ufd; }countはbuffサイズで、count /= sizeof(struct signalfd_siginfo)で所得するシグナル数とし、signalfd_dequeue()でcount個となるまでシグナルを取得すべくループします。
O_NONBLOCKでは、シグナルが無いとエラーになりますが、複数シグナル受信の場合、最初のシグナルが受信できれば、以降nonblock = 1とし、残りのシグナルはウエイトする事で受信します。
static ssize_t signalfd_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { struct signalfd_ctx *ctx = file->private_data; struct signalfd_siginfo __user *siginfo; int nonblock = file->f_flags & O_NONBLOCK; ssize_t ret, total = 0; siginfo_t info; count /= sizeof(struct signalfd_siginfo); if (!count) return -EINVAL; siginfo = (struct signalfd_siginfo __user *) buf; do { ret = signalfd_dequeue(ctx, &info, nonblock); if (unlikely(ret <= 0)) break; ret = signalfd_copyinfo(siginfo, &info); if (ret < 0) break; siginfo++; total += ret; nonblock = 1; } while (--count); return total ? total: ret; }currentのstruct sigpending pendingから、ctx->sigmaskのシグナルを取得します。返値は取得したシグナルNOです。0なら取得しておらず、ブロックしないならエラーですが、ブロックするならカレントプロセスをwaiteキューにリストし、シグナルが取得できるまでschedule()を介してループし続けます。
static ssize_t signalfd_dequeue(struct signalfd_ctx *ctx, siginfo_t *info, int nonblock) { ssize_t ret; DECLARE_WAITQUEUE(wait, current); spin_lock_irq(¤t->sighand->siglock); ret = dequeue_signal(current, &ctx->sigmask, info); switch (ret) { case 0: if (!nonblock) break; ret = -EAGAIN; default: spin_unlock_irq(¤t->sighand->siglock); return ret; } add_wait_queue(¤t->sighand->signalfd_wqh, &wait); for (;;) { set_current_state(TASK_INTERRUPTIBLE); ret = dequeue_signal(current, &ctx->sigmask, info); if (ret != 0) break; if (signal_pending(current)) { ret = -ERESTARTSYS; break; } spin_unlock_irq(¤t->sighand->siglock); schedule(); spin_lock_irq(¤t->sighand->siglock); } spin_unlock_irq(¤t->sighand->siglock); remove_wait_queue(¤t->sighand->signalfd_wqh, &wait); __set_current_state(TASK_RUNNING); return ret; }
追記
BUILD_BUG_ONマクロはconditionが真(1)なら、!!(condition)=1となり、配列char[-1]でコンパイルエラーです。!!(condition)でなく(condition)でいいのでは?と思いますが、BUILD_BUG_ON(a=4-3-1)と言った定数としてconditionを取り扱うこともできるようにするためです。#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))
SFD_CLOEXEC != O_CLOEXEC及びSFD_NONBLOCK != O_NONBLOCKなら、コンパイル時にエラーとなります。カーネルコンパイル時、_LINUX_SIGNALFD_Hを定義すれば、#define SFD_CLOEXEC O_CLOEXEC/#define SFD_NONBLOCK O_NONBLOCKとされ、_LINUX_SIGNALFD_Hをundefineとすることで、signalfdを独自に実装するとか、ないし実装しない実装を考慮してかと思いますが、この必要性の実効性の有無は理解不能です。
signotset()は、ビット単位で設定してあるシグナル情報を反転するだけですが、_SIG_SET_OPマクロを介しての以下の実装となっています。
longsigset_t sigmask typedef struct { unsigned long sig[_NSIG_WORDS]; } sigset_t; #define _NSIG 64 #define _NSIG_BPW __BITS_PER_LONG #define _NSIG_WORDS (_NSIG / _NSIG_BPW) #define _sig_not(x) (~(x)) _SIG_SET_OP(signotset, _sig_not) #define _SIG_SET_OP(name, op) \ static inline void name(sigset_t *set) \ { \ extern void _NSIG_WORDS_is_unsupported_size(void); \ \ switch (_NSIG_WORDS) { \ case 4: set->sig[3] = op(set->sig[3]); \ set->sig[2] = op(set->sig[2]); \ case 2: set->sig[1] = op(set->sig[1]); \ case 1: set->sig[0] = op(set->sig[0]); \ break; \ default: \ _NSIG_WORDS_is_unsupported_size(); \ } \ }結果的に以下の実装になります。目的は、ビット情報としてのシグナル属性を、long型の配列とする事にあり、配列数がシグナル数およびlongのビットサイズに依存するからです。
シグナル数は64で、longが64ビットなら配列は1、32ビットなら配列は2と、_NSIG_WORDSがシステムに応じた配列数となります。で配列数に応じて各要素をただ単に反転するだけです。
配列数が4/2/1(long型が16/32/64ビット)でない場合、_NSIG_WORDS_is_unsupported_size()がコールされる事になりますが、_NSIG_WORDS_is_unsupported_size()は定義されておらず、inlineしかもswitchは定数で、リンク時にエラーとなります。
static inline void signotset(sigset_t *set) \ { \ \ switch (_NSIG_WORDS) { \ case 4: set->sig[3] = ~set->sig[3]; \ set->sig[2] = ~set->sig[2]; \ case 2: set->sig[1] = ~set->sig[1]; \ case 1: set->sig[0] = ~set->sig[0]; \ break; \ default: \ _NSIG_WORDS_is_unsupported_size(); \ } \ }