基础编程规范

命令行参数处理

下面这行代码通常在程序的“帮助信息”或“用法提示”中出现,用于告诉用户如何正确运行程序。

1
printf("Usage: %s <file>\n", argv[0]);
  • 参数占位符规范:

    • < > 表示必填参数(如 <file> 需替换为实际文件路径)
    • [ ] 表示可选参数(如 [options]
  • 示例执行流程:

    1
    2
    ./program /path/to/file  # 正确用法
    ./program # 错误:缺少必要参数

系统手册(man)

手册章节结构

man(manual 的缩写)是 Linux/Unix 系统中用于查看命令、函数、配置文件等手册页的核心工具。

章节 内容类型 示例命令 典型内容
1 用户命令 man 1 ls 文件列表操作
2 系统调用 man 2 fork 进程创建机制
3 库函数 man 3 printf 格式化输出函数
4 设备文件 man 4 tty 终端设备接口
5 配置文件格式 man 5 passwd 用户账户文件结构
8 系统管理命令 man 8 ifconfig 网络接口配置

实用技巧

1
2
man -k keyword    # 搜索关键字相关手册
man -f command # 显示命令的简短描述

文件描述符(File Descriptor)

分配机制

  • 规则:返回当前进程未使用的最小整数
  • 特殊描述符:
    • 0:标准输入(STDIN_FILENO
    • 1:标准输出(STDOUT_FILENO)
    • 2:标准错误(STDERR_FILENO

成功调用 open() 时,返回的文件描述符是当前进程 未使用的最小编号。最小编号分配策略:若当前打开的 fd 是 0、1、2、5,新分配的会是 3(因为 3 是未使用的最小整数)。

示例代码

1
2
3
4
5
6
7
8
9
10
#include <fcntl.h>
#include <unistd.h>

int main() {
int fd = open("data.txt", O_RDONLY); // 典型返回3(假设0-2已被占用)
char buffer[128];
read(fd, buffer, sizeof(buffer));
close(fd);
return 0;
}

设备控制(ioctl)

核心功能

Linux/Unix 系统中的一个设备控制函数,用于与设备驱动程序进行交互,执行无法通过标准文件操作(如 read/write)实现的特定操作。

1
ioctl(fd, FBIOGET_VSCREENINFO, &fb_info);
  • 作用:与设备驱动交互的特殊操作
  • 典型应用:
    • 获取帧缓冲设备信息(分辨率/色深)
    • 配置串口参数(波特率/数据位)
    • 控制摄像头参数(曝光/对焦)

参数解析

参数 作用
fd 设备文件描述符
cmd 设备特定命令(如FBIOGET_*
arg 指向参数结构的指针

内存映射(mmap)

mmap(Memory Map,内存映射)是 Unix/Linux 系统提供的一种 将文件或设备直接映射到进程内存空间 的系统调用。它允许程序通过读写内存的方式操作文件或硬件资源,避免了频繁的 read/write 调用,从而提升性能。

系统调用原型

1
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

典型应用场景

  1. 帧缓冲设备映射:

    1
    unsigned char *fb = mmap(NULL, screen_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd_fb, 0);
  2. 大文件高效IO:

    1
    2
    int fd = open("large_file.bin", O_RDONLY);
    void *data = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, fd, 0);

返回值:
成功:返回映射区域的起始地址(void * 类型,此处强制转换为 unsigned char *)。
失败:返回 MAP_FAILED(即 (void *) -1)。

参数详解

参数 可选值 说明
prot PROT_READ/PROT_WRITE 内存页保护标志
flags MAP_SHARED/MAP_PRIVATE 映射修改的可见性

进程管理

fork() 工作机制

用于创建一个与父进程几乎完全相同的子进程。

1
2
3
4
5
6
pid_t pid = fork();
switch(pid) {
case -1: /* 错误处理 */ break;
case 0: /* 子进程代码 */ break;
default: /* 父进程代码 */ break;
}

返回值:
成功时:
父进程:返回子进程的 PID(正整数)。
子进程:返回 0。
失败时:返回 -1(比如系统资源不足)。

fork() 的执行过程

父进程调用 fork() 后,内核会创建一个几乎完全相同的子进程:
代码段、数据段、堆栈、打开的文件描述符等都会被复制(但实际采用 “写时复制” (Copy-On-Write, COW) 优化)。
子进程从 fork() 返回处继续执行(而不是从头开始)。
父进程和子进程并行运行,谁先执行由调度器决定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <unistd.h>

int main() {
pid_t pid = fork(); // 创建子进程

if (pid == -1) {
perror("fork failed");
return -1;
} else if (pid == 0) {
// 子进程代码
printf("Child process (PID=%d), parent PID=%d\n", getpid(), getppid());
} else {
// 父进程代码
printf("Parent process (PID=%d), child PID=%d\n", getpid(), pid);
}

return 0;
}

僵尸进程处理

signal(SIGCHLD, SIG_IGN); 是一个 信号处理设置,用于告诉操作系统内核:当前进程对子进程的终止状态不感兴趣,内核应自动清理子进程的残留资源(避免僵尸进程)。

1
2
3
signal(SIGCHLD, SIG_IGN);  // 自动回收子进程
// 或
while(waitpid(-1, NULL, WNOHANG) > 0); // 非阻塞回收

网络编程(Socket)

套接字的核心作用

套接字(Socket)是 应用层与传输层之间的抽象接口,主要作用包括:

建立网络连接
作为通信的端点,支持不同主机(或同一主机)上的进程间数据传输。
例如:客户端与服务端的 TCP 连接、UDP 数据报发送。
协议封装
封装底层协议细节(如 TCP/IP 或 UDP/IP),开发者无需直接处理网络包的分组、校验、重传等。
多协议支持
通过参数指定协议族(如 IPv4/IPv6)、传输类型(如面向连接的 TCP 或无连接的 UDP)。

步骤 服务端(接电话的人) 客户端(打电话的人) 对应函数
1 准备通信设备(买一部手机) 准备通信设备(买一部手机) socket()
2 绑定身份(插卡固定号码) 无需绑定(系统自动分配端口) bind()(仅服务端)
3 等待连接(开机并等待来电) 主动发起连接(拨打对方号码) listen() + accept()(服务端)
connect()(客户端)
4 数据传输(接听电话后说话/听对方) 数据传输(接通后说话/听对方) send() / recv()
5 挂断(挂断电话) 挂断(挂断电话) close()

socket() 的底层工作

(1) 资源分配

创建套接字描述符

操作系统内核分配一个 文件描述符(类似打开文件时的 fd),用于唯一标识这个套接字。
例如:调用 int sockfd = socket(AF_INET, SOCK_STREAM, 0); 返回的 sockfd 可能是 3(假设当前进程已占用 0,1,2)。
内核数据结构初始化

内核为该套接字创建关键数据结构(不同系统实现不同,例如 Linux 的 struct socket):

1
2
3
4
struct socket {
int type; // 如 SOCK_STREAM(TCP)或 SOCK_DGRAM(UDP)
struct sock *sk; // 指向协议栈相关的核心结构(含缓冲区、状态等)
};

同时初始化关联的 发送缓冲区 和 接收缓冲区(用于临时存储待发送/接收的数据)。

(2) 协议栈绑定

确定通信协议
根据参数(如 SOCK_STREAM)绑定到 TCP/IP 协议栈,后续所有操作(如 connect()、send())均通过协议栈处理。
如果是 UDP(SOCK_DGRAM),则绑定到无连接的协议栈。

(3) 设置默认属性

套接字初始化为 主动模式(可调用 connect() 发起连接,适用于客户端)。
默认是 阻塞模式(调用 recv() 等函数时,若数据未就绪,进程会休眠等待)。

线程编程

1
/tmp/ccQzThAV.o: In function `main': Pthread_Text3.c:(.text+0xa3): undefined reference to `pthread_create'

在Linux/Unix系统下进行多线程编程时,编译时需要显式链接-lpthread(即POSIX Threads库)

1
gcc -o thread_07 ./Pthread_Text7.c  -lpthread

相关函数

pthread_join() :

POSIX 线程(Pthreads)库中的一个核心函数,用于阻塞当前线程,等待指定的目标线程终止,并回收其资源。

pthread_cancel():

POSIX 线程(Pthreads)库中的一个函数,用于请求取消(终止)指定的线程。它提供了一种协作式线程终止机制,允许一个线程向另一个线程发送取消请求,但实际终止的时机取决于目标线程的取消状态和类型。

核心作用
发送取消请求:向目标线程发送终止请求,但不会立即强制终止线程。
协作式终止:目标线程是否响应取消请求,取决于其取消状态(是否允许被取消)和取消类型(延迟或异步取消)。
资源清理:若线程响应取消请求,会执行清理函数(通过 pthread_cleanup_push 注册)后再终止。

pthread_mutex_t mutex

作用:声明一个互斥锁(mutex)变量,类型为 pthread_mutex_t。
pthread_mutex_lock(&mutex);
作用
尝试对互斥锁加锁:
如果锁未被其他线程持有,则当前线程成功获取锁,继续执行。
如果锁已被其他线程持有,则当前线程阻塞,直到锁被释放。

pthread_mutex_unlock(&mutex);

作用
释放已持有的互斥锁,允许其他线程获取该锁。

ret = pthread_mutex_init(&mutex,NULL);

作用
主函数中动态初始化互斥锁,第二个参数为 NULL 时使用默认属性。

pthread_mutex_destroy(&mutex);

作用
主函数中销毁互斥锁,释放相关资源。

信号量(Semaphore)是线程/进程同步的重要工具,用于控制对共享资源的访问或实现线程间的协作。

sem_init()

初始化信号量

1
int sem_init(sem_t *sem, int pshared, unsigned int value);

功能:初始化一个无名信号量。

sem_wait()

1
2
int sem_wait(sem_t *sem);
sem_wait(&sem);

功能:
如果信号量值 > 0,则减 1 并立即返回。
如果信号量值 = 0,则线程阻塞,直到信号量值变为正数。
返回值:成功返回 0,失败返回 -1(如被信号中断)。

sem_post()

释放(V操作)

1
int sem_post(sem_t *sem);

功能:将信号量值加 1,并唤醒等待该信号量的线程。
返回值:成功返回 0,失败返回 -1。

sem_trywait()

非阻塞等待

sem_destroy()

销毁信号量

/dev/tty*和其他

在 Linux 系统中,/dev/tty* 是一系列与终端(Terminal)相关的设备文件,它们代表了不同的终端类型和功能。以下是详细的分类和解释:

设备文件 类型 主要用途
/dev/ttyS0 物理串行终端 连接硬件设备(如调制解调器)
/dev/tty1~tty6 虚拟终端 本地多用户文本界面(Ctrl+Alt+F1~F6切换)
/dev/pts/0 伪终端 图形终端或远程SSH会话
/dev/tty 当前终端 进程获取自己的控制终端
/dev/console 系统控制台 内核输出和紧急消息

/dev/console 是 Linux 系统中的 系统控制台设备,用于内核和系统级消息的输出,同时也是系统启动过程中的主要交互终端。

行规程(Line Discipline)

Linux TTY(终端)子系统的核心组件之一,负责管理终端设备的数据流,包括:
输入/输出处理(缓冲、回显、转换)
特殊字符解释(如 Ctrl+C 终止进程)
协议封装(如串口通信的 SLIP 或 PPP)

行规程的作用
行规程位于 TTY 驱动层 和 用户进程 之间,对数据流进行中间处理:

用户进程 (e.g., Bash) ←→ 行规程 (Line Discipline) ←→ TTY 驱动 (UART/PTY) ←→ 硬件/网络
主要功能包括:

功能 说明
行缓冲 将用户输入按行缓冲(直到按下 Enter)。
回显(Echo) 将用户输入的字符显示到终端(可关闭,如密码输入)。
特殊字符处理 解释 Ctrl+C(中断)、Ctrl+Z(挂起)、Backspace(删除)等。
字符转换 如将 \r(回车)转换为 \n(换行),或处理 Unicode 编码。
流量控制 支持 XON/XOFF(软件流控)或硬件流控(RTS/CTS)。
协议封装 将原始字节流封装为 SLIP/PPP 等协议(用于串口拨号或嵌入式通信)。

SMBus(System Management Bus)详解

SMBus(System Management Bus)是由 Intel 在 1995 年定义的一种 两线制串行总线,主要用于 低速率系统管理通信,如:

电源管理(PMBus)
传感器监控(温度、电压、风扇)
智能电池管理
嵌入式设备配置
SMBus 基于 I²C(Inter-Integrated Circuit)协议,但增加了额外的规范和限制,使其更适合 系统管理 任务。

特性 SMBus I²C
时钟速度 10 kHz ~ 100 kHz(标准) 标准模式:100 kHz,最高 5 MHz
电压电平 3.3V(兼容 5V) 1.8V ~ 5V(取决于设备)
超时机制 强制超时(35ms 时钟超时) 无强制超时
ACK/NACK 规则 更严格(某些情况必须 ACK) 较灵活
协议扩展 支持 Packet Error Checking (PEC) 无内置校验机制
用途 系统管理(电源、传感器、电池) 通用外设通信(EEPROM、LCD)

volatile 使用场景

C/C++ 中的一个关键字,用于告诉编译器不要优化对该变量的访问,确保每次读写都直接操作内存(而非寄存器缓存)。它在嵌入式系统和多线程编程中尤为重要。

1
volatile int sensor_value;  // 防止编译器优化
  • 适用情况:
    • 内存映射硬件寄存器
    • 多线程共享变量
    • 信号处理程序修改变量