前言 在 Linux 进程管理体系中,信号(Signal)是最古老也最核心的异步通知机制,而进程间通信(IPC)则是多进程协作的基础设施。本文基于 Linux 6.4-rc1 内核源码,深入剖析信号的数据结构、发送路径、处理流程,以及 pipe、POSIX 消息队列、System V IPC、UNIX Domain Socket 和 futex 的内核实现。理解这些机制,是系统编程、性能调优和内核调试的必备基础。
一、信号机制:数据结构全景 1.1 sigset_t:信号集位图 信号集的底层表示是一个位图数组。x86-64 下 _NSIG = 64,_NSIG_BPW = 64,因此 _NSIG_WORDS = 1,整个集合用一个 64 位整数表示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 static inline void sigaddset (sigset_t *set , int _sig) { unsigned long sig = _sig - 1 ; if (_NSIG_WORDS == 1 ) set ->sig[0 ] |= 1UL << sig; else set ->sig[sig / _NSIG_BPW] |= 1UL << (sig % _NSIG_BPW); } static inline int sigismember (sigset_t *set , int _sig) { unsigned long sig = _sig - 1 ; if (_NSIG_WORDS == 1 ) return 1 & (set ->sig[0 ] >> sig); else return 1 & (set ->sig[sig / _NSIG_BPW] >> (sig % _NSIG_BPW)); }
信号编号从 1 开始,因此位操作时先减 1。信号 1-31 是传统不可靠信号(POSIX 标准信号),32-64 是实时信号(SIGRTMIN=34, SIGRTMAX=64)。
1.2 核心数据结构 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 32 33 34 35 struct sigpending { struct list_head list ; sigset_t signal; }; struct sigqueue { struct list_head list ; int flags; kernel_siginfo_t info; struct ucounts *ucounts ; }; struct sigaction { __sighandler_t sa_handler; unsigned long sa_flags; sigset_t sa_mask; }; struct k_sigaction { struct sigaction sa ; }; struct sighand_struct { spinlock_t siglock; refcount_t count; wait_queue_head_t signalfd_wqh; struct k_sigaction action [_NSIG ]; };
sighand_struct 是线程组内所有线程共享的,当调用 sigaction() 修改某个信号的处理器时,整个线程组都受影响。
1.3 signal_struct:进程级信号状态 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 struct signal_struct { refcount_t sigcnt; atomic_t live; int nr_threads; struct list_head thread_head ; wait_queue_head_t wait_chldexit; struct task_struct *curr_target ; struct sigpending shared_pending ; int group_exit_code; int group_stop_count; unsigned int flags; };
每个 task_struct 还拥有自己私有的 task->pending,用于接收 tgkill/tkill 等指定线程的信号。信号投递时,shared_pending 由线程组中任意一个线程处理,task->pending 只能由目标线程处理。
二、信号发送路径 2.1 kill() 到 __send_signal_locked 的调用链 1 2 3 4 5 6 7 8 kill(pid, sig) └── sys_kill() └── kill_something_info() ├── kill_pid_info() → 发给单进程 └── __kill_pgrp_info() → 发给进程组 └── group_send_sig_info() └── send_signal_locked() └── __send_signal_locked()
进程组信号发送的核心实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int __kill_pgrp_info(int sig, struct kernel_siginfo *info, struct pid *pgrp){ struct task_struct *p = NULL ; int retval, success; success = 0 ; retval = -ESRCH; do_each_pid_task(pgrp, PIDTYPE_PGID, p) { int err = group_send_sig_info(sig, info, p, PIDTYPE_PGID); success |= !err; retval = err; } while_each_pid_task(pgrp, PIDTYPE_PGID, p); return success ? 0 : retval; }
2.2 __send_signal_locked:信号入队核心 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 32 33 34 35 36 37 38 39 40 41 static int __send_signal_locked(int sig, struct kernel_siginfo *info, struct task_struct *t, enum pid_type type, bool force) { struct sigpending *pending ; struct sigqueue *q ; int override_rlimit; lockdep_assert_held(&t->sighand->siglock); if (!prepare_signal(sig, t, force)) goto ret; pending = (type != PIDTYPE_PID) ? &t->signal->shared_pending : &t->pending; if (legacy_queue(pending, sig)) goto ret; if (sig < SIGRTMIN) override_rlimit = (is_si_special(info) || info->si_code >= 0 ); else override_rlimit = 0 ; q = __sigqueue_alloc(sig, t, GFP_ATOMIC, override_rlimit, 0 ); if (q) { list_add_tail(&q->list , &pending->list ); copy_siginfo(&q->info, info); } else if (sig >= SIGRTMIN && info->si_code != SI_USER) { return -EAGAIN; } out_set: signalfd_notify(t, sig); sigaddset(&pending->signal, sig); complete_signal(sig, t, type); return 0 ; }
可靠信号 vs 不可靠信号的关键差异 在第 1073-1076 行的 legacy_queue():
1 2 3 4 static inline bool legacy_queue (struct sigpending *signals, int sig) { return (sig < SIGRTMIN) && sigismember(&signals->signal, sig); }
信号 1-31(sig < SIGRTMIN=34)如果 pending 位图已置位,新的投递会被静默丢弃。而实时信号(34-64)不受此约束,每次都会分配新的 sigqueue 节点入链表,从而实现多次投递的可靠排队。
2.3 signal_wake_up:唤醒目标进程 1 2 3 4 5 6 7 8 9 10 11 12 void signal_wake_up_state (struct task_struct *t, unsigned int state) { lockdep_assert_held(&t->sighand->siglock); set_tsk_thread_flag(t, TIF_SIGPENDING); if (!wake_up_state(t, state | TASK_INTERRUPTIBLE)) kick_process(t); }
TIF_SIGPENDING 标志设置后,目标进程在下次从内核返回用户态时(系统调用返回或中断返回)会检查并处理信号。
2.4 tgkill / tkill:向指定线程发送信号 tkill(tid, sig) 绕过线程组,直接向特定 tid 发送信号,信号进入 task->pending(私有队列)而非 signal->shared_pending。这对于多线程程序中精确控制信号投递至关重要,也是 pthread_kill() 的内核实现基础。
三、信号处理流程 3.1 信号处理时机 信号不是异步立即执行的,而是在进程从内核态返回用户态的”安全点”处理:
系统调用返回前 :syscall_exit_to_user_mode() → exit_to_user_mode_loop() → 检查 TIF_SIGPENDING
中断返回前 :硬件中断处理完毕返回用户态时检查
x86-64 的入口点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void arch_do_signal_or_restart (struct pt_regs *regs) { struct ksignal ksig ; if (get_signal(&ksig)) { handle_signal(&ksig, regs); return ; } if (syscall_get_nr(current, regs) != -1 ) { switch (syscall_get_error(current, regs)) { case -ERESTARTNOINTR: regs->ax = regs->orig_ax; regs->ip -= 2 ; break ; } } restore_saved_sigmask(); }
3.2 get_signal:从 pending 队列取信号 get_signal() 是信号出队的核心函数(kernel/signal.c:2642),其工作流程:
检查 TIF_SIGPENDING 标志
调用 try_to_freeze() 处理冻结请求
调用 dequeue_signal() 从 task->pending 或 signal->shared_pending 取出最高优先级信号
检查信号处理器:若为 SIG_DFL 且是致命信号,执行默认行为(SIGKILL 直接在此终止进程)
返回 ksignal(含信号编号、info、处理器)
SIGKILL 和 SIGSTOP 在此处被特殊处理——它们永远不会到达用户态信号处理器:
1 2 3 4 5 6 if ((signal->flags & SIGNAL_GROUP_EXIT) || signal->group_exec_task) { ksig->info.si_signo = signr = SIGKILL; goto fatal; }
3.3 handle_signal 与信号栈帧 当信号有用户态处理器时,handle_signal() 负责在用户栈上构建信号栈帧,并修改 pt_regs 使处理器返回用户态时跳转到信号处理函数:
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 32 33 static void handle_signal (struct ksignal *ksig, struct pt_regs *regs) { bool stepping, failed; struct fpu *fpu = ¤t->thread.fpu; if (syscall_get_nr(current, regs) != -1 ) { switch (syscall_get_error(current, regs)) { case -ERESTART_RESTARTBLOCK: case -ERESTARTNOHAND: regs->ax = -EINTR; break ; case -ERESTARTSYS: if (!(ksig->ka.sa.sa_flags & SA_RESTART)) { regs->ax = -EINTR; break ; } fallthrough; case -ERESTARTNOINTR: regs->ax = regs->orig_ax; regs->ip -= 2 ; break ; } } failed = (setup_rt_frame(ksig, regs) < 0 ); if (!failed) { regs->flags &= ~(X86_EFLAGS_DF|X86_EFLAGS_RF|X86_EFLAGS_TF); fpu__clear_user_states(fpu); } signal_setup_done(failed, ksig, stepping); }
3.4 rt_sigframe:信号栈帧布局 setup_rt_frame() 在用户栈上压入 struct rt_sigframe:
1 2 3 4 5 6 7 struct rt_sigframe { char __user *pretcode; struct ucontext uc ; struct siginfo info ; };
struct ucontext 内嵌 struct sigcontext,后者保存了所有通用寄存器(rax/rbx/…/rsp/rip/rflags)以及 FPU 状态指针。信号处理函数执行完毕后,调用 rt_sigreturn 系统调用,内核从 uc 中恢复完整的 CPU 状态,进程无缝回到被中断的执行点。
四、管道(pipe) 4.1 struct pipe_inode_info:环形缓冲区 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 struct pipe_inode_info { struct mutex mutex ; wait_queue_head_t rd_wait, wr_wait; unsigned int head; unsigned int tail; unsigned int max_usage; unsigned int ring_size; unsigned int readers; unsigned int writers; struct pipe_buffer *bufs ; struct user_struct *user ; }; struct pipe_buffer { struct page *page ; unsigned int offset, len; const struct pipe_buf_operations *ops ; unsigned int flags; unsigned long private; };
默认 ring_size = PIPE_DEF_BUFFERS = 16,每槽一个 4KB 页,管道默认容量 64KB。head 和 tail 不做掩码,允许自然溢出环绕,访问时用 index & (ring_size - 1) 取模。
4.2 pipe_write:数据写入 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 32 33 34 35 36 37 38 39 40 41 42 43 static ssize_t pipe_write (struct kiocb *iocb, struct iov_iter *from) { struct pipe_inode_info *pipe = filp->private_data; unsigned int head; __pipe_lock(pipe); if (!pipe->readers) { send_sig(SIGPIPE, current, 0 ); ret = -EPIPE; goto out; } head = pipe->head; was_empty = pipe_empty(head, pipe->tail); if (chars && !was_empty) { unsigned int mask = pipe->ring_size - 1 ; struct pipe_buffer *buf = &pipe->bufs[(head - 1 ) & mask]; if ((buf->flags & PIPE_BUF_FLAG_CAN_MERGE) && offset + chars <= PAGE_SIZE) { ret = copy_page_from_iter(buf->page, offset, chars, from); buf->len += ret; } } for (;;) { head = pipe->head; if (!pipe_full(head, pipe->tail, pipe->max_usage)) { struct pipe_buffer *buf = &pipe->bufs[head & mask]; pipe->head = head + 1 ; } else { if (wait_event_interruptible_exclusive(pipe->wr_wait, pipe_writable(pipe)) < 0 ) break ; } } }
4.3 管道容量控制 1 /proc/sys/fs/pipe-max-size # 默认 1048576(1MB),非 root 用户上限
通过 fcntl(fd, F_SETPIPE_SZ, size) 可以动态调整单个管道容量。内核将请求的 size 向上取整到 2 的幂(不超过 pipe_max_size),然后 krealloc 扩展 bufs 数组。
4.4 splice 与零拷贝 splice(2) 系统调用利用管道的 pipe_buffer 结构实现文件到 socket 的零拷贝:数据以页引用 的方式在管道内传递,不需要 memcpy 到用户缓冲区。vmsplice 可以将用户态内存页”赠送”给管道(PIPE_BUF_FLAG_GIFT),配合 splice 实现用户态到 socket 的全程零拷贝传输。
五、POSIX 消息队列 5.1 基于 tmpfs 的实现 POSIX 消息队列挂载在独立的 mqueue 文件系统(基于 tmpfs)。每个消息队列对应一个 mqueue_inode_info:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 struct mqueue_inode_info { spinlock_t lock; struct inode vfs_inode ; wait_queue_head_t wait_q; struct rb_root msg_tree ; struct rb_node *msg_tree_rightmost ; struct posix_msg_tree_node *node_cache ; struct mq_attr attr ; struct sigevent notify ; struct pid *notify_owner ; struct ext_wait_queue e_wait_q [2]; unsigned long qsize; };
5.2 优先级队列 1 2 3 4 5 6 struct posix_msg_tree_node { struct rb_node rb_node ; struct list_head msg_list ; int priority; };
mq_send() 时,先在红黑树中查找对应优先级节点,若不存在则创建新节点插入;mq_receive() 直接取 msg_tree_rightmost(最右节点即最高优先级),时间复杂度 O(log P)(P 为不同优先级数量),同优先级内 FIFO 顺序。
六、System V IPC 6.1 信号量:struct sem_array 1 2 3 4 5 6 7 8 9 10 11 12 13 struct sem_array { struct kern_ipc_perm sem_perm ; time64_t sem_ctime; struct list_head pending_alter ; struct list_head pending_const ; struct list_head list_id ; int sem_nsems; int complex_count; unsigned int use_global_lock; struct sem sems []; } __randomize_layout;
semop() 的原子性保证:操作执行前检查整个操作集合能否同时满足(”all-or-nothing”语义),不能满足则整体入 pending_alter 睡眠等待。内核还支持 SEM_UNDO 标志,进程退出时自动回滚所有 semop 操作,防止死锁。
6.2 消息队列:struct msg_queue 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 struct msg_queue { struct kern_ipc_perm q_perm ; time64_t q_stime; time64_t q_rtime; unsigned long q_cbytes; unsigned long q_qnum; unsigned long q_qbytes; struct pid *q_lspid ; struct pid *q_lrpid ; struct list_head q_messages ; struct list_head q_receivers ; struct list_head q_senders ; } __randomize_layout;
msgsnd() 将消息附加到 q_messages 链表尾部;msgrcv() 支持按 msgtype 过滤接收(正数接收指定类型,负数接收类型绝对值最小的,0 接收任意最早消息)。
6.3 共享内存 shmget()/shmat() 基于 tmpfs(shmem)实现。shmget() 在 shmem 文件系统上创建一个匿名文件,shmat() 调用 do_mmap() 将该文件的页面映射到进程虚拟地址空间。多个进程 shmat() 同一个 shmid,它们的虚拟地址映射到相同的物理页(零拷贝),通过共享页表实现数据直接共享。
七、UNIX Domain Socket 7.1 struct unix_sock 1 2 3 4 5 6 7 8 9 10 11 12 13 struct unix_sock { struct sock sk ; struct unix_address *addr ; struct path path ; struct mutex iolock , bindlock ; struct sock *peer ; struct list_head link ; atomic_long_t inflight; spinlock_t lock; struct socket_wq peer_wq ; struct scm_stat scm_stat ; };
7.2 unix_stream_sendmsg:内存直传 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 static int unix_stream_sendmsg (struct socket *sock, struct msghdr *msg, size_t len) { struct sock *sk = sock->sk; struct sock *other = unix_peer(sk); err = scm_send(sock, msg, &scm, false ); while (sent < len) { skb = sock_alloc_send_pskb(sk, size - data_len, data_len, ...); err = unix_scm_to_skb(&scm, skb, !fds_sent); err = skb_copy_datagram_from_iter(skb, 0 , &msg->msg_iter, size); unix_state_lock(other); skb_queue_tail(&other->sk_receive_queue, skb); unix_state_unlock(other); other->sk_data_ready(other); } }
UNIX Domain Socket 的关键优势:数据通过 sk_buff 在内核内存中传递,完全绕过网络协议栈 (无 TCP/IP 头部处理、无校验和计算、无路由查找)。与管道相比,它支持双向通信和数据报语义(SOCK_DGRAM)。
7.3 文件描述符传递(SCM_RIGHTS) UNIX Domain Socket 最独特的能力是通过 sendmsg() + SCM_RIGHTS 辅助消息传递文件描述符。发送端将 fd 列表附加到 struct scm_fp_list,内核在 unix_scm_to_skb() 中调用 get_file() 增加文件的引用计数,将 struct file * 指针存入 skb->cb(即 UNIXCB(skb).fp)。接收端通过 recvmsg() 取出后,内核在当前进程的文件描述符表中安装新的 fd,指向同一个 struct file 对象,实现跨进程 fd 共享。这是容器运行时、Chrome 沙箱和 systemd socket activation 等系统的核心机制。
八、futex:快速用户空间互斥锁 8.1 设计哲学 futex(Fast Userspace muTEX)的核心洞察:在无竞争时完全在用户态完成,仅在竞争时陷入内核 。glibc 的 pthread_mutex_lock() 底层就是 futex。
8.2 struct futex_hash_bucket 1 2 3 4 5 6 struct futex_hash_bucket { atomic_t waiters; spinlock_t lock; struct plist_head chain ; } ____cacheline_aligned_in_smp;
内核维护一个全局哈希表 futex_queues,以 futex 变量的物理地址(对于 shared futex)或虚拟地址(对于 private futex)为 key 哈希到对应的 bucket。
8.3 FUTEX_WAIT:快速路径 vs 慢速路径 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 32 int futex_wait (u32 __user *uaddr, unsigned int flags, u32 val, ktime_t *abs_time, u32 bitset ) { struct futex_hash_bucket *hb ; struct futex_q q = futex_q_init; retry: ret = futex_wait_setup(uaddr, val, flags, &q, &hb); if (ret) goto out; futex_wait_queue(hb, &q, to); if (!futex_unqueue(&q)) goto out; if (!signal_pending(current)) goto retry; ret = -ERESTARTSYS; }
快速路径 (用户态完成):pthread_mutex_lock() 用 cmpxchg 原子地尝试将 futex 值从 0 改为线程 TID;成功则无需系统调用。只有 cmpxchg 失败(有竞争)时才调用 sys_futex(FUTEX_WAIT, ...)。
8.4 FUTEX_WAKE:唤醒等待者 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 int futex_wake (u32 __user *uaddr, unsigned int flags, int nr_wake, u32 bitset ) { struct futex_hash_bucket *hb ; DEFINE_WAKE_Q(wake_q); ret = get_futex_key(uaddr, flags & FLAGS_SHARED, &key, FUTEX_READ); hb = futex_hash(&key); if (!futex_hb_waiters_pending(hb)) return ret; spin_lock(&hb->lock); plist_for_each_entry_safe(this, next, &hb->chain, list ) { if (futex_match(&this->key, &key)) { futex_wake_mark(&wake_q, this); if (++ret >= nr_wake) break ; } } spin_unlock(&hb->lock); wake_up_q(&wake_q); return ret; }
futex_hb_waiters_pending() 检查 hb->waiters 计数,若为 0 则跳过所有锁操作——这是 futex 在无竞争时保持低开销的关键优化。
8.5 pthread_mutex 的 futex 实现原理 1 2 3 4 5 6 7 8 9 10 11 12 pthread_mutex_lock(): 1. cmpxchg(futex_addr, 0, tid) // 用户态原子操作,无系统调用 成功 → 获得锁,返回 失败 → 调用 sys_futex(FUTEX_WAIT, futex_addr, current_val) 内核将当前线程加入 hash bucket 的等待链表 调度出去睡眠 pthread_mutex_unlock(): 1. atomic_store(futex_addr, 0) // 用户态释放 2. 如果 futex 值之前标记了 FUTEX_WAITERS: 调用 sys_futex(FUTEX_WAKE, futex_addr, 1) 内核从等待链表中取出一个线程唤醒
九、诊断方法与工具 9.1 信号诊断 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 kill -lstrace -e trace=signal -p <PID> cat /proc/$(pgrep mysqld)/status | grep -E "Sig(Blk|Pnd|Cgt|Ign)" bpftrace -e 'kprobe:__send_signal_locked { printf("sig=%d pid=%d->%d\n", arg0, pid, ((struct task_struct*)arg2)->pid); }'
9.2 IPC 资源诊断 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ipcs -a lsof | grep pipe | head -20 ls -la /dev/mqueue/bpftrace -e 'kprobe:do_futex { @[ustack] = count(); } interval:s:5 { print(@); clear(@); exit(); }' perf stat -e 'syscalls:sys_enter_futex' -p <PID> -- sleep 10
9.3 /proc/PID/status 信号位图解读 信号位图是 64 位十六进制数,每个 bit 对应一个信号编号(bit N = 信号 N+1)。例如 SigIgn: 0000000000001000:
1 2 0x1000 = 0001 0000 0000 0000 (binary) bit 12 已置位 → 信号 13 (SIGPIPE) 被忽略
守护进程通常会忽略 SIGPIPE 和 SIGHUP,这在 SigIgn 中可以直接观察到。
十、各 IPC 机制对比
机制
方向
持久性
传输效率
主要用途
信号
单向通知
非持久
极低(仅编号)
异步事件通知
pipe
单向数据流
进程生命期
高(ring buffer)
父子进程数据流
UNIX Socket
双向
进程生命期
高(内存拷贝)
本机 C/S 通信、fd 传递
POSIX MQ
双向
内核持久
中(优先级排序)
有序异步消息传递
SysV 消息队列
双向
内核持久
中
传统 IPC
共享内存
双向
内核持久
极高(零拷贝)
大数据量共享
futex
同步原语
非持久
极高(无竞争零系统调用)
互斥锁/条件变量
总结 Linux 信号与 IPC 机制的设计体现了内核”机制与策略分离”的哲学:
信号 通过 sigpending 位图 + sigqueue 链表分别处理可靠性需求,借助 TIF_SIGPENDING 在内核返回用户态的安全点触发;
pipe 以环形 pipe_buffer 数组为核心,支持页级零拷贝(splice),默认容量 64KB 可动态调整;
futex 以用户态原子操作为快速路径、内核哈希等待队列为慢速路径,是现代高性能同步原语的基础;
UNIX Domain Socket 通过 sk_buff 在内核内完成内存传递,配合 SCM_RIGHTS 实现跨进程 fd 共享;
System V IPC 和 POSIX 消息队列 则提供了更结构化的进程间通信语义。
理解这些机制的内核实现,能够帮助我们在系统设计、性能调优和问题排查时做出更明智的技术选择。
参考源码
kernel/signal.c — 信号发送、处理核心
include/linux/signal_types.h — 信号数据结构定义
include/linux/sched/signal.h — sighand_struct / signal_struct
arch/x86/kernel/signal.c — x86-64 信号帧构建
arch/x86/include/asm/sigframe.h — struct rt_sigframe
include/linux/pipe_fs_i.h — pipe_inode_info / pipe_buffer
fs/pipe.c — 管道读写实现
kernel/futex/core.c — futex 哈希表
kernel/futex/waitwake.c — futex_wait / futex_wake
kernel/futex/futex.h — futex_hash_bucket
ipc/sem.c — System V 信号量
ipc/msg.c — System V 消息队列
ipc/mqueue.c — POSIX 消息队列
net/unix/af_unix.c — UNIX Domain Socket
include/net/af_unix.h — struct unix_sock