一,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异步信号

  • 信号会随时中断程序执行;
  • 信号处理函数中不能调用大部分库函数(如 printfmalloc);
  • 多任务下极易出现数据混乱、竞态条件

b. sleep不可随便使用的原因

先给核心结论

  1. sleep 绝对不能随便用:因为老式系统的 sleep 底层 = alarm + pause,会和你代码里的 alarm 彻底冲突,直接导致定时失效、程序崩溃;
  2. 移植性极差sleep 没有统一标准实现,不同系统(老 Unix / 新 Linux/Windows)底层完全不一样,行为天差地别;
  3. 两个实现的本质区别是否使用信号 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,核心思想和优缺点

一、核心

经典流量限流整形算法:请求像水一样不停倒入漏桶,桶底固定匀速漏水处理请求,桶满则直接丢弃超额请求,强行把突发乱流压成平稳匀速流。

二、通俗模型比喻

  1. 漏桶:服务器 / 接口最大积压承载上限
  2. 注水:客户端突发高频请求(忽快忽慢)
  3. 漏水:系统固定 CPU/IO 处理速率(恒定不变)
  4. 溢出丢弃:超出承载上限的无效请求直接拒绝

三、四大核心参数

  1. 桶容量 capacity:最大能积压的请求总数
  2. 当前水量 water:正在排队积压的未处理请求
  3. 入流速率:随机、无规律的用户请求速率
  4. 出水速率 rate:系统固定匀速处理速率(不可提速)

四、标准工作流程

  1. 定时漏水:依托计时器(贴合你学的 alarm/time),固定周期匀速减少积压请求,正常处理业务
  2. 新请求到来:判断当前水量 < 桶容量?
    • 是:水量 + 1,排队等待漏水处理
    • 否:请求溢出,直接丢弃 / 返回限流错误
  3. 全程约束:无论入流多突发,出水永远匀速平稳

五、关键特性(区别令牌桶)

  1. 强整形:绝对匀速输出,彻底抹平流量尖峰
  2. 无突发容忍:哪怕系统空闲,也不能一次性快速处理批量请求
  3. 简单可靠:只靠计数 + 定时,无复杂逻辑,极低性能开销

六、优缺点精简

优点

  1. 实现极简,适配嵌入式 / Linux 底层限流
  2. 严格保护后端,不会被突发流量瞬间压垮
  3. 流量输出极致平滑,适配 TCP 拥塞控制

缺点

  1. 灵活性差,无法应对短时间合法业务突发
  2. 桶满直接丢包,无排队重试兜底

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 字节,严格匀速
  1. 绝对匀速:无任何突发流量,输出速度完全恒定;
  2. 阻塞限流:未到放行时间,程序会等待,不消耗多余资源;
  3. 简单可靠:用 Linux 信号 + 标志位实现,轻量无依赖;
  4. 速率可控:修改 CPS 的值,就能直接调整每秒输出的字节数。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐