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_CLOEXECfork 在大型进程中开销大(页表复制),高并发场景可考虑 vforkclone。子进程只复制调用线程,其他线程不复制。

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

注意execFD_CLOEXEC 标记的 fd 会被关闭,其余的保留。execlpexecvp 如果 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 的子进程

常用 optionsWNOHANG(非阻塞)、WUNTRACED(也返回已停止的子进程)、WCONTINUED(也返回被 SIGCONT 恢复的子进程)。

状态检查宏(传入 wstatus):

含义
WIFEXITED(status) 正常退出 → 用 WEXITSTATUS(status) 取退出码
WIFSIGNALED(status) 被信号杀死 → 用 WTERMSIG(status) 取信号
WIFSTOPPED(status) 被信号暂停 → 用 WSTOPSIG(status) 取信号
WIFCONTINUED(status) SIGCONT 恢复执行

注意必须 wait() 回收子进程,否则产生僵尸进程。SIGCHLD 默认忽略,子进程仍产生僵尸(除非显式设为 SIG_IGNSA_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_IGNSIGCHLD 的效果)
SA_NODEFER handler 执行期间不自动屏蔽本信号(默认会屏蔽)

注意sa_mask 在 handler 执行期间临时加入进程屏蔽字,handler 返回后自动还原。SIGKILLSIGSTOP 不能自定义处理。

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) 取消定时。

注意只能设一个定时器alarmsleep/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_VIRTUALITIMER_PROF 用于性能剖析(profiling)。ITIMER_REALalarm 共享 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 可传 NULLSIGKILLSIGSTOP 不能被阻塞。子进程通过 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 填满所有信号。

注意:信号集使用前必须sigemptysetsigfillset 初始化,否则行为未定义。sigismember 返回 1 表示在集合中,0 表示不在,-1 出错。


慢速系统调用与信号 链接到标题

慢速系统调用指可能永久阻塞的调用:read(pipe/tty/网络)、writeopen(FIFO)、pausewaitacceptconnect 等。

核心问题:慢速系统调用被信号中断后返回 -1errno = EINTR。必须显式处理:

// 典型写法
do {
    n = read(fd, buf, len);
} while (n == -1 && errno == EINTR);

SA_RESTART:设置后,被信号中断的慢速系统调用由内核自动重启,handler 中不需要手写重试。但 selectpollsleepconnect 即使设置了 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 等宏)。新线程的调度顺序不保证。attrNULL 用默认属性(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

注意必须 joindetach 每个线程,否则资源泄漏(类似僵尸进程)。线程必须是 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(延迟取消)。

常见取消点readwritesleeppthread_cond_waitpthread_joinaccept 等阻塞函数。

注意:纯计算循环无取消点,需手动 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_timedwaitabstime绝对时间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(&not_full, &mutex);
        buffer[in] = i;
        in = (in + 1) % BUF_SIZE;
        count++;
        printf("[producer] 生产 %d, 库存 %d\n", i, count);
        pthread_cond_signal(&not_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(&not_empty, &mutex);
        int val = buffer[out];
        out = (out + 1) % BUF_SIZE;
        count--;
        printf("[consumer] 消费 %d, 库存 %d\n", val, count);
        pthread_cond_signal(&not_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 更优(被唤醒线程不需二次睡眠);④ 每个线程必须 joindetach;⑤ 编译需加 -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 做反向转换。afAF_INET(IPv4)或 AF_INET6(IPv6)。

注意inet_pton 返回 1=成功、0=格式无效、-1=af 不支持。inet_ntopsize 建议用 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_TCPIPPROTO_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(用于与客户端通信)。addraddrlen 可获取客户端地址(不关心可传 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 的各种选项。levelSOL_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),大量连接用 pollepoll
  • 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

常用 flagsO_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_TRUNC, O_APPEND, O_NONBLOCK

注意O_CREAT 时必须给出第三个参数 mode(如 0644),否则权限不确定。modeumask 影响。

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 — 从文件头偏移 offset
  • SEEK_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() 比较。

#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);

功能:创建有文件名的管道,任意进程可通过路径打开,不限于父子关系。modeumask 限制。

注意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)的整数倍。映射超过文件大小的区域在访问时收到 SIGBUSmmapread/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 == pidgetpid() 返回 tgidgettid() 返回内核 pid,二者在线程场景下不同。

进程状态 链接到标题

成员 含义
volatile long state 进程当前状态:TASK_RUNNING(0 — 运行中或就绪)、TASK_INTERRUPTIBLE(可中断睡眠)、TASK_UNINTERRUPTIBLE(不可中断睡眠,即 D 状态)、TASK_STOPPEDTASK_TRACEDEXIT_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_FIFOSCHED_RRSCHED_BATCHSCHED_IDLESCHED_DEADLINE
struct sched_entity se CFS 调度实体(包含 vruntime——虚拟运行时间,CFS 用红黑树选 vruntime 最小的进程)
cpumask_t cpus_allowed 进程允许运行的 CPU 集合(taskset / sched_setaffinity 控制)

编程要点:用户态 nice() / setpriority() 修改 static_priosched_setscheduler() 修改 policyrt_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_CLOEXECexec 时关闭)。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->rlimstruct rlimit 数组,用户态 getrlimit/setrlimit 修改的就是它(如 RLIMIT_NOFILERLIMIT_STACK)。信号 handler 通过 sighand 设置,/proc/[pid]/statusSigCgt/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 进程凭证:uidgideuidegidsuidsgidfsuidfsgidcap_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/suidsetuid 机制的核心——fork 继承、exec 视 SUID 位变化。rlimRLIMIT_NOFILEulimit -nRLIMIT_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 追到这里)、mmmmapbrk、地址空间的真相)、信号三件套(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