一,令牌桶算法实例
基于现有的计算机,在缓冲区中,当我们将在内存中的数据一刷新,那么刷新完之后的数据就可以直接交给CPU进行处理,在内存和缓冲区以及外存,他们的数据是一块一块进行交互传输的,那么基于这个思想,我们可以将我们的程序漏桶算法改为这样的形式,然后这个算法也就令牌桶算法
令牌桶算法就是在闲着的时候不断地积累权限,就比如一秒传输10个字节,当我们地程序在暂停地时候,这个算法就是也会进行等待,但是在这个等待地过程中在不断地累加,比如暂停了五秒,那么当执行第5秒结束的时候,那么执行就是直接传输50个字节,就是动态的
cpp#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 token = 0; #define CPS 10 #define BUFSIZE CPS #define BURST 100 // 最大值 static void alarm_handler(){ alarm(1); token++; if(token > BURST){ token = BURST; } } 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(token <= 0) pause(); token--; // 1. 从文件读数据到缓冲区 while((len = read(sfd, buf, BUFSIZE)) < 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); }
令牌桶规则 你的代码实现 每秒生成令牌 alarm_handler每秒token++令牌上限 token > BURST时强制等于 100无令牌等待 while(token <=0) pause();消耗令牌 传输前 token--限速传输 1 个令牌 → 读 / 写 10 字节 这个是这个代码的实现
二,令牌桶算法和漏桶算法总结
这里的令牌桶不是一次性输出的,而是连续输出,就是连续的三秒都进输出10个字符这样
三,令牌桶算法的封装
这里的令牌桶算法
atexit是系统函数,专门用来注册「退出时自动执行的函数」- 被注册的函数 = 钩子函数
- 钩子函数不用手动调用,程序退出时系统自动调用
- 用途:清理资源、关闭文件、释放内存、恢复系统设置
这是这个程序的大致图谱
main.c文件
cpp#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> #include <string.h> // 修复:strerror 需要这个头文件 #include "mytbf.h" // 修复:必须包含令牌桶头文件! static volatile int token = 0; #define CPS 10 #define BUFSIZE 1024 #define BURST 100 int main(int argc, char **argv) { int sfd, dfd=1; char buf[BUFSIZE]; int len, ret, pos; mytbf_t *tbf; int size; if(argc < 2) { fprintf(stderr, "Usage: %s <filename>\n", argv[0]); exit(1); } tbf = mytbf_init(CPS, BURST); if(tbf == NULL){ fprintf(stderr, "mytbf_init failed\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) { size = mytbf_fetchtoken(tbf, BUFSIZE); if(size < 0) { fprintf(stderr, "mytbf_fetchtoken():%s\n", strerror(-size)); exit(1); } while((len = read(sfd, buf, size)) < 0) { if(errno == EINTR) continue; perror("read()"); break; } if(len == 0) break; if(size - len > 0) mytbf_returntoken(tbf, size - len); 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); mytbf_destroy(tbf); exit(0); }mytbf.c文件
cpp#include <stdio.h> #include <stdlib.h> #include <signal.h> #include <string.h> #include <errno.h> #include <unistd.h> #include "mytbf.h" // 必须包含头文件! // 系统已有 sighandler_t,删除自定义定义 static struct mytbf_st *job[MYTBF_MAX]; static int inited = 0; static void (*alrm_handler_save)(int); // 修复信号指针类型 // 令牌桶结构体 struct mytbf_st { int cps; int burst; int token; int pos; }; // 统一函数名:alrm_handler static void alrm_handler(int s) { int i; alarm(1); for(i = 0; i < MYTBF_MAX; i++) { if(job[i] != NULL) { job[i]->token += job[i]->cps; if(job[i]->token > job[i]->burst) job[i]->token = job[i]->burst; } } } // 修复拼写错误:module_unload static void module_unload(void) { int i; signal(SIGALRM, alrm_handler_save); alarm(0); for(i = 0; i < MYTBF_MAX; i++) free(job[i]); } static void module_load(void) { // 保存原信号处理函数 alrm_handler_save = signal(SIGALRM, alrm_handler); alarm(1); atexit(module_unload); } static int get_free_pos(void) { int i; for(i = 0; i < MYTBF_MAX; i++) { if(job[i] == NULL) return i; } return -1; } mytbf_t *mytbf_init(int cps, int burst) { struct mytbf_st *me; int pos; if(!inited) { module_load(); inited = 1; } pos = get_free_pos(); if(pos < 0) return NULL; // 修复拼写错误:melloc → malloc // 修复大小:sizeof(*me) 不是 sizeof(me) me = malloc(sizeof(*me)); if(me == NULL) return NULL; me->token = 0; me->cps = cps; me->burst = burst; me->pos = pos; job[pos] = me; return me; } static int min(int a, int b) { return a < b ? a : b; } int mytbf_fetchtoken(mytbf_t *ptr, int size) { struct mytbf_st *me = ptr; int n; if(size <= 0) return -EINVAL; while(me->token <= 0) pause(); n = min(me->token, size); me->token -= n; return n; } int mytbf_returntoken(mytbf_t *ptr, int size) { struct mytbf_st *me = ptr; if(size <= 0) return -EINVAL; me->token += size; if(me->token > me->burst) me->token = me->burst; return size; } int mytbf_destroy(mytbf_t *ptr) { struct mytbf_st *me = ptr; job[me->pos] = NULL; free(ptr); return 0; }mytbf.h文件
cpp#ifndef MYTBF_H__ #define MYTBF_H__ #define MYTBF_MAX 1024 typedef void mytbf_t; mytbf_t * mytbf_init(int cps, int burst); int mytbf_fetchtoken(mytbf_t *, int); int mytbf_returntoken(mytbf_t *, int); int mytbf_destroy(mytbf_t *); #endif这是整体的功能实现图
最重要的就是alarm的signal设置,第一次的初始化alarm和signal这个可以通过一个全局变量标签来设置,然后后续就交给signal的handler函数来反复的设置alarm()就可以了、
四,多任务计时器
使用单一计时器,构造一组函数,实现任意数量的计时器
1,setittimer()函数
setitimer参数的讲解:
1,which:表达的是这个是在哪一个
- ITIMER_REAL:真实时间,到点发 SIGALRM(最常用)
- ITIMER_VIRTUAL:进程用户态时间,发 SIGVTALRM,虚拟时钟信号,只有在进程运行的时候,才进行计时,
- ITIMER_PROF:用户 + 内核态,也就是real时间,发 SIGPROF信号
剩下的两个参数:第二个表示这个参数新设置的值,第三个参数表示的是将老的值保存下来
上面表示的是保存值,也就是it_interval这个值,然后下面就是递减值,也就是说结果保存到initerval值,下面是计算值,initerval给value赋值,这个期间的复制是原子操作
然后timeval结构体:sec表示的是秒的意思,usec表示的是微秒的意思
返回值:0表示成功,-1表示失败,并且设置errno值
这个函数的最大的优势就是:误差不会累计,并且自带周期性质
2,多任务计时器的实例
就是将每一个任务都放到一个结构体里面,然后里面有秒,函数的地址,参数,然后一同执行,
比如sec分别是4 2 6,过来两秒第二个执行,变成2 0 4,然后再过两秒0 0 2 第一个执行,然后再过两秒,就全部执行完成了
这个需要我们后面学完相对应的只是才可以写,等我会后续补充
五,信号集
1,信号集的相关函数
信号集类型: sigset_t
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);
函数 功能(大白话) sigemptyset(sigset_t *set)清空信号篮子(不屏蔽任何信号) sigfillset(sigset_t *set)装满所有信号(屏蔽全部信号) sigaddset(set, signum)添加一个信号到篮子(屏蔽该信号) sigdelset(set, signum)从篮子删除一个信号(取消屏蔽) sigismember(set, signum)查询信号是否在篮子里(是否被屏蔽) 2,system函数
system()函数执行时,内部会自动临时处理 3 个信号:
- 忽略 2 个信号 :
SIGINT、SIGQUIT- 阻塞 1 个信号 :
SIGCHLD执行完后,自动恢复原来的信号设置 ,完全不影响你写的定时器信号(
SIGALRM)
信号名 触发方式 含义 SIGINTCtrl+C中断程序(终止进程) SIGQUITCtrl+\退出程序(核心转储) SIGCHLD自动触发 子进程退出时,自动发给父进程
system()底层是:fork()创建子进程 → 子进程执行命令 → 父进程等待子进程结束。
- 如果不忽略 :你按
Ctrl+C,父进程和子进程会一起被杀死,命令执行到一半直接崩溃;- 如果忽略 :
Ctrl+C只会传给子进程(执行命令的进程),父进程稳稳等待,不会被打断。忽略这两个信号 = 保护
system()执行的命令不被随意中断。子进程退出 → 发
SIGCHLD→ 执行handler→ 自动收尸 → 无僵尸进程3,nanosleep函数
nanosleep是 Linux 下纳秒级精度的休眠函数。- 完全不使用信号 ,不和
SIGALRM定时器冲突,比sleep/usleep更安全。- 被信号打断时会返回剩余休眠时间,可以继续补睡。
- 是多任务、定时器程序里推荐的标准休眠方式
第一表示的是秒,第二个表示的是毫秒
cppstruct timespec req; // 休眠 2秒 + 500毫秒 req.tv_sec = 2; req.tv_nsec = 500000000; nanosleep(&req, NULL); // 不需要剩余时间,rem传NULL4,abort函数
- 作用 :强制异常终止进程,用于程序严重错误时主动崩溃。
- 信号 :发送
SIGABRT信号,默认终止进程并生成崩溃日志(coredump)。- 特性 :无法被阻塞、忽略、捕获,一定会终止进程。
- 行为 :刷新 IO 缓冲区,但不执行
atexit注册的清理函数。- 场景:断言失败、致命错误,无法恢复时使用。
cpp#include <stdio.h> #include <stdlib.h> int main() { printf("准备异常退出\n"); // 直接触发程序崩溃 abort(); // 下面这行永远不会执行 printf("这句不会打印\n"); return 0; }这就是abort函数的用法
5,select的基础用法
这个函数会到后面的话,会在高级IO里面进行讲解
这里的中间的参数就不说了,然后这个一般使用再IO多路连接的,然后最后一个是超时设置,就是当到达这个函数,然后前面的指针全部是NULL,timeout是一个计时器,比如20,就是倒数20秒,然后当到达了20秒之后,就执行前面的,但是前面我们都没有设置,所以就不会有任何事情响应,但是中间会延迟20秒
nfds是 "number of file descriptors" 的缩写,它表示:需要检查的文件描述符的范围上限 = 「当前所有待监控文件描述符中的最大值 + 1」
六,信号屏蔽字和pending
1,信号屏蔽字mask
- how (屏蔽方式)
SIG_BLOCK:屏蔽 set 中的信号(追加)SIG_UNBLOCK:解除 set 中信号的屏蔽SIG_SETMASK:直接设置屏蔽字为 set- set:新的信号集(要屏蔽 / 解除的信号)
- oldset :保存旧的屏蔽字(可选,传 NULL 则不保存)
mask:1 -> 不被处理,信号被阻塞 2 -> 被处理,信号不被阻塞
- 屏蔽的信号不会丢失,解除屏蔽后立即触发
- SIGKILL、SIGSTOP 无法被屏蔽(强制信号)
- 多线程中用
pthread_sigmask,单进程用sigprocmask2,pending
cpp#include <signal.h> int sigpending(sigset_t *set);
set:输出型参数 ,函数执行后,里面存放当前所有未决信号- 返回值 :成功返回
0,失败返回-1读取已经产生、但被阻塞无法处理的信号,存放到指定的信号集里。
这就是有个很难受的点
就是当我们触发这个函数的时候,然后会到kernel里面去取pending,然后返回的时候,kernel到user会触发一个与运算,mask&pending,然后这个时候信号已经被响应的,我们取得不是最新的pending,所以就很难运用这个
3,mask实例
cpp#include <stdio.h> #include <stdlib.h> #include <signal.h> static void sig_handler(int s) { write(1, "!", 1); } int main() { int i,j; sigset_t set, oset, saveset; signal(SIGINT, sig_handler); sigemptyset(&set); sigaddset(&set, SIGINT); // 保存初始状态 sigprocmask(SIG_UNBLOCK, &set, &saveset); for(j = 0; j < 1000; j++) { // 将set中信号增加到block集 sigprocmask(SIG_BLOCK, &set, NULL); // 该信号的maks位置为0 /* 或者存储之前状态,其后再恢复 sigprocmask(SIG_BLOCK, &set, &oset); 临界区 sigprocmask(SIG_SETMASK, &oset, NULL); */ for(i=0; i<5; i++) { write(1, "*", 1); sleep(1); } write(1, "\n", 1); sigprocmask(SIG_UNBLOCK, &set, NULL); } // 确保进入和离开模块后,信号的行为不发生改变 sigprocmask(SIG_SETMASK, &saveset, NULL); }
- 你定义的
sigset_t set / saveset→ 用户态的普通变量
- 就是两个存信号的本地盒子,和内核无关;
set:你用来临时阻塞信号的工具盒;saveset:你用来备份初始状态的存档盒;- 两个盒子互相独立,数据不互通。
- 内核里的 进程信号屏蔽字 → 真正控制信号的核心
- 内核里只有一个,管进程能不能收到信号;
sigprocmask所有操作,都是改这个内核屏蔽字,不是改你的变量!还原的真实原理(一句话)
SIG_SETMASK的作用:把「备份盒 saveset 里的数据」,直接覆盖到「内核的信号屏蔽字」里,完成还原。 全程和set没有任何关系!
【Linux系统编程】信号 令牌桶算法实例/多任务计时器/信号集/信号屏蔽字/pending
一只自律的鸡2026-03-30 10:17
相关推荐
宝桥南山2 小时前
GitHub Copilot - 可以使用Local, Copilot CLI, Cloud等不同方式来运行agent tasks牢七4 小时前
白盒123codkingo14 小时前
一文讲清 Harness Engineering:为什么 Agent 的瓶颈不在模型步步为营DotNet21 小时前
深入剖析.NET 11 中 Microsoft.Extensions.AI 在 AI 驱动后端开发的进阶应用Java后端的Ai之路1 天前
Playwright是微软开源的浏览器自动化库:从入门到精通的实战指南LINgZone22 天前
Java Mock 测试框架 Mockitocoderlin_2 天前
langchain 基础王哥儿聊AI2 天前
微软开源神器MarkItDown:一键把PPT/PDF/Excel转成markdown,LLM直呼内行!love530love2 天前
【独家资源】Windows 本地部署微软 BitNet b1.58: Flash Attention + CUDA GPU 加速 (sm_86) + AVX2 优化 + 1.58bit 量化









