进程
进程简介
进程(Process)是正在运行的程序,是操作系统进行资源分配和调度的基本单位。程序是存储在硬盘或内存的一段二进制序列,是静态的,而进程是动态的。进程包括代码、数据以及分配给它的其他系统资源(如文件描述符、网络连接等)。
进程处理相关系统调用
system函数
system
函数是标准库中执行shell指令的函数,可以使用 man 3 system
命令查看其声明。创建 system_test.c
,写入以下内容:
#include <stdio.h>
#include <stdlib.h>
int main() {
int result = system("ping -c 10 www.atguigu.com");
if (result != 0) {
printf("无法执行命令");
return 1;
}
return 0;
}
fork、execve和waitpid系统调用
- fork():创建一个子进程,相当于复制,包括内存空间。
- 返回值:
- 在父进程中返回子进程的PID
- 在子进程中返回0
- 发生错误返回-1
- 返回值:
- execve():在同一个进程中跳转执行另外一个程序。
- 参数:
char *__path
:需要执行程序的完整路径名char *const __argv[]
:指向字符串数组的指针,需要传入多个参数char *const __envp[]
:指向字符串数组的指针,需要传入多个环境变量参数
- 返回值:
- 成功就回不来了,下面的代码都没有意义
- 失败返回-1
- 参数:
- waitpid():等待子进程的终止并获取子进程的退出状态。
- 参数:
pid_t pid
:等待的模式int *wstatus
:整数指针,子进程返回的状态码会保存到该intint options
:选项的值是以下常量之一或多个的按位或(OR)运算的结果
- 返回值:
- 成功等到子进程停止返回pid
- 没等到并且没有设置WNOHANG一直等
- 没等到设置WNOHANG返回0
- 出错返回-1
- 参数:
测试例程
创建 fork_test.c
,写入以下内容:
#include <stdio.h>
#include <unistd.h>
int main(int argc, char const* argv[]) {
// 利用 fock 来复制出来一个子进程
printf("当前进程的 pid = %d \n", getpid());
/**
* 参数:无
* return: -1 创建进程失败 ,在子进程中返回值为 0 , 在父进程中返回值为创建出来的子进程的pid
*/
__pid_t pid = fork();
// printf("%d \n", pid);
if (pid == -1) {
perror("创建进程失败");
return 1;
} else if (pid == 0) {
// 子进程要执行的代码
printf("我是子进程 pid 为 %d , 我的父进程pid 为 %d \n", getpid(), getppid());
} else {
// 父进程要执行的代码
printf("我是父进程 pid 为 %d , 创建出来的子进程 pid %d \n", getpid(), pid);
}
printf("这里是父子进程都会执行 \n");
// 添加死循环更好观察到,要不然父进程执行代码就立即关闭,到时 fork 出来的子进程归属给其他进程
// 父进程结束了,子进程也会即可退出
while (1) {}
return 0;
}
文件描述符的引用计数和close()
sleep()
函数原型
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
测试例程
execve
execve单独测试
创建 erlou.c
exec系列函数可以在同一个进程中跳转执行另外一个程序,我们先准备一个可执行程序 erlou
,通过编译 erlou.c
获得。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("参数不够,上不了二楼.\n");
return 1; // 当没有传入参数时,应返回非零值表示错误
}
printf("我是%s %d,我跟海哥上二楼啦!\n", argv[1], getpid());
return 0;
}
创建 execve_test.c
,写入以下内容
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
char *name = "banzhang";
printf("我是%s %d,我现在在一楼\n", name, getpid());
char *args[] = {"/home/atguigu/process_test/erlou", name, NULL};
char *envs[] = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin", NULL};
int re = execve(args[0], args, envs);
if (re == -1) {
printf("你没机会上二楼\n");
return -1;
}
return 0;
}
execve+fork
测试例程
可以 fork
和 exec
共同使用,实现场景老学员推荐新学员在二楼学习,自己保持不变。
创建 fork_execve_test.c
,写入以下内容。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
int main(int argc, char const *argv[]) {
char *name = "老学员";
printf("%s%d在一楼精进\n", name, getpid());
pid_t pid = fork();
if (pid == -1) {
printf("邀请新学员失败!\n");
} else if (pid == 0) {
char *newName = "ergou";
char *args[] = {"/home/atguigu/process_test/erlou", newName, NULL};
char *envs[] = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin", NULL};
int re = execve(args[0], args, envs);
if (re == -1) {
printf("新学员上二楼失败\n");
return 1;
}
} else {
printf("老学员%d邀请完%d之后还是在一楼学习\n", getpid(), pid);
}
return 0;
}
waitpid
Linux中父进程除了可以启动子进程,还要负责回收子进程的状态。如果子进程结束后父进程没有正常回收,那么子进程就会变成一个僵尸进程——即程序执行完成,但是进程没有完全结束,其内核中PCB结构体(下文介绍)没有释放。在上面的例子中,父进程在子进程结束前就结束了,那么其子进程的回收工作就交给了父进程的父进程的父进程(省略若干父进程)。
本节通过系统调用 waitpid
在父进程中等待子进程完成并执行回收工作。
函数原型
执行 man 2 waitpid
查看手册可知声明如下:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);
测试例程
创建 waitpid_test.c
,写入以下内容。
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char const *argv[]) {
int subprocess_status;
printf("老学员在校区\n");
pid_t pid = fork();
if (pid < 0) {
perror("fork");
return 1;
} else if (pid == 0) {
char *args[] = {"/usr/bin/ping", "-c", "50", "www.atguigu.com", NULL};
char *envs[] = {NULL};
printf("新学员%d联系海哥10次\n", getpid());
int exR = execve(args[0], args, envs);
if (exR < 0) {
perror("execve");
return 1;
}
} else {
printf("老学员%d等待新学员%d联系\n", getpid(), pid);
waitpid(pid, &subprocess_status, 0);
}
printf("老学员等待新学员联系完成\n");
return 0;
}
进程树
Linux的进程是通过父子关系组织起来的,所有进程之间的父子关系共同构成了进程树(Process Tree)。进程树中每个节点都是其上级节点的子进程,同时又是子结点的父进程。一个进程的父进程只能有一个,而一个进程的子进程可以不止一个。
创建 pstree_test.c
,写入以下内容
以下程序是在 fork_execve_test.c
的基础上做了改动,通过 fgetc()
阻塞父进程的执行,确保测试期间父进程不会退出。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
int main(int argc, char const* argv[]) {
char* name = "老学员";
printf("%s%d在一楼精进\n", name, getpid());
pid_t pid = fork();
if (pid == -1) {
printf("邀请新学员失败!\n");
} else if (pid == 0) {
char* newName = "ergou";
char* argv[] = { "/home/atguigu/process_test/erlou", newName, NULL };
char* envp[] = { "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin", NULL };
int re = execve(argv[0], argv, envp);
if (re == -1) {
printf("新学员上二楼失败\n");
return 1;
}
} else {
printf("老学员%d邀请完%d之后还是在一楼学习\n", getpid(), pid);
char bye = fgetc(stdin);
}
return 0;
}
孤儿进程
孤儿进程(Orphan Process)是指父进程已结束或终止,而它仍在运行的进程。
当父进程结束之前没有等待子进程结束,且父进程先于子进程结束时,那么子进程就会变成孤儿进程。
创建 erlou_block.c
,写入以下内容
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("参数不够,上不了二楼.\n");
return 1; // 当没有传入参数时,应返回非零值表示错误
}
printf("我是%s %d,我跟海哥上二楼啦!\n", argv[1], getpid());
sleep(100);
return 0;
}
创建 orphan_process_test.c
,写入以下内容
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
int main(int argc, char const *argv[]) {
char *name = "老学员";
printf("%s%d在一楼精进\n", name, getpid());
pid_t pid = fork();
if (pid == -1) {
printf("邀请新学员失败!\n");
} else if (pid == 0) {
char *newName = "ergou";
char *argv[] = {"/home/atguigu/process_test/erlou_block", newName, NULL};
char *envp[] = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin", NULL};
int re = execve(argv[0], argv, envp);
if (re == -1) {
printf("新学员上二楼失败\n");
return 1;
}
} else {
printf("老学员%d邀请完%d之后还是在一楼学习\n", getpid(), pid);
}
return 0;
}
进程间通信
进程之前的内存是隔离的,如果多个进程之间需要进行信息交换,常用的方法有以下几种:
- Unix Domain Socket IPC
- 管道(有名管道、无名管道)
- 共享内存
- 消息队列
- 信号量
SystemV IPC和POSIX IPC
System V
System V(读作System Five)是一种基于UNIX的操作系统版本,最初由AT&T(American TelePhone and Telegraph Company,美国电话电报公司,由Bell TelePhone Company发展而来)开发。它在1983年首次发布,对UNIX操作系统的发展产生了深远的影响。SystemV引入了许多新的特性和标准,后来被许多UNIX系统和类UNIX系统(如Linux)采纳。
System V IPC
System V IPC(Inter-Process Communication,进程间通信)是System V操作系统引入的一组进程间通信机制,包括消息队列、信号量和共享内存。这些机制允许不同的进程以一种安全且高效的方式共享数据和同步操作。
- 消息队列:允许进程以消息的形式交换数据,这些消息存储在队列中,直到它们被接收。
- 信号量:主要用于进程间的同步,防止多个进程同时访问相同的资源。
- 共享内存:允许多个进程访问同一块内存区域,提供了一种非常高效的数据共享方式。
System V IPC是UNIX和类UNIX系统中常用的IPC方法之一,它通过关键字(key)来标识和访问IPC资源。
POSIX IPC
POSIX IPC是POSIX标准中的一部分,提供了一种更现代和标准化的进程间通信方式,同样包括消息队列、信号量和共享内存三种方式。
- 消息队列:类似于System V,但通常具有更简洁的API和更好的错误处理能力。
- 信号量:提供了更多的功能和更高的性能,支持更大范围的操作。
- 共享内存:提供了更多的控制和配置选项,以支持更复杂的应用场景。
POSIX IPC 使用名字(name)作为唯一标识。这些名字通常是以正斜杠(/)开头的字符串,用于唯一地识别资源如消息队列、信号量或共享内存对象。
二者的比较
System V IPC和POSIX IPC在功能上有所重叠,但它们在实现和API设计上有明显的区别。POSIX IPC通常被视为更现代、更标准化的解决方案,提供了更好的跨平台支持和更易于使用的API。然而,System V IPC在历史上更早地被大量UNIX系统所采用,因此在一些旧的或特定的环境中仍然非常重要。在选择使用哪种IPC机制时,应考虑应用程序的具体需求、目标系统的支持程度以及开发者的熟悉程度。
匿名管道(Pipe)
库函数 perror()
创建 perror_test.c
,写入以下内容。
#include <stdio.h>
int main(int argc, char const *argv[]) {
fopen("bucunzai.txt", "r");
perror("这道题我不会做! ");
return 0;
}
系统调用 pipe()
匿名管道是位于内核的一块缓冲区,用于进程间通信。创建匿名管道的系统调用为 pipe
。执行 man 2 pipe
查看手册,其声明如下:
#include <unistd.h>
int pipe(int pipefd[2]);
匿名管道测试例程
宏定义 EXIT_FAILURE
、EXIT_SUCCESS
、STDOUT_FILENO
#define EXIT_FAILURE 1 /* Failing exit status. */
#define EXIT_SUCCESS 0 /* Successful exit status. */
#define STDIN_FILENO 0 /* Standard input. */
#define STDOUT_FILENO 1 /* Standard output. */
#define STDERR_FILENO 2 /* Standard error output. */
创建 unnamed_pipe_test.c
下面的例子展示了父进程将 argv[1]
写入匿名管道,子进程读取并输出到控制台的过程,例程如下。
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char const *argv[]) {
int pipefd[2];
pid_t cpid;
char buf;
if (argc != 2) {
fprintf(stderr, "%s:请填写需要传递的信息\n", argv[0]);
exit(EXIT_FAILURE);
}
if (pipe(pipefd) == -1) {
perror("创建管道失败\n");
exit(EXIT_FAILURE);
}
cpid = fork();
if (cpid == -1) {
perror("邀请新学员失败!\n");
exit(EXIT_FAILURE);
}
if (cpid == 0) {
close(pipefd[1]);
char str[100] = {0};
sprintf(str, "新学员%d收到邀请\n", getpid());
write(STDOUT_FILENO, str, sizeof(str));
while (read(pipefd[0], &buf, 1) > 0) {
write(STDOUT_FILENO, &buf, 1);
}
write(STDOUT_FILENO, "\n", 1);
close(pipefd[0]);
_exit(EXIT_SUCCESS);
} else {
close(pipefd[0]);
printf("老学员%d发出邀请\n", getpid());
write(pipefd[1], argv[1], strlen(argv[1]));
close(pipefd[1]);
waitpid(cpid, NULL, 0);
exit(EXIT_SUCCESS);
}
}
有名管道(FIFO)
上文介绍的Pipe是匿名管道,只能在有父子关系的进程间使用,某些场景下并不能满足需求。与匿名管道相对的是有名管道,在Linux中称为FIFO,即First In First Out,先进先出队列。
FIFO和Pipe一样,提供了双向进程间通信渠道。但要注意的是,无论是有名管道还是匿名管道,同一条管道只应用于单向通信,否则可能出现通信混乱(进程读到自己发的数据)。
有名管道可以用于任何进程之间的通信。
库函数 mkfifo()
执行 man 3 mkfifo
查看文件说明。
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
有名管道发送端
创建 fifo_write.c
,写入以下内容。
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main() {
int fd;
char *pipe_path = "/tmp/myfifo";
if (mkfifo(pipe_path, 0664) != 0) {
perror("mkfifo failed");
if (errno != 17) {
exit(EXIT_FAILURE);
}
}
fd = open(pipe_path, O_WRONLY);
if (fd == -1) {
perror("open failed");
exit(EXIT_FAILURE);
}
char write_buf[100];
ssize_t read_num;
while ((read_num = read(STDIN_FILENO, write_buf, 100)) > 0) {
write(fd, write_buf, read_num);
}
if (read_num < 0) {
perror("read");
printf("命令行数据读取异常,退出");
close(fd);
exit(EXIT_FAILURE);
}
printf("发送管道退出,进程终止\n");
close(fd);
return 0;
}
有名管道接收端
创建 fifo_read.c
,写入以下内容。
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
int fd;
char *pipe_path = "/tmp/myfifo";
fd = open(pipe_path, O_RDONLY);
if (fd == -1) {
perror("open failed");
exit(EXIT_FAILURE);
}
char read_buff[100];
ssize_t read_num;
while ((read_num = read(fd, read_buff, 100)) > 0) {
write(STDOUT_FILENO, read_buff, read_num);
}
if (read_num < 0) {
perror("read");
printf("管道数据读取异常,退出");
exit(EXIT_FAILURE);
}
printf("接收管道退出,进程终止\n");
close(fd);
return 0;
}
共享内存
shm_open()
和 shm_unlink()
shm_open
可以开启一块内存共享对象,我们可以像使用一般文件描述符一般使用这块内存对象。
#include <sys/mman.h>
int shm_open(const char *name, int oflag, mode_t mode);
int shm_unlink(const char *name);
truncate()
和 ftruncate()
truncate
和 ftruncate
都可以将文件缩放到指定大小,二者的行为类似:如果文件被缩小,截断部分的数据丢失,如果文件空间被放大,扩展的部分均为 \0
字符。缩放前后文件的偏移量不会更改。缩放成功返回0,失败返回-1。
不同的是,前者需要指定路径,而后者需要提供文件描述符;ftruncate
缩放的文件描述符可以是通过 shm_open()
开启的内存对象,而 truncate
缩放的文件必须是文件系统已存在文件,若文件不存在或没有权限则会失败。
#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
mmap()
mmap
系统调用可以将一组设备或者文件映射到内存地址,我们在内存中寻址就相当于在读取这个文件指定地址的数据。父进程在创建一个内存共享对象并将其映射到内存区后,子进程可以正常读写该内存区,并且父进程也能看到更改。使用 man 2 mmap
查看该系统调用声明:
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);
测试例程
下面的程序展示了如何使用 mmap
在父子进程之间共享信息。
创建 shared_memory.c
,写入以下内容
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <string.h>
int main() {
char *share;
pid_t pid;
char shmName[100] = {0};
sprintf(shmName, "/letter%d", getpid());
int fd;
fd = shm_open(shmName, O_CREAT | O_RDWR, 0644);
if (fd < 0) {
perror("共享内存对象开启失败!\n");
exit(EXIT_FAILURE);
}
ftruncate(fd, 100);
share = mmap(NULL, 100, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (share == MAP_FAILED) {
perror("共享内存对象映射到内存失败!\n");
exit(EXIT_FAILURE);
}
close(fd);
pid = fork();
if (pid == 0) {
strcpy(share, "你是个好人!\n");
printf("新学员%d完成回信!\n", getpid());
} else {
sleep(1);
printf("老学员%d看到新学员%d回信的内容: %s", getpid(), pid, share);
wait(NULL);
int ret = munmap(share, 100);
if (ret == -1) {
perror("munmap");
exit(EXIT_FAILURE);
}
}
shm_unlink(shmName);
return 0;
}
临时文件系统
Linux的临时文件系统(tmpfs)是一种基于内存的文件系统,它将数据存储在RAM或者在需要时部分使用交换空间(swap)。tmpfs访问速度快,但因为存储在内存,重启后数据清空,通常用于存储一些临时文件。
我们可以通过 df -h
查看当前操作系统已挂载的文件系统。
内存共享对象在临时文件系统中的表示位于 /dev/shm
目录下。
消息队列
相关数据类型
mqd_t
:该数据类型定义在mqueue.h
中,是用来记录消息队列描述符的。实质上是int
类型的别名。struct mq_attr
:消息队列的属性信息。struct timespec
:时间结构体,提供了纳秒级的UNIX时间戳。
相关系统调用
mq_open()
:创建或打开一个已存在的POSIX消息队列。mq_timedsend()
:将消息追加到消息队列的尾部。mq_timedreceive()
:从消息队列中取走最早入队且权限最高的消息。mq_unlink()
:清除消息队列。clock_gettime()
:获取以struct timespec
形式表示的clockid
指定的时钟。
父子进程间通信测试例程
创建 father_son_mq_test.c
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char const* argv[]) {
struct mq_attr attr;
attr.mq_maxmsg = 10;
attr.mq_msgsize = 100;
attr.mq_flags = 0;
attr.mq_curmsgs = 0;
char* mq_name = "/father_son_mq";
mqd_t mqdes = mq_open(mq_name, O_RDWR | O_CREAT, 0664, &attr);
if (mqdes == ( mqd_t ) -1) {
perror("mq_open");
exit(EXIT_FAILURE);
}
pid_t pid = fork();
if (pid < 0) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) {
char read_buf[100];
struct timespec time_info;
for (size_t i = 0; i < 10; i++) {
memset(read_buf, 0, 100);
clock_gettime(0, &time_info);
time_info.tv_sec += 15;
if (mq_timedreceive(mqdes, read_buf, 100, NULL, &time_info) == -1) {
perror("mq_timedreceive");
}
printf("子进程接收到数据:%s\n", read_buf);
}
} else {
char send_buf[100];
struct timespec time_info;
for (size_t i = 0; i < 10; i++) {
memset(send_buf, 0, 100);
sprintf(send_buf, "父进程的第%d次发送消息\n", ( int ) (i + 1));
clock_gettime(0, &time_info);
time_info.tv_sec += 5;
if (mq_timedsend(mqdes, send_buf, strlen(send_buf), 0, &time_info) == -1) {
perror("mq_timedsend");
}
printf("父进程发送一条消息,休眠1s\n");
sleep(1);
}
}
close(mqdes);
if (pid > 0) {
mq_unlink(mq_name);
}
return 0;
}
非父子进程通信案例
我们创建两个进程:生产者和消费者,前者从控制台接收数据并写入消息队列,后者从消息队列接收数据并打印到控制台。
创建 producer.c
#include <time.h>
#include <mqueue.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main() {
char *mq_name = "/p_c_mq";
struct mq_attr attr;
attr.mq_flags = 0;
attr.mq_maxmsg = 10;
attr.mq_msgsize = 100;
attr.mq_curmsgs = 0;
mqd_t mqdes = mq_open(mq_name, O_CREAT | O_WRONLY, 0666, &attr);
if (mqdes == (mqd_t)-1) {
perror("mq_open");
}
char writeBuf[100];
struct timespec time_info;
while (1) {
memset(writeBuf, 0, 100);
ssize_t read_count = read(0, writeBuf, 100);
if (read_count == -1) {
perror("read");
continue;
} else if (read_count == 0) {
}
clock_gettime(CLOCK_REALTIME, &time_info);
time_info.tv_sec += 5;
if (read_count == 0) {
printf("Received EOF, exit...\n");
char eof = EOF;
if (mq_timedsend(mqdes, &eof, 1, 0, &time_info) == -1) {
perror("mq_timedsend");
}
break;
}
if (mq_timedsend(mqdes, writeBuf, strlen(writeBuf), 0, &time_info) == -1) {
perror("mq_timesend");
}
printf("从命令行接收到数据,已发送至消费者端\n");
}
close(mqdes);
return 0;
}
创建 consumer.c
#include <time.h>
#include <mqueue.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main() {
char *mq_name = "/p_c_mq";
struct mq_attr attr;
attr.mq_flags = 0;
attr.mq_maxmsg = 10;
attr.mq_msgsize = 100;
attr.mq_curmsgs = 0;
mqd_t mqdes = mq_open(mq_name, O_CREAT | O_RDONLY, 0666, &attr);
if (mqdes == -1) {
perror("mq_open");
}
char readBuf[100];
struct timespec time_info;
while (1) {
memset(readBuf, 0, 100);
clock_gettime(CLOCK_REALTIME, &time_info);
time_info.tv_sec += 86400;
if (mq_timedreceive(mqdes, readBuf, 100, NULL, &time_info) == -1) {
perror("mq_timedreceive");
}
if (readBuf[0] == EOF) {
printf("接收到生产者的终止信号,准备退出...\n");
break;
}
printf("接收到来自于生产者的数据\n%s", readBuf);
}
close(mqdes);
mq_unlink(mq_name);
return 0;
}
信号
信号简介
在Linux中,信号是一种用于通知进程发生了某种事件的机制。信号可以由内核、其他进程或者通过命令行工具发送给目标进程。Linux系统中有多种信号,每种信号都用一个唯一的整数值来表示,例如,常见的信号包括:
- SIGINT(2):这是当用户在终端按下Ctrl+C时发送给前台进程的信号,通常用于请求进程终止。
- SIGKILL(9):这是一种强制终止进程的信号,它会立即终止目标进程,且不能被捕获或忽略。
- SIGTERM(15):这是一种用于请求进程终止的信号,通常由系统管理员或其他进程发送给目标进程。
- SIGUSR1(10)和SIGUSR2(12):这两个信号是用户自定义的信号,可以由应用程序使用。
- SIGSEGV(11):这是一种表示进程非法内存访问的信号,通常是由于进程尝试访问未分配的内存或者试图执行非法指令而导致的。
- SIGALRM(14):这是一个定时器信号,通常用于在一定时间间隔后向目标进程发送信号。
每种信号都有其特定的含义和行为,进程可以通过注册信号处理函数来捕获信号并执行相应的操作,例如终止进程、忽略信号或者执行特定的处理逻辑。如果想查看所有的Linux信号,请执行 kill -l
指令。
信号处理例程
我们可以通过 signal
系统调用注册信号处理函数:
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
下面的例子简单演示了如何处理收到的信号。
创建 signal_test.c
,写入以下内容
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void sigint_handler(int signum) {
printf("\n收到%d信号,停止程序!\n", signum);
exit(signum);
}
int main() {
if (signal(SIGINT, sigint_handler) == SIG_ERR) {
perror("注册新的信号处理函数失败\n");
return 1;
}
while (1) {
sleep(1);
printf("你好,在吗?\n");
}
return 0;
}