Signals can express process control actions such as termination or suspension, and can also notify a thread of an event or exception. The key to correct usage lies in: who receives the signal, which thread delivers it, when it is blocked, and what the handler can safely do.
Disposition, Masking, and Pending
Signal disposition (default, ignore, or invoke handler) is a process-shared attribute, while the mask is a per-thread attribute. A signal sent to a process can be delivered to any thread that has it unblocked; thread-directed signals, such as those from hardware exceptions or pthread_kill, have a specific target.
Blocking is not ignoring: a signal may become pending until it is unblocked or consumed by a synchronous wait interface. SIGKILL and SIGSTOP cannot be caught, ignored, or blocked. Multithreaded programs use pthread_sigmask to coordinate masking; new threads inherit the creator's mask and do not automatically change if another thread modifies its mask later. signal(7)
Ordinary signals are typically not counted per send; multiple occurrences of the same signal while it is pending may be coalesced. Treating SIGUSR1 as a lossless task queue is an unreliable design; the actual task count should be stored in a queue or counter, with the signal serving only as a hint to check.
Why doesn't the number of sends equal the number of handles?
The key condition is whether the signal is still pending. If you keep it blocked and send it three times, only one instance remains for ordinary signals; if you allow the handler to consume one instance per send, you can handle three. Real-time signals preserve queued instances, but you must also check if the send succeeded.
Preparing the visual
Why doesn't the number of sends equal the number of handles?
Ordinary signals coalesce while pending; real-time signals can queue but have resource limits. Unblocking does not restore the coalesced count for ordinary signals.
{"id":"signal-pending-coalescing","title":"Why doesn't the number of sends equal the number of handles?","summary":"Ordinary signals coalesce while pending; real-time signals can queue but have resource limits. Unblocking does not restore the coalesced count for ordinary signals.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"presets\" role=\"group\" data-i18n-label=\"kind\"><button data-mode=\"standard\" data-i18n=\"standard\"></button><button data-mode=\"rt\" data-i18n=\"rt\"></button></div><label class=\"check\"><input type=\"checkbox\" id=\"blocked\" checked><span data-i18n=\"blocked\"></span></label><div class=\"actions\"><button id=\"send\" data-i18n=\"send\"></button><button id=\"reset\" data-i18n=\"reset\"></button></div><div class=\"track-row\"><span data-i18n=\"pending\"></span><div id=\"pending\" class=\"track\"></div></div><div class=\"metrics\"><div><span data-i18n=\"sent\"></span><strong id=\"sent\"></strong></div><div><span data-i18n=\"handled\"></span><strong id=\"handled\"></strong></div><div><span data-i18n=\"failed\"></span><strong id=\"failed\"></strong></div><div><span data-i18n=\"merged\"></span><strong id=\"merged\"></strong></div></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n#pending{display:grid;grid-template-columns:repeat(4,minmax(0,1fr))}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}let mode='standard',queue=[],sent=0,handled=0,failed=0,merged=0;function drain(){if(!$('#blocked').checked){handled+=queue.length;queue=[]}}function draw(){pressed(mode);cells('pending',Array.from({length:4},(_,i)=>({text:queue[i]?'#'+queue[i]:'—',cls:queue[i]?'on':'empty'})));for(const [k,v] of Object.entries({sent,handled,failed,merged}))set(k,v);$('#pending').dataset.count=queue.length;}function reset(){queue=[];sent=handled=failed=merged=0;$('#blocked').checked=true;draw()}$('#send').onclick=()=>{sent++;if(mode==='standard'&&queue.length)merged++;else if(queue.length===4)failed++;else queue.push(sent);drain();draw()};$('#blocked').onchange=()=>{drain();draw()};document.querySelectorAll('[data-mode]').forEach(e=>e.onclick=()=>{mode=e.dataset.mode;reset()});$('#reset').onclick=reset;draw();","audio":false,"strings":{"blocked":"Delivery Blocked","failed":"Queue Full, Rejected","handled":"Handler Calls","heading":"Why doesn't the number of sends equal the number of handles?","intro":"Keep the signal blocked and send three times, then unblock; compare with a real-time signal of the same number.","kind":"Signal Type","merged":"Coalesced Sends","pending":"Pending Instance","reset":"Reset","rt":"Real-time Signal (same number)","scope":"Single thread, handler installed. Real-time queue teaching capacity is 4, rejecting when full; after unblocking, the model immediately processes all deliverable items, which does not represent real scheduling latency.","scopeLabel":"Model Scope and Conventions","send":"Send Once","sent":"Send Request","standard":"Ordinary Signal"}}
sigaction and Execution Context
sigaction explicitly specifies the handler, the additional mask during execution, and behavior flags. sigset_t should be initialized via interfaces like sigemptyset/sigaddset; do not assume it is a portable field that can be directly assigned as an integer.
Flag
Meaning
SA_SIGINFO
Use a handler that takes siginfo_t and context arguments
SA_RESTART
Request corresponding behavior for interfaces that support restart; does not cover all system calls
SA_NODEFER
Do not automatically block the same signal while processing it, allowing more complex reentrancy
SA_RESETHAND
Restore the default disposition upon entering the handler, affecting subsequent delivery behavior
SA_ONSTACK
Use the alternate signal stack if one is available for the thread
The signal frame saves the context (registers, mask, etc.) needed to resume execution; its specific layout is determined by the architecture and ABI. Upon normal return from the handler, the relevant sigreturn path is called via the user-space trampoline. Do not assume an x86-64 stack layout is fixed for arm64, nor call sigreturn directly like a normal function.
Async-signal safety is stricter than thread safety
A handler may interrupt the current thread while malloc, printf, or a normal lock is modifying internal state; calling the same facility again will encounter inconsistency or self-deadlock. Ordinary "thread safety" guarantees do not replace async-signal-safe guarantees.
A common design is for the handler to only set a volatile sig_atomic_t flag, with the actual work handled by the normal control flow. This type is for restricted signal interaction and is not a substitute for arbitrary multithreaded shared protocols; complex counting, incrementing, and consistency across multiple fields still require separate analysis.
When you need to wake an event loop, you can use a pre-established non-blocking self-pipe and call the allowed write in the handler; if the pipe is full, you must accept that the notification is coalesced and recheck the state in the normal path. Although write is an async-signal-safe interface, it may still block on a blocking fd; safe does not mean zero latency. Handlers that use errno should save and restore it. signal-safety(7)
Use sigsuspend to avoid the race between check and wait
while (!seen) pause() has a window: the signal might be handled after the check, but then pause continues to wait for the next one. The following single-threaded example first blocks the signal, then uses sigsuspend to atomically replace the mask and wait.
#define_POSIX_C_SOURCE200809L#include<errno.h>#include<signal.h>#include<unistd.h>staticvolatile sig_atomic_t seen;staticvoidon_signal(intsigno){(void)signo;
seen =1;}intmain(void){
sigset_t blocked, previous, waiting;sigemptyset(&blocked);sigaddset(&blocked, SIGUSR1);if(sigprocmask(SIG_BLOCK,&blocked,&previous)==-1)return1;struct sigaction action ={0};
action.sa_handler= on_signal;sigemptyset(&action.sa_mask);if(sigaction(SIGUSR1,&action,NULL)==-1)return1;if(raise(SIGUSR1)!=0)return1;// Enters pending at this point
waiting = previous;sigdelset(&waiting, SIGUSR1);while(!seen){if(sigsuspend(&waiting)==-1&& errno != EINTR)return1;}if(sigprocmask(SIG_SETMASK,&previous,NULL)==-1)return1;return0;}
During the wait, delivery of SIGUSR1 is allowed; after the handler returns, sigsuspend restores the original blocked mask and returns with EINTR. The loop rechecks seen to handle wakeups caused by other signals. The self-generated signal in the example is just to make the flow repeatable; in a real program, notifications can come from other authorized sources. sigsuspend(2)
pselect, ppoll, and epoll_pwait can also temporarily replace the mask while waiting for I/O, solving the same window issue, rather than manually unblocking and then calling a normal wait.
Step-by-step comparison of error timing vs. correct timing
"The handler has already set the flag to 1" does not mean an entering wait will automatically see it. Wait system calls do not read the application's seen variable. This example also shows that the notification and the condition being waited for are two different things: the loop is responsible for confirming the condition, and the atomic wait is responsible for avoiding missing notifications corresponding to condition changes.
If the flag is already 1, why does the thread still sleep?
Try "check → send → wait": in the naive solution, seen is already 1, but the thread waits for the next signal; in the safe solution, the signal stays pending and is delivered upon entering sigsuspend. Try "check → wait → send"; both solutions return, showing that verifying only the normal order is insufficient to check the wait protocol.
Preparing the visual
If the flag is already 1, why does the thread still sleep?
pause does not read application flags. If the signal is handled after the check but before the wait, the wakeup may be lost; blocking and then using sigsuspend to atomically switch the mask can preserve the pending notification.
{"id":"signal-check-sleep-gap","title":"If the flag is already 1, why does the thread still sleep?","summary":"pause does not read application flags. If the signal is handled after the check but before the wait, the wakeup may be lost; blocking and then using sigsuspend to atomically switch the mask can preserve the pending notification.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"presets\" role=\"group\" data-i18n-label=\"mode\"><button data-mode=\"naive\" data-i18n=\"naive\"></button><button data-mode=\"safe\" data-i18n=\"safe\"></button></div><div class=\"actions\"><button id=\"check\" data-i18n=\"check\"></button><button id=\"send\" data-i18n=\"send\"></button><button id=\"wait\" data-i18n=\"wait\"></button><button id=\"reset\" data-i18n=\"reset\"></button></div><div class=\"metrics\"><div><span data-i18n=\"flag\"></span><strong id=\"flag\"></strong></div><div><span data-i18n=\"pending\"></span><strong id=\"pending\"></strong></div><div><span data-i18n=\"checked\"></span><strong id=\"checked\"></strong></div><div><span data-i18n=\"thread\"></span><strong id=\"thread\"></strong></div></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n.metrics strong{font-size:17px}#thread{min-height:4em}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}let mode='naive',seen=0,pending=0,checked=null,state='run';function draw(){pressed(mode);set('flag',seen);set('pending',pending);set('checked',checked??'—');set('thread',t(state));$('#thread').dataset.state=state;$('#check').disabled=state==='sleep';$('#wait').disabled=checked!==0||state!=='run';}function reset(){seen=pending=0;checked=null;state='run';draw()}$('#check').onclick=()=>{checked=seen;state=seen?'skip':'run';draw()};$('#send').onclick=()=>{if(mode==='safe'&&state!=='sleep')pending=1;else {seen=1;if(state==='sleep')state='returned';}draw()};$('#wait').onclick=()=>{if(mode==='safe'&&pending){pending=0;seen=1;state='returned'}else state='sleep';draw()};document.querySelectorAll('[data-mode]').forEach(e=>e.onclick=()=>{mode=e.dataset.mode;reset()});$('#reset').onclick=reset;draw();","audio":false,"strings":{"check":"Check seen","checked":"Last Check Result","flag":"seen","heading":"If the flag is already 1, why does the thread still sleep?","intro":"Freely arrange the order of \"check, send, wait\" and compare pause with sigsuspend.","mode":"Wait Protocol","naive":"Check + pause","pending":"Pending","reset":"Reset","returned":"Wait Returned, Needs Recheck","run":"Runnable","safe":"Block + sigsuspend","scope":"Single thread, single ordinary signal, no other signals or reentrancy. Operations show one loop iteration; after returning, the condition must still be rechecked.","scopeLabel":"Model Scope and Conventions","send":"Send Signal","skip":"Condition Met, No Wait Needed","sleep":"Waiting for New Signal","thread":"Thread State","wait":"Enter Wait"}}
SA_RESTART and EINTR
On Linux, some slow device read/write, wait series, and socket operations meeting certain conditions can be affected by SA_RESTART; poll, select, epoll_wait, pause, sigsuspend, etc., will not automatically restart just because this flag is set. Socket timeout options, etc., will also change specific behaviors; you cannot rely solely on function names to create an unconditional binary table.
When a read/write has transferred partial data, it may return a positive short count; the application should preserve progress rather than retransmitting the entire message from scratch. If a timeout wait uses a relative duration, retrying after EINTR also requires deducting the elapsed time.
For synchronous faults like SIGSEGV, if the cause is not eliminated after the handler returns, the same faulting instruction may be executed again. Installing a handler does not turn arbitrary memory errors into recoverable exceptions.
Alternate signal stacks are per-thread resources
sigaltstack registers a still-valid memory region, and SA_ONSTACK makes the corresponding handler use it. This ensures there is still space to record minimal information when the normal stack is insufficient, but the alternate stack itself does not grow infinitely, nor does it make malloc, complex stack unwinding, or logging libraries suddenly async-signal-safe.
The size must consider architecture extension state, nested signals, and handler requirements; it should not be hardcoded as "usually 8 KiB is enough." Establish it in each relevant thread and manage its lifecycle correctly; do not replace or free it while it is still in use. sigaltstack(2)
Synchronous Consumption: sigwait and signalfd
A dedicated signal thread can block the target signal and then synchronously receive it using sigwaitinfo/sigwait; subsequent processing in the normal thread context is not subject to the same restrictions as an async handler. Linux signalfd integrates signal records into the fd event loop.
You should establish a consistent routing strategy across all relevant threads to avoid some signals going to the traditional handler. signalfd also cannot arbitrarily move hardware faults exclusive to another thread to the management thread. See epoll and Event-Driven Programming for configuration examples.
Ordinary signal coalescing can be deterministically reproduced within a single process
The following example first blocks SIGUSR1, sends it to itself three times in a row, and then consumes it synchronously. Since all three occur before delivery, only one ordinary signal instance remains in pending. Use sigtimedwait with a zero timeout to check if there is a second one, rather than waiting indefinitely.
#define_POSIX_C_SOURCE200809L#include<errno.h>#include<signal.h>#include<stdio.h>#include<time.h>#include<unistd.h>intmain(void){
sigset_t set, old;sigemptyset(&set);sigaddset(&set, SIGUSR1);if(sigprocmask(SIG_BLOCK,&set,&old)==-1)return1;for(int i =0; i <3;++i)if(kill(getpid(), SIGUSR1)==-1)return1;struct timespec zero ={0,0};
siginfo_t info;int first =sigtimedwait(&set,&info,&zero);
errno =0;int second =sigtimedwait(&set,&info,&zero);int saved = errno;if(sigprocmask(SIG_SETMASK,&old,NULL)==-1)return1;printf("one received=%d; next empty=%d\n",
first == SIGUSR1, second ==-1&& saved == EAGAIN);return!(first == SIGUSR1 && second ==-1&& saved == EAGAIN);}
Save as pending-demo.c, compile and run expecting one received=1; next empty=1. This single-threaded, self-generated signal experiment does not require installing a handler; sigwaitinfo/sigtimedwait synchronously takes it while the signal remains blocked. Zero timeout EAGAIN indicates there is no target signal to receive at this moment. sigtimedwait(2)
Do not generalize this result to "sending three times always results in processing only once." If the previous one is consumed between each send, you may observe three separate times. The behavior during pending determines coalescing; when you need a reliable task count, you should read the actual task queue.
Real-time Signals and timerfd
Real-time signals can carry sigval data and queue up, but the queue is resource-limited, sigqueue can fail, and you cannot promise zero loss. The queuing order for the same real-time signal and the delivery priority for different signal numbers are specified, which does not mean all numbers follow a unified FIFO based on send time. SIGRTMIN may also be adjusted by the thread library; use interface macros and verify the range.
timerfd notifications do not require a signal handler. A successful read usually returns sizeof(uint64_t) bytes; the integer read out is the accumulated expiration count, which may be greater than 1; do not misinterpret "return value is 8" as expiring 8 times. Monotonic clocks are suitable for measuring time intervals, and whether they include system suspend depends on the chosen clock.
Retrying waits must preserve the total time budget
Assume the wait budget is 100 ms. If interrupted by a signal at 30 ms, and then consuming another 40 ms later. If you wait 100 ms again for every EINTR, the total wait is extended; continuous signals may even keep pushing the completion time back. The application should calculate a fixed deadline using the selected monotonic clock, and then calculate the remaining budget based on the current time.
This is just an integer time model; actual C programs must handle timespec second/nanosecond normalization, clock read errors, and the timeout rules of specific wait interfaces. SA_RESTART cannot maintain the application-layer deadline for you. Even if a call can restart, you should not confuse "single blocking duration" with "total allowed duration for the entire operation."
SigBlk, SigIgn, SigCgt in /proc/PID/status describe masking and disposition; SigPnd and ShdPnd distinguish thread and process pending; for multithreaded issues, you also need to check the status of the corresponding tasks.
First confirm the signal target, mask, and pending state, then analyze whether the handler executed and whether the wait restarted. Do not use signal frequency to directly estimate the number of events, nor treat a single successful handler as proof that the entire program's synchronization protocol is correct.
Keywords: signal, sigaction, signal mask, pending, async-signal-safe, sigsuspend, SA_RESTART, sigaltstack, signalfd, real-time signal