【Linux系统编程】信号 kill/raise/alarm/pause/alarm实例/漏桶算法
一,kill,raise,alarm,pause函数
1,kill函数
kill的作用是向指定进程 / 进程组发送信号(默认发送终止信号)
这个函数是有两个参数的,第一个参数是pid,pid指的是进程号,第二个参数就是sig,也就是信号的宏,可以通过kill -l可以查看信号的宏名这个分为以下几种情况
1,当pid是正数的时候,是实打实的给这个进程发送信号的
2,当pid是0的时候,这个信号会发给当前进程组的每一个进程信号
3,当pid是-1的时候,这个信号会发给当前进程组拥有发送信号权限的进程
4,当pid是小于-1的时候,这个信号会发给这个pid的绝对值的进程的进程组的每一个进程
5,当sig是0的时候,通常用来检测这个进程或者进程组存不存在返回值:为0的时候表示正常,为-1的时候为不正常
当我们设置sig为0的时候,来检测这个进程或者进程组是否存在的时候,当返回值为-1的时候,其实不一定表示这个进程不存在,因为有这几个宏名
EPERM:当当前进程与其他进程发生冲突的时候,那么就会返回这个
ESRCH:当当前进程不存在的话,那么就会返回这个2,raise函数
这个的用处就是给当前线程或者进程发送信号(注意是线程/进程)返回值:success返回值为0,fail返回值为非0
3,alarm函数
这个函数是以秒为单位的,当倒计时结束的时候,就会给当前进程发送一个SIGALRM的信号,然后这个信号的默认行为是:杀死当前的进程a. 无法实现多任务计时器的原因
原因 1:全局唯一,后调用直接覆盖前调用(最致命)
进程只有唯一一个闹钟资源:
- 第 1 次调用
alarm(3)→ 设 3 秒计时器;- 第 2 次调用
alarm(5)→ 直接覆盖前面的 3 秒计时器;- 最终只有 5 秒的计时生效,3 秒的任务直接作废。
多任务需要多个独立计时器,alarm 做不到!
原因 2:只绑定一个信号
SIGALRM,无法区分任务所有
alarm定时,只会触发同一个信号SIGALRM:
- 信号处理函数收到信号后,根本不知道是哪个任务的时间到了;
- 无法针对性执行任务 A、任务 B、任务 C 的逻辑。
原因 3:仅支持秒级精度,完全不满足多任务
alarm最小单位是秒;- 多任务计时通常需要毫秒 / 微秒级精度(如游戏、网络、串口通信);
- 精度完全不达标。
原因 4:信号异步不安全,多任务极易出 Bug
SIGALRM是异步信号:
- 信号会随时中断程序执行;
- 信号处理函数中不能调用大部分库函数(如
printf、malloc);- 多任务下极易出现数据混乱、竞态条件。
b. sleep不可随便使用的原因
先给核心结论
sleep绝对不能随便用:因为老式系统的sleep底层 =alarm + pause,会和你代码里的alarm彻底冲突,直接导致定时失效、程序崩溃;- 移植性极差:
sleep没有统一标准实现,不同系统(老 Unix / 新 Linux/Windows)底层完全不一样,行为天差地别;- 两个实现的本质区别:是否使用信号
SIGALRM(这是所有问题的根源)。一、先搞懂:sleep 的两种底层实现
1. 老式实现:
sleep = alarm() + pause()// 伪代码:老式 sleep 实现 unsigned int sleep(unsigned int sec) { alarm(sec); // 1. 用 alarm 设置秒级定时器,发 SIGALRM pause(); // 2. 进程挂起,等待信号唤醒 return 0; }致命问题:它复用了进程唯一的
SIGALRM信号 和 唯一的 alarm 计时器!2. 现代实现:
sleep = nanosleep()这是 新版 Linux/glibc 的实现,完全抛弃了信号:
// 伪代码:现代 sleep 实现 unsigned int sleep(unsigned int sec) { struct timespec ts = {sec, 0}; nanosleep(&ts, NULL); // 无信号、高精度休眠 return 0; }安全:不使用任何信号,不和
alarm冲突。4,pause函数
1. 核心功能
让当前进程主动挂起(休眠 / 阻塞),进入无限等待状态,直到进程收到 任意一个未被忽略的信号,才会被唤醒,继续执行代码。
- 进程挂起后:不占用 CPU 资源,处于睡眠状态;
- 唤醒条件:必须收到信号(无超时机制,会一直等)。
2. 函数原型 & 头文件
#include <unistd.h> // 唯一依赖头文件 int pause(void); // 无参数3. 返回值
pause()没有成功返回值,永远只返回 -1!
- 返回值:-1
- 错误码
errno:被设置为EINTR(表示「被信号中断」)为什么没有成功返回?因为 pause 只有被信号打断这一种退出方式,不存在正常执行完成的情况。
二,alarm的实例
1,time与alarm实例对比
这个是对于时间的精度的把控所设计的实验
#include <stdio.h> #include <stdlib.h> #include <time.h> int main() { time_t end; int64_t count=0; end = time(NULL) + 5; // time函数结果为整数 while(time(NULL) <= end) // 每次调用time都会从内核取时间戳,比较耗时 { count++; } printf("%ld", count); exit(0); }先用time来做了一个实例,就是在5秒钟无限次的循环,看可以执行循环多少次,然后我们分析结果
不难看出来,这个时间是有快有慢的,为什么呢?
其实在time看来,5.000.....1~5.9999....这个区间的数其实都是表示为5秒钟,所以就会有这么大地差距,但是我们用alarm来实现一下看看#include <stdio.h> #include <stdlib.h> #include <signal.h> #include <sys/time.h> #include <unistd.h> static int loop = 1; static void sig_handler(int s) { loop = 0; } int main() { time_t end; int64_t count=0; signal(SIGALRM, sig_handler); alarm(5); while(loop) // 每次调用time都会从内核取时间戳,比较耗时 { count++; } printf("%ld", count); exit(0); }
可以看到alarm实现出来地时间精度非常的高2,优化后的问题
当我们使用优化,就会出现问题,陷入无限循环
为什么呢?这要我们去查阅汇编代码
这是未优化的,看可以看到这里是先设置变量,然后alarm,然后下面就是jne去到L4去了,然后就继续进行
这里是陷入了无限的死循环的,所以这个优化会导致很多错误这个原因就是因为while循环里面咩有用到roop这个变量,他是一直都是从内存去读取数据,而不是从磁盘读取数据,所以就会陷入死循环,这个时候我们要用到volatile类型,强制让他去存取的位置读取数据
#include <stdio.h> #include <stdlib.h> #include <signal.h> #include <sys/time.h> #include <unistd.h> static volatile int loop = 1; static void sig_handler(int s) { loop = 0; } int main() { time_t end; int64_t count=0; signal(SIGALRM, sig_handler); alarm(5); while(loop) // 每次调用time都会从内核取时间戳,比较耗时 { count++; } printf("%ld", count); exit(0); }这样就可以了
三,漏桶算法
1,核心思想和优缺点
一、核心
经典流量限流整形算法:请求像水一样不停倒入漏桶,桶底固定匀速漏水处理请求,桶满则直接丢弃超额请求,强行把突发乱流压成平稳匀速流。
二、通俗模型比喻
- 漏桶:服务器 / 接口最大积压承载上限
- 注水:客户端突发高频请求(忽快忽慢)
- 漏水:系统固定 CPU/IO 处理速率(恒定不变)
- 溢出丢弃:超出承载上限的无效请求直接拒绝
三、四大核心参数
- 桶容量 capacity:最大能积压的请求总数
- 当前水量 water:正在排队积压的未处理请求
- 入流速率:随机、无规律的用户请求速率
- 出水速率 rate:系统固定匀速处理速率(不可提速)
四、标准工作流程
- 定时漏水:依托计时器(贴合你学的 alarm/time),固定周期匀速减少积压请求,正常处理业务
- 新请求到来:判断当前水量 < 桶容量?
- 是:水量 + 1,排队等待漏水处理
- 否:请求溢出,直接丢弃 / 返回限流错误
- 全程约束:无论入流多突发,出水永远匀速平稳
五、关键特性(区别令牌桶)
- 强整形:绝对匀速输出,彻底抹平流量尖峰
- 无突发容忍:哪怕系统空闲,也不能一次性快速处理批量请求
- 简单可靠:只靠计数 + 定时,无复杂逻辑,极低性能开销
六、优缺点精简
优点
- 实现极简,适配嵌入式 / Linux 底层限流
- 严格保护后端,不会被突发流量瞬间压垮
- 流量输出极致平滑,适配 TCP 拥塞控制
缺点
- 灵活性差,无法应对短时间合法业务突发
- 桶满直接丢包,无排队重试兜底
2,实例
首先我们要实现一个cat指令的功能
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #include <unistd.h> # define BUFSIZE 1024 int main(int argc, char **argv) { int sfd, dfd=1; // dfd为标准输出 char buf[BUFSIZE]; int len, ret, pos; if(argc < 2) { fprintf(stderr, "Usage...\n"); exit(1); } do { sfd = open(argv[1], O_RDONLY); if(sfd < 0) { if(errno != EINTR) { perror("open()"); exit(1); } } }while(sfd < 0); while(1) { // 1. 从文件读数据到缓冲区 len = read(sfd, buf, BUFSIZE); if(len < 0) { if(errno == EINTR) continue; // 信号中断,重试读取 perror("read()"); break; // 真正的读取错误 } if(len == 0) break; // len=0 → 读到文件末尾,退出循环 // 2. 把缓冲区数据写入屏幕(标准输出) pos = 0; while(len > 0) { ret = write(dfd, buf + pos, len); if(ret < 0) { if(errno == EINTR) continue; perror("write()"); exit(1); } pos += ret; // 偏移:下次从写入完成的位置继续 len -= ret; // 剩余未写入的长度 } } close(sfd); exit(0); }然后再改成漏桶算法:每秒输出10个字节
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #include <unistd.h> #include <signal.h> static volatile int loop = 0; #define CPS 10 #define BUFSIZE CPS static void alarm_handler(){ alarm(1); loop = 1; } int main(int argc, char **argv) { int sfd, dfd=1; // dfd为标准输出 char buf[BUFSIZE]; int len, ret, pos; if(argc < 2) { fprintf(stderr, "Usage...\n"); exit(1); } signal(SIGALRM, alarm_handler); alarm(1); do { sfd = open(argv[1], O_RDONLY); if(sfd < 0) { if(errno != EINTR) { perror("open()"); exit(1); } } }while(sfd < 0); while(1) { while(!loop); loop = 0; // 1. 从文件读数据到缓冲区 len = read(sfd, buf, BUFSIZE); if(len < 0) { if(errno == EINTR) continue; // 信号中断,重试读取 perror("read()"); break; // 真正的读取错误 } if(len == 0) break; // len=0 → 读到文件末尾,退出循环 // 2. 把缓冲区数据写入屏幕(标准输出) pos = 0; while(len > 0) { ret = write(dfd, buf + pos, len); if(ret < 0) { if(errno == EINTR) continue; perror("write()"); exit(1); } pos += ret; // 偏移:下次从写入完成的位置继续 len -= ret; // 剩余未写入的长度 } } close(sfd); exit(0); }
漏桶算法概念 你的代码实现 漏桶容量 / 单次流出量 BUFSIZE = 10(每次只读写 10 字节)固定漏水速率 alarm(1)+ 信号函数:每秒触发 1 次漏水开关(阀门) volatile int loop:信号触发 = 打开阀门,执行后 = 关闭阀门匀速放行逻辑 while(!loop);等待阀门打开,强制等待每秒一次的放行信号数据流出 read+write:每次仅输出 10 字节,严格匀速
- 绝对匀速:无任何突发流量,输出速度完全恒定;
- 阻塞限流:未到放行时间,程序会等待,不消耗多余资源;
- 简单可靠:用 Linux 信号 + 标志位实现,轻量无依赖;
- 速率可控:修改
CPS的值,就能直接调整每秒输出的字节数。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐













所有评论(0)