Linux C 常用函数速查 链接到标题
以下总结 Linux 环境下编程常用函数的原型、用法与注意事项。每个函数注明头文件。
遇到忘了的函数,可以做以下操作 链接到标题
#install man-pages
man -k <大概的名字> # 你如果连个大概名字也没印象,那最好上网搜搜了.
命令行选项 链接到标题
getopt — 解析命令行选项 链接到标题
#include <unistd.h>
int getopt(int argc, char *const argv[], const char *optstring);
extern char *optarg;
extern int optind, opterr, optopt;
功能:解析 -x 短选项,支持带参数 -x val。循环调用直到返回 -1。
注意:每次调用修改 optind;解析完所有选项后 optind 指向第一个非选项参数的位置。多线程不安全。
错误处理 链接到标题
strerror — 错误码转字符串 链接到标题
#include <string.h>
char *strerror(int errnum);
功能:将 errno 值转为可读的错误字符串。
注意:返回的指针是静态 buffer,非线程安全。多线程用 strerror_r()。不要修改返回的字符串。
进程管理 链接到标题
fork — 创建子进程 链接到标题
#include <unistd.h>
pid_t fork(void);
功能:创建子进程,子进程是父进程的写时复制副本。父进程中返回子进程 PID,子进程中返回 0,失败返回 -1。
注意:fork 后父子并发执行,调度顺序不确定。fd 默认共享(偏移量也共享)——注意 FD_CLOEXEC。fork 在大型进程中开销大(页表复制),高并发场景可考虑 vfork 或 clone。子进程只复制调用线程,其他线程不复制。
exec 系列 — 替换进程映像 链接到标题
#include <unistd.h>
int execl(const char *path, const char *arg, ..., (char *) NULL);
int execlp(const char *file, const char *arg, ..., (char *) NULL);
int execle(const char *path, const char *arg, ..., (char *) NULL, char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
功能:用新程序替换当前进程。成功不返回,失败返回 -1。
命名规则:
| 后缀 | 含义 |
|---|---|
l (list) |
参数以变参形式给出,NULL 结尾 |
v (vector) |
参数以 char *argv[] 数组给出 |
p (path) |
在 PATH 中搜索可执行文件(不包含 / 时) |
e (environment) |
传递自定义环境变量数组,代替 environ |
注意:exec 后 FD_CLOEXEC 标记的 fd 会被关闭,其余的保留。execlp 和 execvp 如果 file 不含 /,会遍历 PATH 查找,找不到才失败。
wait / waitpid — 等待子进程状态改变 链接到标题
#include <sys/wait.h>
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);
功能:父进程等待子进程状态变化(终止、停止、恢复)。wait 等任意子进程,waitpid 可指定 pid。
waitpid 的 pid 取值:
| 值 | 含义 |
|---|---|
< -1 |
等待进程组 ID 为 -pid 的任意子进程 |
-1 |
等待任意子进程(等同于 wait) |
0 |
等待与本进程同进程组的任意子进程 |
> 0 |
等待指定 PID 的子进程 |
常用 options:WNOHANG(非阻塞)、WUNTRACED(也返回已停止的子进程)、WCONTINUED(也返回被 SIGCONT 恢复的子进程)。
状态检查宏(传入 wstatus):
| 宏 | 含义 |
|---|---|
WIFEXITED(status) |
正常退出 → 用 WEXITSTATUS(status) 取退出码 |
WIFSIGNALED(status) |
被信号杀死 → 用 WTERMSIG(status) 取信号 |
WIFSTOPPED(status) |
被信号暂停 → 用 WSTOPSIG(status) 取信号 |
WIFCONTINUED(status) |
被 SIGCONT 恢复执行 |
注意:必须 wait() 回收子进程,否则产生僵尸进程。SIGCHLD 默认忽略,子进程仍产生僵尸(除非显式设为 SIG_IGN 或 SA_NOCLDWAIT)。如果 SIGCHLD 设为 SIG_IGN,子进程自动回收不产生僵尸。
进程组、会话与终端 链接到标题
进程组 链接到标题
- 一个或多个进程的集合,由
setpgid()设置。getpgid(pid)/getpgrp()获取。 - shell 为每条管道的各个进程建立进程组。前台进程组接收终端输入和信号(
Ctrl+C → SIGINT发往前台进程组全体)。 kill(-pgid, sig)向整个进程组发信号(负号表示进程组)。
会话 链接到标题
setsid()创建新会话:调用进程成为 session leader 和新进程组的 leader,脱离控制终端。- daemon 启动的标准第一步:
fork→ 父进程退出 → 子进程setsid()。 - 一个会话最多一个控制终端。session leader 打开终端设备时终端成为控制终端。
子进程 链接到标题
fork后父子并发执行,调度顺序不保证。父子关系影响:- 子进程退出 →
SIGCHLD发往父进程。 - 父进程先退出 → 子进程被
init(PID 1)收养(ppid变为 1)。 - 父进程不
wait→ 子进程变为僵尸(ps显示Z状态),PCB 不释放。
- 子进程退出 →
- 僵尸进程占 PCB 但不占内存/CPU,大量僵尸会耗尽 PID(因为内核需保留 exit_code 等信息)。解决:
wait()或SIGCHLD设为SIG_IGN。
信号 链接到标题
signal — 注册信号处理函数(已过时) 链接到标题
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:为 signum 设置处理函数。handler 可以是 SIG_IGN(忽略)、SIG_DFL(默认)或自定义函数指针。
注意:不要用 signal(),用 sigaction()。signal 在不同 UNIX 实现中行为不一致(SysV 一次性注册 vs BSD 持久注册)。Linux 上 signal 等同于 BSD 语义,但缺乏对信号屏蔽字的控制。
sigaction — 可靠的信号处理 链接到标题
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int); // SIG_DFL, SIG_IGN 或 handler
void (*sa_sigaction)(int, siginfo_t *, void *); // sa_flags 含 SA_SIGINFO 时用
sigset_t sa_mask; // handler 执行期间额外屏蔽的信号集
int sa_flags; // SA_RESTART, SA_NOCLDSTOP, SA_SIGINFO 等
void (*sa_restorer)(void); // 不用填
};
功能:精确控制信号处理行为,取代 signal。
关键 flags:
| flag | 含义 |
|---|---|
SA_RESTART |
被该信号中断的慢速系统调用自动重启 |
SA_SIGINFO |
使用 sa_sigaction(三参数 handler,可获得发送者 PID、UID 等信息) |
SA_NOCLDWAIT |
子进程终止时不产生僵尸(等价于 SIG_IGN 对 SIGCHLD 的效果) |
SA_NODEFER |
handler 执行期间不自动屏蔽本信号(默认会屏蔽) |
注意:sa_mask 在 handler 执行期间临时加入进程屏蔽字,handler 返回后自动还原。SIGKILL 和 SIGSTOP 不能自定义处理。
kill — 发送信号 链接到标题
#include <signal.h>
int kill(pid_t pid, int sig);
功能:向进程发送信号。pid 语义和 waitpid 相同(>0 指定进程,0 进程组广播,-1 所有进程,<-1 指定进程组)。sig=0 时不发信号,只做权限检查(检测进程是否存在)。
注意:kill 不是"杀死",只是发信号。SIGTERM/SIGKILL 才是终止信号。普通用户只能向自己的进程发信号。
alarm — 定时发送 SIGALRM 链接到标题
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
功能:seconds 秒后向本进程发 SIGALRM。重复调用会重置计时,返回上次剩余秒数。alarm(0) 取消定时。
注意:只能设一个定时器。alarm 和 sleep/setitimer 可能相互干扰——它们共享同一内核定时器。不能用于高精度需求(精度 1 秒)。
setitimer — 更精确的间隔定时器 链接到标题
#include <sys/time.h>
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
struct itimerval {
struct timeval it_interval; // 周期触发间隔(0 表示单次触发)
struct timeval it_value; // 首次触发时间
};
功能:比 alarm 更灵活的定时器,支持微秒精度和周期触发。
which 取值:
| 值 | 含义 | 触发信号 |
|---|---|---|
ITIMER_REAL |
真实时间 | SIGALRM |
ITIMER_VIRTUAL |
用户态 CPU 时间 | SIGVTALRM |
ITIMER_PROF |
用户+内核态 CPU 时间 | SIGPROF |
注意:ITIMER_VIRTUAL 和 ITIMER_PROF 用于性能剖析(profiling)。ITIMER_REAL 和 alarm 共享 SIGALRM,同时只能用一个。微秒精度受系统时钟粒度限制(通常 4ms / 250Hz)。
sigprocmask — 检查/修改信号屏蔽字 链接到标题
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能:修改或获取当前线程的信号屏蔽字(blocked)。被屏蔽的信号不会递送,解除屏蔽后递送(信号不丢失)。
how 取值:
| 值 | 含义 |
|---|---|
SIG_BLOCK |
将 set 中的信号加入屏蔽字(并集) |
SIG_UNBLOCK |
将 set 中的信号从屏蔽字移除 |
SIG_SETMASK |
将屏蔽字设为 set |
注意:oldset 可传 NULL。SIGKILL 和 SIGSTOP 不能被阻塞。子进程通过 fork 继承父进程屏蔽字,exec 后屏蔽字保留(但 handler 重置为默认)。
sigset 操作函数 — 操作信号集 链接到标题
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
功能:sigset_t 不可跨平台直接用位运算操作,必须用以上函数。sigemptyset 清空,sigfillset 填满所有信号。
注意:信号集使用前必须用 sigemptyset 或 sigfillset 初始化,否则行为未定义。sigismember 返回 1 表示在集合中,0 表示不在,-1 出错。
慢速系统调用与信号 链接到标题
慢速系统调用指可能永久阻塞的调用:read(pipe/tty/网络)、write、open(FIFO)、pause、wait、accept、connect 等。
核心问题:慢速系统调用被信号中断后返回 -1,errno = EINTR。必须显式处理:
// 典型写法
do {
n = read(fd, buf, len);
} while (n == -1 && errno == EINTR);
SA_RESTART:设置后,被信号中断的慢速系统调用由内核自动重启,handler 中不需要手写重试。但 select、poll、sleep、connect 即使设置了 SA_RESTART 也不会自动重启——这是 POSIX 规定。
线程 链接到标题
线程概念 链接到标题
线程是进程中独立的执行流,同进程的线程共享地址空间、fd 表、信号处理设置、cwd 等。Linux 上线程由 clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | ...) 实现,内核用 task_struct 表示每个线程,tgid 相同的 task 属于同一进程。
线程间通信直接通过共享内存即可,无需 IPC。每个线程有独立的栈、
errno、信号屏蔽字。
pthread_create — 创建线程 链接到标题
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void *), void *arg);
功能:创建新线程执行 start_routine(arg)。成功返回 0。
注意:编译必须加 -pthread(同时链接 libpthread 并定义 _REENTRANT 等宏)。新线程的调度顺序不保证。attr 传 NULL 用默认属性(joinable + 默认栈大小)。start_routine 通过 return 或 pthread_exit 返回值。
pthread_exit — 线程退出 链接到标题
#include <pthread.h>
void pthread_exit(void *retval);
功能:终止调用线程,retval 作为返回值传给 pthread_join。不会影响进程中的其他线程。
注意:线程函数直接 return 等价于 pthread_exit(返回值)。不要在线程函数中 exit()——那会终止整个进程。不要返回指向线程栈的指针(线程退出后栈被回收)。
pthread_join — 等待线程终止并回收 链接到标题
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
功能:阻塞等待指定线程结束,获取其返回值。类似于进程的 waitpid。
注意:必须 join 或 detach 每个线程,否则资源泄漏(类似僵尸进程)。线程必须是 joinable 的(默认),已 detach 的线程不能 join。多个线程不能 join 同一个线程。
pthread_self — 获取本线程 ID 链接到标题
#include <pthread.h>
pthread_t pthread_self(void);
功能:返回调用线程的 ID,类似 getpid() 之于进程。
注意:pthread_t 是不透明类型,不要用 == 比较——用 pthread_equal()。不要手动解析 pthread_t 的值。
pthread_detach — 分离线程 链接到标题
#include <pthread.h>
int pthread_detach(pthread_t thread);
功能:将线程标记为 detached 状态。线程退出时内核自动回收其资源,无需 pthread_join 回收。
注意:已 detach 的线程不能再 join。pthread_detach(pthread_self()) 是常见写法让线程"自生自灭"。join 和 detach 必须二选一,否则资源泄漏。
pthread_cancel — 取消线程 链接到标题
#include <pthread.h>
int pthread_cancel(pthread_t thread);
功能:向指定线程发送取消请求。目标线程在到达取消点(cancellation point)时才终止。默认取消类型为 deferred(延迟取消)。
常见取消点:read、write、sleep、pthread_cond_wait、pthread_join、accept 等阻塞函数。
注意:纯计算循环无取消点,需手动 pthread_testcancel() 插入取消点。pthread_setcancelstate 可关闭取消(PTHREAD_CANCEL_DISABLE),pthread_setcanceltype 可设为异步取消(PTHREAD_CANCEL_ASYNCHRONOUS——危险,不推荐)。取消线程时应用 pthread_cleanup_push/pop 注册清理函数以释放锁和内存。
pthread_attr — 线程属性 链接到标题
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
功能:pthread_create 的第二个参数控制线程属性。先用 pthread_attr_init 初始化,设置后用 pthread_attr_destroy 销毁。
detachstate 取值:PTHREAD_CREATE_JOINABLE(默认)或 PTHREAD_CREATE_DETACHED(创建即分离,无需再调 pthread_detach)。
注意:pthread_attr_destroy 必须与 init 配对,否则内存泄漏。attr 还可设置栈大小(pthread_attr_setstacksize)、栈地址(pthread_attr_setstack)、调度策略(pthread_attr_setschedpolicy)等。
互斥锁 (mutex) 链接到标题
pthread_mutex_init / lock / unlock / trylock / destroy 链接到标题
#include <pthread.h>
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; // 静态初始化
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:互斥锁保证同一时刻只有一个线程进入临界区。lock 阻塞等待,trylock 立即返回(成功 0,失败 EBUSY)。
注意:
PTHREAD_MUTEX_INITIALIZER静态初始化等价于pthread_mutex_init(&m, NULL),无需 destroy。pthread_mutex_init动态初始化的锁用完后需pthread_mutex_destroy。- 必须
unlock配对lock,同一线程不能重复 lock(默认非递归锁)。 attr可设锁类型:PTHREAD_MUTEX_ERRORCHECK(检查死锁)、PTHREAD_MUTEX_RECURSIVE(递归锁)。
读写锁 (rwlock) 链接到标题
#include <pthread.h>
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
功能:允许多个读者并发访问,写者独占。rdlock 可多个线程同时持有,wrlock 写者独占。unlock 统一释放(无论读锁写锁)。
注意:写者优先还是读者优先由实现决定(可能导致写者饥饿)。大量写操作的场景读写锁不如互斥锁——读写锁内部开销更大。tryrdlock/trywrlock 不阻塞,失败返回 EBUSY。
条件变量 (cond) 链接到标题
#include <pthread.h>
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,
const struct timespec *abstime);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_destroy(pthread_cond_t *cond);
功能:条件变量用于线程间的事件通知。wait 原子操作:解锁 mutex → 睡眠等待 → 被唤醒后重新加锁。signal 唤醒一个等待者,broadcast 唤醒全部。
注意:
pthread_cond_wait调用前必须先 lock mutex,返回后 mutex 已被重新 lock。- 等待条件必须用
while而非if——防止虚假唤醒(spurious wakeup)。 pthread_cond_timedwait的abstime是绝对时间(gettimeofday+ offset 转timespec),不是相对秒数。signal后要不要立即unlock?先 signal 再 unlock 更高效(被唤醒线程不需再次睡眠)。
POSIX 信号量 (sem) 链接到标题
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
int sem_destroy(sem_t *sem);
功能:sem_wait(P 操作)对信号量减 1,若值为 0 则阻塞;sem_post(V 操作)加 1。pshared=0 表示线程间共享,pshared≠0 表示进程间共享(需放在共享内存中)。
注意:链接需加 -pthread。信号量和条件变量的关键区别:条件变量需要 mutex 配合,信号量自带计数;条件变量适合通知模式,信号量适合资源计数(如连接池)。sem_wait 可能被信号中断。
线程综合示例:生产者-消费者 链接到标题
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#define BUF_SIZE 5
int buffer[BUF_SIZE];
int count = 0, in = 0, out = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t not_full = PTHREAD_COND_INITIALIZER;
pthread_cond_t not_empty = PTHREAD_COND_INITIALIZER;
void *producer(void *arg) {
for (int i = 1; i <= 10; i++) {
pthread_mutex_lock(&mutex);
while (count == BUF_SIZE) // ① while 而非 if — 防虚假唤醒
pthread_cond_wait(¬_full, &mutex);
buffer[in] = i;
in = (in + 1) % BUF_SIZE;
count++;
printf("[producer] 生产 %d, 库存 %d\n", i, count);
pthread_cond_signal(¬_empty); // ② 通知消费者
pthread_mutex_unlock(&mutex); // ③ 先 signal 再 unlock 更高效
usleep(100000);
}
return NULL;
}
void *consumer(void *arg) {
for (int i = 1; i <= 10; i++) {
pthread_mutex_lock(&mutex);
while (count == 0) // ④ 必须 while
pthread_cond_wait(¬_empty, &mutex);
int val = buffer[out];
out = (out + 1) % BUF_SIZE;
count--;
printf("[consumer] 消费 %d, 库存 %d\n", val, count);
pthread_cond_signal(¬_full);
pthread_mutex_unlock(&mutex);
usleep(150000);
}
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, NULL, producer, NULL);
pthread_create(&t2, NULL, consumer, NULL);
pthread_join(t1, NULL); // ⑤ detach 或 join 必须二选一
pthread_join(t2, NULL);
return 0;
}
要点回顾:①
pthread_cond_wait前必须拿到 mutex;② 判断条件必用while防虚假唤醒;③ 先 signal 再 unlock 更优(被唤醒线程不需二次睡眠);④ 每个线程必须join或detach;⑤ 编译需加-pthread。
网络编程 链接到标题
字节序转换 — htonl / htons / ntohl / ntohs 链接到标题
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); // Host to Network Long (32 位)
uint16_t htons(uint16_t hostshort); // Host to Network Short (16 位)
uint32_t ntohl(uint32_t netlong); // Network to Host Long
uint16_t ntohs(uint16_t netshort); // Network to Host Short
功能:在主机字节序和网络字节序(大端)之间转换。网络传输中多字节整数必须用大端序。
注意:x86 是小端,htonl(1) → 0x01000000;大端机器上这些函数是空操作。端口和 IP 赋值前必须转换,接收后必须转回来。大端也叫"网络字节序"。
地址转换 — inet_pton / inet_ntop 链接到标题
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
功能:inet_pton(presentation to numeric)将 IP 字符串如 "192.168.1.1" 转为二进制(struct in_addr)。inet_ntop 做反向转换。af 取 AF_INET(IPv4)或 AF_INET6(IPv6)。
注意:inet_pton 返回 1=成功、0=格式无效、-1=af 不支持。inet_ntop 的 size 建议用 INET_ADDRSTRLEN(16)或 INET6_ADDRSTRLEN(46)。不要用已过时的 inet_addr/inet_ntoa(不支持 IPv6,非线程安全)。
socket — 创建套接字 链接到标题
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能:创建套接字,返回 fd,失败返回 -1。
参数:
| 参数 | 常见取值 |
|---|---|
domain |
AF_INET(IPv4)、AF_INET6(IPv6)、AF_UNIX(本地 Unix 域) |
type |
SOCK_STREAM(TCP,面向连接)、SOCK_DGRAM(UDP,无连接) |
protocol |
通常填 0(自动选择),也可显式指定 IPPROTO_TCP、IPPROTO_UDP |
注意:返回的 fd 默认为阻塞模式。需非阻塞时用 fcntl(fd, F_SETFL, O_NONBLOCK) 或创建时传入 SOCK_NONBLOCK 标志。
bind — 绑定地址到套接字 链接到标题
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:将 socket 绑定到指定的 IP 和端口。服务端必调,客户端可省略(由内核自动分配临时端口)。
注意:绑定特权端口(<1024)需 root。bind 失败常见原因——端口已被占用(EADDRINUSE)。设置 SO_REUSEADDR 可在 TIME_WAIT 后立即重用端口。
listen — 开始监听 链接到标题
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
功能:将 socket 标记为被动模式,准备接受连接。backlog 指定已完成连接队列(accept queue)的最大长度。
注意:内核维护两个队列——未完成握手队列(syn queue)和已完成握手队列(accept queue)。backlog 仅影响后者。/proc/sys/net/core/somaxconn 是系统范围的 backlog 上限。调用 listen 前必须先 bind。
accept — 接受连接 链接到标题
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:从 accept 队列中取出一个已完成的连接,返回新的 socket fd(用于与客户端通信)。addr 和 addrlen 可获取客户端地址(不关心可传 NULL)。
注意:返回的新 fd 和监听 fd 是两个不同的 fd。监听 fd 持续存在接受后续连接;新 fd 仅用于与该客户端通信,用完后 close。若 accept 队列为空且 socket 为阻塞模式,accept 会阻塞。
connect — 发起连接 链接到标题
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:客户端向服务端发起连接。TCP 下完成三次握手。成功返回 0,失败返回 -1。
注意:阻塞模式下 connect 超时由内核决定(通常约 20 秒)。非阻塞 socket 上 connect 立即返回 -1 且 errno = EINPROGRESS,之后用 select/poll/epoll 检测可写即可。连接失败后 socket 不可再 connect,需重新创建。
send / recv — TCP 收发数据 链接到标题
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:send 发送数据(等价于 write + flags),recv 接收数据(等价于 read + flags)。TCP 是流协议,没有消息边界。
常用 flags:
| flag | 含义 |
|---|---|
MSG_PEEK |
窥探数据,不将其从缓冲区移除 |
MSG_DONTWAIT |
本次调用非阻塞(等价于 O_NONBLOCK) |
MSG_WAITALL |
recv 阻塞直到收到 len 字节(仍可能被信号中断) |
注意:必须循环收发——TCP 不保证一次调用收/发完所有数据。和 read/write 一样,返回值可能少于请求量(部分读/部分写)。flags 一般传 0。
setsockopt — 设置套接字选项 链接到标题
#include <sys/types.h>
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
功能:设置 socket 的各种选项。level 为 SOL_SOCKET 表示通用选项,IPPROTO_TCP 表示 TCP 层选项。
常用选项:
| optname | 含义 |
|---|---|
SO_REUSEADDR |
允许重用本地地址(TIME_WAIT 后可立即 bind) |
SO_KEEPALIVE |
启用 TCP keep-alive 探测 |
SO_RCVBUF / SO_SNDBUF |
设置接收/发送缓冲区大小 |
TCP_NODELAY |
禁用 Nagle 算法(level 用 IPPROTO_TCP) |
注意:SO_REUSEADDR 必须在 bind 前设置。所有 TCP 服务端都应设置此选项,否则服务重启需等 TIME_WAIT(2MSL ≈ 60 秒)。
shutdown — 半关闭连接 链接到标题
#include <sys/socket.h>
int shutdown(int sockfd, int how);
功能:关闭 socket 的单向或双向通信,不释放 fd。close 关闭 fd 并减少引用计数(多进程共享时不会立即关闭连接),shutdown 直接操作连接无关引用计数。
how 取值:SHUT_RD(关读)、SHUT_WR(关写,对端收到 EOF)、SHUT_RDWR(全部关闭)。
注意:close vs shutdown——想立即给对方发 FIN 用 shutdown,想释放 fd 用 close。即使 dup 了 fd,shutdown 也会立即关闭连接。
select — I/O 多路复用 链接到标题
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
void FD_ZERO(fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
功能:同时监控多个 fd 的可读/可写/异常状态。返回就绪 fd 的总数,0 超时,-1 出错。
注意:
nfds=max(fd) + 1(不是 fd 个数,是最大 fd 号 + 1)。fd_set大小有限(默认 1024),大量连接用poll或epoll。select会修改传入的fd_set——每次调用前必须用FD_ZERO/FD_SET重新设置。- 被信号中断返回 -1 且
errno = EINTR(即使SA_RESTART也不会自动重启select)。
sockaddr 结构体用法 链接到标题
#include <netinet/in.h>
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr)); // ① 必须先清零
addr.sin_family = AF_INET;
addr.sin_port = htons(8080); // ② 端口用 htons
inet_pton(AF_INET, "192.168.1.1", &addr.sin_addr); // ③ IP 用 inet_pton
// 或者绑定所有本地 IP:
addr.sin_addr.s_addr = htonl(INADDR_ANY); // ④ INADDR_ANY 也需 htonl
bind(fd, (struct sockaddr *)&addr, sizeof(addr)); // ⑤ 必须强转
注意:
sockaddr_in定义在<netinet/in.h>,传给bind/connect/accept时必须强转为(struct sockaddr *)。必须先memset清零,否则sin_zero中的垃圾值会导致bind失败。
网络综合示例:TCP Echo 服务端 链接到标题
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/select.h>
int main() {
int sfd = socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // ① 端口复用
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr)); // ② 必须先清零
addr.sin_family = AF_INET;
addr.sin_port = htons(8080); // ③ 字节序转换
addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sfd, (struct sockaddr *)&addr, sizeof(addr));
listen(sfd, 5); // ④ backlog
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(sfd, &rfds); // ⑤ 监控监听 fd 的可读
int maxfd = sfd;
while (1) {
fd_set tmp = rfds; // ⑥ select 会修改 fd_set,需备份
int n = select(maxfd + 1, &tmp, NULL, NULL, NULL);
if (n <= 0) continue;
if (FD_ISSET(sfd, &tmp)) { // ⑦ 新连接到达
struct sockaddr_in cli;
socklen_t len = sizeof(cli);
int cfd = accept(sfd, (struct sockaddr *)&cli, &len);
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &cli.sin_addr, ip, sizeof(ip)); // ⑧ 地址转字符串
printf("[连接] %s:%d\n", ip, ntohs(cli.sin_port));
FD_SET(cfd, &rfds); // ⑨ 将客户端 fd 加入监控
if (cfd > maxfd) maxfd = cfd;
}
for (int fd = 0; fd <= maxfd; fd++) {
if (fd == sfd || !FD_ISSET(fd, &tmp)) continue;
char buf[1024];
ssize_t n = recv(fd, buf, sizeof(buf), 0); // ⑩ 接收
if (n <= 0) {
if (n == 0) printf("[断开]\n");
close(fd);
FD_CLR(fd, &rfds); // ⑪ 从监控集移除
} else {
send(fd, buf, n, 0); // ⑫ 回显
}
}
}
close(sfd);
return 0;
}
要点回顾:①
SO_REUSEADDR必须在bind之前设置;② 端口和地址赋值必须用htons/htonl;③select每次调用前需重建fd_set(因为会被修改);④nfds = maxfd + 1;⑤recv返回 0 表示对端关闭连接;⑥inet_ntop用于日志/调试输出 IP。
文件 I/O 链接到标题
open — 打开/创建文件 链接到标题
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
功能:返回文件描述符,失败返回 -1 并设置 errno。
常用 flags:O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_TRUNC, O_APPEND, O_NONBLOCK。
注意:O_CREAT 时必须给出第三个参数 mode(如 0644),否则权限不确定。mode 受 umask 影响。
close — 关闭文件描述符 链接到标题
#include <unistd.h>
int close(int fd);
功能:关闭 fd,释放内核资源。成功返回 0,失败返回 -1。
注意:一定要检查返回值——NFS 上的 close 可能失败(数据未刷盘)。多次 close 同一个 fd 是 bug。
read — 读文件 链接到标题
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
功能:从 fd 读最多 count 字节到 buf。返回实际读取字节数,0 表示 EOF,-1 出错。
注意:返回的字节数可以少于请求数(普通文件遇到 EOF、pipe/socket 部分数据到达、被信号中断)。必须循环读到期望的量。被信号中断时 errno = EINTR,见"慢速系统调用与信号"节。
write — 写文件 链接到标题
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
功能:向 fd 写最多 count 字节。返回实际写入字节数,-1 出错。
注意:和 read 一样,返回值可能少(磁盘满、信号中断)。写 pipe/socket 时可能写不满。同样需要循环写。
lseek — 移动文件偏移 链接到标题
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
功能:修改 fd 的文件偏移量。whence 取值:
SEEK_SET— 从文件头偏移offsetSEEK_CUR— 从当前位置偏移SEEK_END— 从文件末尾偏移
注意:对 pipe、socket、tty 调用 lseek 会失败(ESPIPE)。lseek 超过文件末尾再写入会产生"空洞文件"(hole,占元数据不占磁盘)。
truncate / ftruncate — 截断文件 链接到标题
#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
功能:将文件截断到指定长度。超出的数据丢失。truncate 用路径,ftruncate 用 fd(需要打开为可写)。
注意:不可逆操作。如果 length 大于原文件,文件被扩展(扩展部分填 0,形成空洞)。
文件描述符控制 链接到标题
fcntl — 文件描述符控制 链接到标题
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */);
功能:对 fd 执行各种操作:
F_GETFL/F_SETFL:获取/设置文件状态标志(如设为O_NONBLOCK)F_DUPFD:复制 fd(比 dup 更精确)F_GETFD/F_SETFD:获取/设置 fd 标志(目前只有FD_CLOEXEC)
注意:设置 O_NONBLOCK 等标志时,必须先 F_GETFL 再 按位或 后再 F_SETFL,不能直接覆盖,否则其他标志丢失。
dup / dup2 — 复制文件描述符 链接到标题
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
功能:复制 fd。新 fd 与旧 fd 指向同一内核文件表项(共享文件偏移和状态标志)。
dup — 返回可用最小 fd。
dup2 — 指定新 fd 号码;若 newfd 已打开则先关闭它。若 oldfd == newfd,直接返回 newfd,不关闭。
注意:dup2 的 close + dup 是原子的(这很重要,避免竞态)。二者不共享 FD_CLOEXEC 标志。常用于 fork+exec 前实现 I/O 重定向:dup2(fd, STDOUT_FILENO)。
文件元数据 链接到标题
stat / lstat / fstat — 获取文件信息 链接到标题
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);
功能:获取文件元数据(大小、权限、inode、时间戳等)。
stat vs lstat:如果 path 是符号链接,stat 返回链接目标的信息,lstat 返回链接自身的信息。
注意:struct stat 中时间字段(st_mtime 等)有的是 time_t,用 difftime() 比较。
link / unlink — 硬链接操作 链接到标题
#include <unistd.h>
int link(const char *oldpath, const char *newpath);
int unlink(const char *pathname);
link — 为现有文件创建硬链接(同一 inode 的别名)。 unlink — 删除目录项;当硬链接计数归零且无进程打开文件时,文件被真正删除。
注意:unlink 不能删目录(用 rmdir)。硬链接不能跨文件系统、不能链接目录(root 也不行)。unlink 不检查文件内容是否被占用,靠引用计数。
目录操作 链接到标题
opendir / closedir / readdir — 目录操作 链接到标题
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
struct dirent *readdir(DIR *dirp);
int closedir(DIR *dirp);
功能:打开目录、逐条读取目录项、关闭目录。readdir 返回 NULL 表示读完或出错(需检查 errno 区分)。
dirent 关键字段:d_name(文件名),d_ino(inode 号),d_type(文件类型,有些文件系统不支持)。
注意:readdir 返回的指针可能被下一次调用覆盖,不是线程安全的。目录读取顺序无保证。
进程间通信 (IPC) 链接到标题
pipe — 创建匿名管道 链接到标题
#include <unistd.h>
int pipe(int pipefd[2]);
功能:创建单向数据通道。pipefd[0] 读端,pipefd[1] 写端。成功返回 0。
注意:只能用于父子/兄弟进程(通过 fork 继承 fd)。读空 pipe 会阻塞到有数据或写端全部关闭。写满 pipe(通常 64KB)会阻塞。所有写端关闭后,read 返回 0(EOF)。所有读端关闭后,write 触发 SIGPIPE(默认动作是终止进程)。
mkfifo — 创建命名管道 (FIFO) 链接到标题
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
功能:创建有文件名的管道,任意进程可通过路径打开,不限于父子关系。mode 受 umask 限制。
注意:open FIFO 时,以 O_RDONLY 打开会阻塞直到有进程以写打开(反之亦然)。O_NONBLOCK 可避免阻塞。FIFO 数据不落盘——文件只是命名入口,内核中的缓冲区才存数据。
内存映射 链接到标题
mmap — 映射文件或匿名内存 链接到标题
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
功能:将文件内容或匿名内存映射到进程地址空间,返回映射区起始地址,失败返回 MAP_FAILED。
prot 取值:PROT_READ | PROT_WRITE | PROT_EXEC | PROT_NONE(组合使用)。
flags:
| flag | 含义 |
|---|---|
MAP_SHARED |
修改写回文件,其他映射同一文件的进程可见 |
MAP_PRIVATE |
COW——修改不写回文件,不影响其他进程 |
MAP_ANONYMOUS |
不关联文件(fd 传 -1),类似 malloc 大块内存 |
注意:offset 必须是页大小(sysconf(_SC_PAGE_SIZE) 即 4096)的整数倍。映射超过文件大小的区域在访问时收到 SIGBUS。mmap 比 read/write 少一次内核拷贝,大文件随机访问更高效。munmap 或进程结束时映射自动解除。
PCB(进程控制块)— task_struct 中与编程相关的重要成员
链接到标题
Linux 内核中,每个进程由一个 task_struct 结构体表示(定义在 <linux/sched.h>),即 PCB。内核调度、内存管理、信号处理等都依赖其中的字段。以下列出与系统编程关系最密切的成员:
进程标识 链接到标题
| 成员 | 含义 |
|---|---|
pid_t pid |
进程 ID(用户态 gettid() 返回值,每个线程唯一) |
pid_t tgid |
线程组 ID;主线程的 tgid == pid(用户态 getpid() 实际返回 tgid) |
pid_t ppid |
父进程 ID(getppid()) |
注意:
fork后子进程tgid == pid。getpid()返回tgid,gettid()返回内核pid,二者在线程场景下不同。
进程状态 链接到标题
| 成员 | 含义 |
|---|---|
volatile long state |
进程当前状态:TASK_RUNNING(0 — 运行中或就绪)、TASK_INTERRUPTIBLE(可中断睡眠)、TASK_UNINTERRUPTIBLE(不可中断睡眠,即 D 状态)、TASK_STOPPED、TASK_TRACED、EXIT_ZOMBIE(僵尸)、EXIT_DEAD |
int exit_state |
退出状态(僵尸 / 已死亡) |
int exit_code |
退出码(WEXITSTATUS 的来源) |
int exit_signal |
退出时发给父进程的信号(通常 SIGCHLD) |
编程要点:
ps看到的D状态就是TASK_UNINTERRUPTIBLE,通常进程卡在内核 IO 上。僵尸进程对应EXIT_ZOMBIE——进程已结束但父进程未wait(),PCB 未被回收。
调度相关 链接到标题
| 成员 | 含义 |
|---|---|
int prio / int static_prio / int normal_prio |
动态优先级 / 静态优先级(nice 值映射,100-139)/ 归一化优先级 |
unsigned int rt_priority |
实时优先级(SCHED_FIFO / SCHED_RR 策略用,1-99) |
unsigned int policy |
调度策略:SCHED_NORMAL(CFS)、SCHED_FIFO、SCHED_RR、SCHED_BATCH、SCHED_IDLE、SCHED_DEADLINE |
struct sched_entity se |
CFS 调度实体(包含 vruntime——虚拟运行时间,CFS 用红黑树选 vruntime 最小的进程) |
cpumask_t cpus_allowed |
进程允许运行的 CPU 集合(taskset / sched_setaffinity 控制) |
编程要点:用户态
nice()/setpriority()修改static_prio;sched_setscheduler()修改policy和rt_priority。/proc/[pid]/sched可以查看调度信息。
文件与 IO 链接到标题
| 成员 | 含义 |
|---|---|
struct fs_struct *fs |
文件系统信息:umask、根目录(/proc/[pid]/root)、当前工作目录(cwd) |
struct files_struct *files |
打开文件表(fdtable 数组);每个 fd 对应一个 struct file 指针 |
struct nsproxy *nsproxy |
命名空间代理(mnt、uts、ipc、pid、net、cgroup、time 命名空间) |
编程要点:
fork后父子共享files_struct(引用计数 +1),所以 fd 在父子间共享偏移量——除非设置了FD_CLOEXEC(exec时关闭)。clone(CLONE_FILES)决定是否共享文件表。
内存管理 链接到标题
| 成员 | 含义 |
|---|---|
struct mm_struct *mm |
进程地址空间描述符(代码段、数据段、堆、栈、mmap 区域等) |
struct mm_struct *active_mm |
当前实际使用的 mm;内核线程没有自己的 mm,借用前一个进程的 active_mm |
编程要点:
mm->mmap是 VMA(vm_area_struct)链表的头,/proc/[pid]/maps的内容就来自它。mm->arg_start/arg_end指向命令行参数;mm->env_start/env_end指向环境变量。线程可通过clone(CLONE_VM)共享mm_struct。
信号处理 链接到标题
| 成员 | 含义 |
|---|---|
struct signal_struct *signal |
信号描述符(线程组共享):挂起信号集、资源限制(rlim 数组)等 |
struct sighand_struct *sighand |
信号处理函数表(struct k_sigaction 数组,每个信号一个 handler),引用计数 |
sigset_t blocked |
当前阻塞(屏蔽)的信号集 |
sigset_t pending |
本线程私有的挂起信号 |
编程要点:
signal->rlim是struct rlimit数组,用户态getrlimit/setrlimit修改的就是它(如RLIMIT_NOFILE、RLIMIT_STACK)。信号 handler 通过sighand设置,/proc/[pid]/status中SigCgt/SigBlk/SigIgn来自这些字段。
时间与统计 链接到标题
| 成员 | 含义 |
|---|---|
u64 utime / u64 stime |
用户态 CPU 时间 / 内核态 CPU 时间(times() 和 /proc/[pid]/stat 的来源) |
u64 start_time |
进程启动时间(/proc/[pid]/stat 第 22 列的 boot-based 时间戳) |
进程关系 链接到标题
| 成员 | 含义 |
|---|---|
struct task_struct __rcu *real_parent |
真实父进程(fork 的那一个) |
struct task_struct __rcu *parent |
当前父进程(收到 SIGCHLD 的那个,ptrace 附加时会改变) |
struct list_head children |
子进程链表 |
struct list_head sibling |
兄弟进程链表(同一父进程的子进程链表节点) |
编程要点:
real_parent != parent最常见的情形是进程被调试器ptrace附加——调试器成为parent。
其他杂项 链接到标题
| 成员 | 含义 |
|---|---|
struct tty_struct *tty |
控制终端(/proc/[pid]/stat 第 7 字段的来源) |
kuid_t loginuid |
审计用的登录 UID |
int link_count |
硬链接计数(防止 task_struct 被过早释放) |
struct task_struct *group_leader |
线程组 leader 指针 |
struct cred *cred |
进程凭证:uid、gid、euid、egid、suid、sgid、fsuid、fsgid、cap_effective 等 |
char comm[TASK_COMM_LEN] |
进程名(最多 15 字符,prctl(PR_SET_NAME) / /proc/[pid]/comm) |
unsigned long nvcsw / nivcsw |
自愿/非自愿上下文切换计数(/proc/[pid]/status 中可见) |
struct rlimit rlim[RLIM_NLIMITS] |
进程资源限制(getrlimit/setrlimit 操作的对象) |
编程要点:
cred结构中uid/euid/suid是setuid机制的核心——fork继承、exec视 SUID 位变化。rlim中RLIMIT_NOFILE即ulimit -n,RLIMIT_CORE控制 core dump 大小。
会话与进程组 链接到标题
| 成员 | 含义 |
|---|---|
pid_t pgrp |
进程组 ID(getpgrp()) |
pid_t session |
会话 ID(getsid()) |
编程要点:
setsid()创建新会话并成为 session leader(脱离控制终端的关键一步)。shell 的作业控制依赖进程组——Ctrl+C发给前台进程组的所有进程,kill(-pgid, sig)发给整个进程组。
总结:从用户态编程角度看,task_struct 中我们直接/间接打交道的字段就是 PID 系列(fork/exec/wait 的基石)、files(每个 fd 追到这里)、mm(mmap、brk、地址空间的真相)、信号三件套(blocked/pending/sighand)、调度字段(nice/sched_setscheduler 的真实作用点)。理解这些字段,/proc 下的大部分文件、ps/top/strace 输出背后的含义便一目了然。
常见头文件总汇 链接到标题
#include <unistd.h> // read, write, close, lseek, dup, dup2, truncate, ftruncate,
// link, unlink, getopt, fork, exec, pipe, alarm, sleep
#include <sys/wait.h> // wait, waitpid
#include <fcntl.h> // open, fcntl (O_*, F_* 宏)
#include <sys/types.h> // off_t, ssize_t, mode_t
#include <sys/stat.h> // stat, lstat, fstat, mkfifo, struct stat, mode 宏
#include <dirent.h> // opendir, readdir, closedir, struct dirent
#include <signal.h> // signal, sigaction, kill, sigprocmask, sigemptyset,
// sigfillset, sigaddset, sigdelset, sigismember
#include <sys/time.h> // setitimer, struct itimerval
#include <sys/mman.h> // mmap, munmap, mprotect
#include <pthread.h> // pthread_create, pthread_exit, pthread_join, pthread_self,
// pthread_detach, pthread_cancel, pthread_mutex_*, pthread_cond_*,
// pthread_rwlock_*, pthread_attr_*
#include <semaphore.h> // sem_init, sem_wait, sem_post, sem_destroy
#include <string.h> // strerror, memset
/* ---------- 网络编程 ---------- */
#include <sys/socket.h> // socket, bind, listen, accept, connect, send, recv,
// setsockopt, shutdown
#include <sys/select.h> // select, FD_ZERO, FD_SET, FD_CLR, FD_ISSET
#include <netinet/in.h> // struct sockaddr_in, INADDR_ANY
#include <arpa/inet.h> // htonl, htons, ntohl, ntohs, inet_pton, inet_ntop