【Linux系统编程】信号 令牌桶算法实例/多任务计时器/信号集/信号屏蔽字/pending

一,令牌桶算法实例

基于现有的计算机,在缓冲区中,当我们将在内存中的数据一刷新,那么刷新完之后的数据就可以直接交给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 个信号

  1. 忽略 2 个信号SIGINTSIGQUIT
  2. 阻塞 1 个信号SIGCHLD

执行完后,自动恢复原来的信号设置 ,完全不影响你写的定时器信号(SIGALRM

信号名 触发方式 含义
SIGINT Ctrl+C 中断程序(终止进程)
SIGQUIT Ctrl+\ 退出程序(核心转储)
SIGCHLD 自动触发 子进程退出时,自动发给父进程

system() 底层是:fork() 创建子进程 → 子进程执行命令 → 父进程等待子进程结束。

  • 如果不忽略 :你按 Ctrl+C父进程和子进程会一起被杀死,命令执行到一半直接崩溃;
  • 如果忽略Ctrl+C 只会传给子进程(执行命令的进程),父进程稳稳等待,不会被打断。

忽略这两个信号 = 保护 system() 执行的命令不被随意中断

子进程退出 → 发 SIGCHLD → 执行 handler → 自动收尸 → 无僵尸进程

3,nanosleep函数

  • nanosleep 是 Linux 下纳秒级精度的休眠函数。
  • 完全不使用信号 ,不和 SIGALRM 定时器冲突,比 sleep/usleep 更安全。
  • 被信号打断时会返回剩余休眠时间,可以继续补睡。
  • 是多任务、定时器程序里推荐的标准休眠方式


第一表示的是秒,第二个表示的是毫秒

cpp 复制代码
struct timespec req;
// 休眠 2秒 + 500毫秒
req.tv_sec = 2;
req.tv_nsec = 500000000; 

nanosleep(&req, NULL); // 不需要剩余时间,rem传NULL

4,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,单进程用 sigprocmask

2,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);
}
  1. 你定义的 sigset_t set / saveset用户态的普通变量
  • 就是两个存信号的本地盒子,和内核无关;
  • set:你用来临时阻塞信号的工具盒;
  • saveset:你用来备份初始状态的存档盒;
  • 两个盒子互相独立,数据不互通。
  1. 内核里的 进程信号屏蔽字真正控制信号的核心
  • 内核里只有一个,管进程能不能收到信号;
  • sigprocmask 所有操作,都是改这个内核屏蔽字,不是改你的变量!

还原的真实原理(一句话)

SIG_SETMASK 的作用:把「备份盒 saveset 里的数据」,直接覆盖到「内核的信号屏蔽字」里,完成还原。 全程和 set 没有任何关系!

相关推荐
宝桥南山2 小时前
GitHub Copilot - 可以使用Local, Copilot CLI, Cloud等不同方式来运行agent tasks
microsoft·微软·github·aigc·copilot·ai编程
牢七4 小时前
白盒123
linux·windows·microsoft
codkingo14 小时前
一文讲清 Harness Engineering:为什么 Agent 的瓶颈不在模型
microsoft
步步为营DotNet21 小时前
深入剖析.NET 11 中 Microsoft.Extensions.AI 在 AI 驱动后端开发的进阶应用
人工智能·microsoft·.net
Java后端的Ai之路1 天前
Playwright是微软开源的浏览器自动化库:从入门到精通的实战指南
运维·microsoft·自动化·浏览器自动化·playwright
LINgZone22 天前
Java Mock 测试框架 Mockito
java·windows·microsoft
coderlin_2 天前
langchain 基础
microsoft·langchain
王哥儿聊AI2 天前
微软开源神器MarkItDown:一键把PPT/PDF/Excel转成markdown,LLM直呼内行!
人工智能·深度学习·microsoft·机器学习·开源·powerpoint
love530love2 天前
【独家资源】Windows 本地部署微软 BitNet b1.58: Flash Attention + CUDA GPU 加速 (sm_86) + AVX2 优化 + 1.58bit 量化
人工智能·windows·microsoft·llama.cpp·bitnet·flash attention·bitlinear_cpp