💖💖⚡️⚡️专栏:C高手编程-面试宝典/技术手册/高手进阶⚡️⚡️💖💖
「C高手编程」专栏融合了作者十多年的C语言开发经验,汇集了从基础到进阶的关键知识点,是不可多得的知识宝典。如果你是即将毕业的学生,面临C语言的求职面试,本专栏将帮助你扎实地掌握核心概念,轻松应对笔试与面试;如果你已有两三年的工作经验,专栏中的内容将补充你在实践中可能忽略的新技术和技巧;而对于资深的C语言程序员,这里也将是一本实用的技术备查手册,提供全面的知识回顾与更新。无论处在哪个阶段,「C高手编程」都能助你一臂之力,成为C语言领域的行家里手。
概述
本章深入探讨了C语言中的进程编程和进程间通信(IPC)的概念、创建与管理机制,以及不同类型的IPC机制。通过本章的学习,读者将能够理解进程编程的基本原理,并能在实际编程中正确地运用这些概念。
1. 进程的基本概念
1.1 进程定义
进程是程序在一个数据集合上的运行过程,是系统进行资源分配和调度的基本单位。每个进程都有一个唯一的进程标识符(PID)和一个父进程标识符(PPID),除非它是初始进程。
1.2 进程属性
- 进程ID (PID):标识进程的唯一编号。
- 父进程ID (PPID):创建该进程的父进程的ID。
- 进程状态:运行、就绪、等待、终止等。
- 进程优先级:影响进程调度的顺序。
- 进程资源使用情况:CPU时间、内存使用等。
- 进程工作目录:进程当前的工作目录。
- 环境变量:进程的环境变量列表。
- 命令行参数:进程启动时传递给它的命令行参数。
1.3 进程控制
- 创建:使用
fork
函数创建子进程。 - 等待:使用
wait
和waitpid
函数等待子进程结束。 - 终止:使用
exit
和_exit
函数终止进程。
2. 进程控制
2.1 创建进程
2.1.1 fork
函数
- 函数原型:
pid_t fork(void);
- 头文件:
<unistd.h>
- 参数:无。
- 返回值:在父进程中返回子进程的PID,在子进程中返回0。如果调用失败,则返回-1。
- 示例
#include <unistd.h>
#include <stdio.h>int main() {pid_t pid = fork();if (pid < 0) {perror("Fork failed");return 1;} else if (pid == 0) {printf("Child process PID: %d\n", getpid());} else {printf("Parent process PID: %d, Child PID: %d\n", getpid(), pid);}return 0;
}
2.2 终止进程
2.2.1 exit
函数
- 函数原型:
void exit(int status);
- 头文件:
<stdlib.h>
- 参数:进程退出状态。
- 示例
#include <stdlib.h>
#include <stdio.h>int main() {printf("Exiting with status 0\n");exit(0);return 0; // This line will never be reached.
}
2.2.2 _exit
函数
- 函数原型:
void _exit(int status);
- 头文件:
<unistd.h>
- 参数:进程退出状态。
- 说明:
_exit
函数是直接终止进程而不进行任何清理工作的版本,适用于子进程。 - 示例
#include <unistd.h>
#include <stdio.h>int main() {printf("Child process exiting\n");_exit(0);return 0; // This line will never be reached.
}
2.3 等待进程
2.3.1 wait
函数
- 函数原型:
pid_t wait(int *status);
- 头文件:
<sys/wait.h>
- 参数:子进程退出状态。
- 返回值:子进程的PID,如果没有子进程或出错则返回-1。
- 示例
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>int main() {pid_t pid = fork();if (pid < 0) {perror("Fork failed");return 1;} else if (pid == 0) { // 子进程printf("Child process exiting\n");_exit(0);} else { // 父进程int status;pid_t wpid = wait(&status);if (wpid == -1) {perror("Wait failed");return 1;}printf("Child process exited with status %d\n", WEXITSTATUS(status));}return 0;
}
2.3.2 waitpid
函数
- 函数原型:
pid_t waitpid(pid_t pid, int *status, int options);
- 头文件:
<sys/wait.h>
- 参数:特定子进程PID、子进程退出状态、选项。
- 返回值:子进程的PID,如果没有子进程或出错则返回-1。
- 示例
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>int main() {pid_t pid = fork();if (pid < 0) {perror("Fork failed");return 1;} else if (pid == 0) { // 子进程printf("Child process exiting\n");_exit(0);} else { // 父进程int status;pid_t wpid = waitpid(pid, &status, 0);if (wpid == -1) {perror("Waitpid failed");return 1;}printf("Child process exited with status %d\n", WEXITSTATUS(status));}return 0;
}
3. 进程间通信(IPC)
3.1 管道 (Pipes)
3.1.1 简介
管道是一种简单的进程间通信机制,它允许两个进程之间进行单向的数据传输。管道由一对特殊文件描述符组成,一个用于写入数据(写端),另一个用于读取数据(读端)。
3.1.2 函数原型
pipe
函数- 函数原型:
int pipe(int pipefd[2]);
- 头文件:
<unistd.h>
- 参数:管道描述符数组。
- 返回值:成功返回0,失败返回-1。
- 说明:创建一个管道,并返回一个包含两个文件描述符的数组,第一个元素为读端,第二个元素为写端。
- 函数原型:
3.1.3 示例
#include <unistd.h>
#include <stdio.h>int main() {int pipefd[2];if (pipe(pipefd) == -1) {perror("Pipe creation failed");return 1;}pid_t pid = fork();if (pid < 0) {perror("Fork failed");return 1;} else if (pid == 0) { // 子进程close(pipefd[0]); // 关闭读端char message[] = "Hello, world!";write(pipefd[1], message, strlen(message) + 1);close(pipefd[1]);_exit(0);} else { // 父进程close(pipefd[1]); // 关闭写端char buffer[100];read(pipefd[0], buffer, sizeof(buffer));printf("Received message: %s\n", buffer);close(pipefd[0]);}return 0;
}
3.2 消息队列 (Message Queues)
3.2.1 简介
消息队列是一种更为复杂的IPC机制,允许多个进程通过队列交换数据。消息队列支持多条消息的存储和检索,并且每个消息都可以有自己的类型标签,使得接收者可以根据类型选择性地接收消息。
3.2.2 函数原型
-
msgget
函数- 函数原型:
int msgget(key_t key, int flag);
- 头文件:
<sys/msg.h>
- 参数:键值、标志位。
- 返回值:消息队列标识符,如果出错返回-1。
- 说明:创建或获取一个消息队列。
- 函数原型:
-
msgsnd
函数- 函数原型:
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
- 头文件:
<sys/msg.h>
- 参数:消息队列标识符、消息结构体指针、消息长度、标志位。
- 返回值:成功返回0,失败返回-1。
- 说明:向消息队列发送一条消息。
- 函数原型:
-
msgrcv
函数- 函数原型:
int msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
- 头文件:
<sys/msg.h>
- 参数:消息队列标识符、消息结构体指针、最大接收消息长度、消息类型、标志位。
- 返回值:成功返回接收的消息长度,失败返回-1。
- 说明:从消息队列接收一条消息。
- 函数原型:
-
msgctl
函数- 函数原型:
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
- 头文件:
<sys/msg.h>
- 参数:消息队列标识符、命令、缓冲区。
- 返回值:成功返回0,失败返回-1。
- 说明:执行各种消息队列控制操作,例如删除消息队列。
- 函数原型:
3.2.3 示例
#include <sys/msg.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>#define MSGKEY 1234typedef struct {long mtype;char mtext[50];
} my_msg;int main() {key_t key = MSGKEY;int msqid = msgget(key, 0666 | IPC_CREAT);if (msqid == -1) {perror("Message queue creation failed");return 1;}my_msg msg;msg.mtype = 1;strcpy(msg.mtext, "Hello, world!");if (msgsnd(msqid, &msg, sizeof(msg.mtext), 0) == -1) {perror("Message send failed");return 1;}if (msgrcv(msqid, &msg, sizeof(msg.mtext), 1, 0) == -1) {perror("Message receive failed");return 1;}printf("Received message: %s\n", msg.mtext);if (msgctl(msqid, IPC_RMID, NULL) == -1) {perror("Message queue removal failed");return 1;}return 0;
}
3.3 共享内存 (Shared Memory)
3.3.1 简介
共享内存是一种高效的IPC机制,它允许多个进程共享同一块内存区域。这种方式比管道和消息队列更高效,因为它不需要复制数据,而是直接访问同一段物理内存。
3.3.2 函数原型
-
shmget
函数- 函数原型:
int shmget(key_t key, size_t size, int shmflg);
- 头文件:
<sys/shm.h>
- 参数:键值、共享内存大小、标志位。
- 返回值:共享内存标识符,如果出错返回-1。
- 说明:创建或获取一个共享内存段。
- 函数原型:
-
shmat
函数- 函数原型:
void *shmat(int shmid, const void *shmaddr, int shmflg);
- 头文件:
<sys/shm.h>
- 参数:共享内存标识符、共享内存地址、标志位。
- 返回值:成功返回指向共享内存的指针,失败返回
(void *)-1
。 - 说明:将共享内存段映射到进程的地址空间。
- 函数原型:
-
shmdt
函数- 函数原型:
int shmdt(const void *shmaddr);
- 头文件:
<sys/shm.h>
- 参数:指向共享内存的指针。
- 返回值:成功返回0,失败返回-1。
- 说明:解除共享内存段的映射。
- 函数原型:
-
shmctl
函数- 函数原型:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 头文件:
<sys/shm.h>
- 参数:共享内存标识符、命令、缓冲区。
- 返回值:成功返回0,失败返回-1。
- 说明:执行各种共享内存控制操作,例如删除共享内存段。
- 函数原型:
3.3.3 示例
#include <sys/shm.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>#define SHMKEY 1234int main() {key_t key = SHMKEY;int shmid = shmget(key, 100, 0666 | IPC_CREAT);if (shmid == -1) {perror("Shared memory creation failed");return 1;}char *shm = shmat(shmid, NULL, 0);if (shm == (char *)-1) {perror("Shared memory attach failed");return 1;}strcpy(shm, "Hello, world!");printf("Data written to shared memory: %s\n", shm);if (shmdt(shm) == -1) {perror("Shared memory detach failed");return 1;}if (shmctl(shmid, IPC_RMID, NULL) == -1) {perror("Shared memory removal failed");return 1;}return 0;
}
3.4 信号量 (Semaphores)
3.4.1 简介
信号量是一种同步原语,用于解决进程间的互斥和同步问题。它可以用来保护共享资源免受并发访问的影响,也可以用来实现进程间的同步。
3.4.2 函数原型
-
semget
函数- 函数原型:
int semget(key_t key, int nsems, int semflg);
- 头文件:
<sys/sem.h>
- 参数:键值、信号量集中的信号量数、标志位。
- 返回值:信号量集标识符,如果出错返回-1。
- 说明:创建或获取一个信号量集。
- 函数原型:
-
semop
函数- 函数原型:
int semop(int semid, struct sembuf *sops, size_t nsops);
- 头文件:
<sys/sem.h>
- 参数:信号量集标识符、信号量操作数组、操作数。
- 返回值:成功返回0,失败返回-1。
- 说明:对信号量集执行操作。
- 函数原型:
-
semctl
函数- 函数原型:
int semctl(int semid, int semnum, int cmd, ...);
- 头文件:
<sys/sem.h>
- 参数:信号量集标识符、信号量索引、命令、额外参数。
- 返回值:成功返回0或指定值,失败返回-1。
- 说明:执行各种信号量控制操作,例如设置信号量值。
- 函数原型:
3.4.3 示例
#include <sys/sem.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>#define SEMKEY 1234union semun {int val;struct semid_ds *buf;unsigned short *array;
};int main() {key_t key = SEMKEY;int semid = semget(key, 1, 0666 | IPC_CREAT);if (semid == -1) {perror("Semaphore creation failed");return 1;}union semun semval;semval.val = 1;if (semctl(semid, 0, SETVAL, semval) == -1) {perror("Semaphore value set failed");return 1;}struct sembuf op;op.sem_num = 0;op.sem_op = -1;op.sem_flg = 0;if (semop(semid, &op, 1) == -1) {perror("Semaphore operation failed");return 1;}op.sem_op = 1;if (semop(semid, &op, 1) == -1) {perror("Semaphore operation failed");return 1;}if (semctl(semid, 0, IPC_RMID, semval) == -1) {perror("Semaphore removal failed");return 1;}return 0;
}
3.5 不同IPC机制的比较
3.5.1 性能
- 管道:性能较低,因为涉及数据复制。
- 消息队列:性能中等,比管道稍快,但仍然涉及数据复制。
- 共享内存:性能最高,因为数据直接在内存中共享,无需复制。
- 信号量:主要用于同步,性能较高,但不是用于数据传输。
3.5.2 复杂性
- 管道:相对简单,适用于简单的数据传输。
- 消息队列:较复杂,支持类型化的消息,适用于复杂的数据交换。
- 共享内存:最复杂,需要手动管理内存,但提供了高性能的数据交换。
- 信号量:简单,主要用于同步目的。
3.5.3 可移植性
- 管道:广泛支持,几乎所有的POSIX系统都支持。
- 消息队列:POSIX标准的一部分,但并非所有系统都支持。
- 共享内存:POSIX标准的一部分,但并非所有系统都支持。
- 信号量:POSIX标准的一部分,但并非所有系统都支持。
3.6 IPC机制的选择
选择合适的IPC机制取决于以下几个因素:
- 数据传输的需求:如果只需要简单的数据传输,管道可能是最好的选择。
- 数据的复杂性:如果需要类型化的消息,消息队列是更好的选择。
- 性能需求:如果对性能有高要求,应考虑使用共享内存。
- 同步需求:如果主要关注同步,信号量是一个好的选择。
- 可移植性:如果需要高度可移植的解决方案,管道是最佳选择。
4. 进程调度
4.1 进程优先级
4.1.1 nice
函数
- 函数原型:
int nice(int inc);
- 头文件:
<unistd.h>
- 参数:优先级增量。
- 返回值:当前优先级,失败返回-1。
- 示例
#include <unistd.h>
#include <stdio.h>int main() {int oldPriority = nice(1);if (oldPriority == -1) {perror("nice failed");return 1;}printf("New priority: %d\n", oldPriority);return 0;
}
4.2 进程调度策略
4.2.1 sched_setscheduler
函数
- 函数原型:
int sched_setscheduler(pid_t pid, int policy, const struct sched_param *param);
- 头文件:
<sched.h>
- 参数:进程ID、调度策略、调度参数。
- 返回值:成功返回0,失败返回-1。
- 示例
#include <sched.h>
#include <stdio.h>
#include <unistd.h>int main() {struct sched_param param;param.sched_priority = 0; // 默认优先级if (sched_setscheduler(0, SCHED_FIFO, ¶m) == -1) {perror("Setting scheduler failed");return 1;}printf("Scheduler set to SCHED_FIFO\n");return 0;
}
5. 进程信号
5.1 信号处理
5.1.1 signal
函数
- 函数原型:
void signal(int signum, void (*handler)(int));
- 头文件:
<signal.h>
- 参数:信号号、信号处理器。
- 返回值:无。
- 示例
#include <signal.h>
#include <stdio.h>
#include <unistd.h>void sigHandler(int sig) {printf("Signal received: %d\n", sig);
}int main() {signal(SIGINT, sigHandler);printf("Press Ctrl+C to send SIGINT\n");while (1) {sleep(1);}return 0;
}
5.2 阻塞信号
5.2.1 sigprocmask
函数
- 函数原型:
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
- 头文件:
<signal.h>
- 参数:设置方式、新信号掩码、旧信号掩码。
- 返回值:成功返回0,失败返回-1。
- 示例
#include <signal.h>
#include <stdio.h>
#include <unistd.h>int main() {sigset_t mask;sigemptyset(&mask);sigaddset(&mask, SIGINT);if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) {perror("Blocking signals failed");return 1;}printf("SIGINT is now blocked\n");sleep(5); // 尝试发送SIGINT,不会被处理return 0;
}
6. 进程资源限制
6.1 设置资源限制
6.1.1 setrlimit
函数
- 函数原型:
int setrlimit(int resource, const struct rlimit *rlim);
- 头文件:
<sys/resource.h>
- 参数:资源类型、资源限制。
- 返回值:成功返回0,失败返回-1。
- 示例
#include <sys/resource.h>
#include <stdio.h>int main() {struct rlimit rlim;rlim.rlim_cur = 1024; // 当前限制rlim.rlim_max = 1024; // 最大限制if (setrlimit(RLIMIT_NOFILE, &rlim) == -1) {perror("Setting resource limit failed");return 1;}printf("Resource limit for RLIMIT_NOFILE set\n");return 0;
}
6.2 获取资源限制
6.2.1 getrlimit
函数
- 函数原型:
int getrlimit(int resource, struct rlimit *rlim);
- 头文件:
<sys/resource.h>
- 参数:资源类型、资源限制。
- 返回值:成功返回0,失败返回-1。
- 示例
#include <sys/resource.h>
#include <stdio.h>int main() {struct rlimit rlim;if (getrlimit(RLIMIT_NOFILE, &rlim) == -1) {perror("Getting resource limit failed");return 1;}printf("Current limit for RLIMIT_NOFILE: %ld\n", rlim.rlim_cur);printf("Maximum limit for RLIMIT_NOFILE: %ld\n", rlim.rlim_max);return 0;
}
结论
本章深入探讨了C语言中的进程编程和进程间通信(IPC)的概念、创建与管理机制,以及不同类型的IPC机制。
进程基本概念
- 进程定义:进程是程序在一个数据集合上的运行过程,是系统进行资源分配和调度的基本单位。
- 进程属性:包括进程ID (PID)、父进程ID (PPID)、进程状态、进程优先级等。
- 进程控制:包括创建(
fork
)、终止(exit
,_exit
)和等待(wait
,waitpid
)进程。
进程控制
- 创建进程:使用
fork
函数创建子进程。 - 终止进程:使用
exit
或_exit
函数终止进程。 - 等待进程:使用
wait
或waitpid
函数等待子进程结束。
进程间通信(IPC)
管道 (Pipes)
- 简介:管道允许两个进程之间进行单向的数据传输。
- 函数原型:
int pipe(int pipefd[2]);
- 头文件:
<unistd.h>
- 参数:管道描述符数组。
- 返回值:成功返回0,失败返回-1。
消息队列 (Message Queues)
- 简介:消息队列允许多个进程通过队列交换数据。
- 函数原型:
int msgget(key_t key, int flag);
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
int msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
- 头文件:
<sys/msg.h>
共享内存 (Shared Memory)
- 简介:共享内存允许多个进程共享同一块内存区域。
- 函数原型:
int shmget(key_t key, size_t size, int shmflg);
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 头文件:
<sys/shm.h>
信号量 (Semaphores)
- 简介:信号量用于解决进程间的互斥和同步问题。
- 函数原型:
int semget(key_t key, int nsems, int semflg);
int semop(int semid, struct sembuf *sops, size_t nsops);
int semctl(int semid, int semnum, int cmd, ...);
- 头文件:
<sys/sem.h>
IPC机制比较
- 性能:共享内存性能最高,其次是消息队列,然后是管道。
- 复杂性:共享内存最复杂,其次是消息队列,管道相对简单。
- 可移植性:管道最广泛支持,其他机制可能受限于特定系统。
IPC机制选择
- 数据传输需求:管道适合简单的数据传输。
- 数据复杂性:消息队列适合类型化的消息交换。
- 性能需求:共享内存适合高性能的数据交换。
- 同步需求:信号量适合进程间的同步。
- 可移植性:管道是最可移植的选择。
进程调度
- 进程优先级:使用
nice
函数修改进程的优先级。 - 进程调度策略:使用
sched_setscheduler
函数设置进程的调度策略。
进程信号
- 信号处理:使用
signal
函数注册信号处理器。 - 阻塞信号:使用
sigprocmask
函数阻塞信号。
进程资源限制
- 设置资源限制:使用
setrlimit
函数设置资源限制。 - 获取资源限制:使用
getrlimit
函数获取资源限制。