博客重载记录

博客重载记录

  • 流控算法实现
  • open系统调用流程
  • 二分查找

前言: 有时候看了一些比较好的文章,过几天就忘了,想想不如自己实现一遍博客代码或按博客结构自己写一遍,加深印象,但把别人的内容改个名字变成自己的博客,有点不太好,故全写在这个博客中,权当个人记录。

流控算法实现

参考文章:
流量控制-从原理到实现
面试官:来,年轻人!请手撸5种常见限流算法!

计数器

#include 
class Counter {
  Counter(int max_request_number, int period) : request_number_(0), max_request_number_(max_request_number), las_update_time_(time(nullptr)), period_(period) {}

  bool IsVaild() {
    time_t now = time(nullptr);
    if (now - las_update_time_ >= period_) {  // 超过时间片,重置请求数
      request_number_ = 1;
      las_update_time_ = now;
    } else {  // 更新请求数
      request_number_++;
    }
    return request_number_ > max_request_number_;
  }

 private:
  int request_number_;      // 当前请求数量
  int max_request_number_;  // 最大请求数量
  time_t las_update_time_;  // 上次更新时间
  int period_;              // 时间片长度
};

滑动窗口
变量定义与第一个博客稍有不同,没怎么懂以下这一句
now_ms - window_size_ - start_time_

#include 
class SlidingWindow {
  SlidingWindow(int window_size_sum, int max_req_num, int split_num) {
    window_size_sum_ = window_size_sum;
    max_req_num_ = max_req_num;
    window_size_ = window_size_sum_ / split_num;
    split_num_ = split_num;
    counter_.resize(split_num_);
    window_start_time_ = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
  }

  bool IsVaild() {
    uint64_t now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();  // 当前时间
    int window_num = (now_ms - window_start_time_) / window_size_;                                                                         // 经过了多少个窗口
    MoveWindow(window_num);                                                                                                                // 移动窗口
    int sum = std::accumulate(counter_.begin(), counter_.end(), 0);                                                                        // 计算窗口请求数总和
    if (sum >= max_req_num_) {                                                                                                             // 超出阈值,返回失败
      return false;
    }
    counter_[index_]++;  // 当前窗口请求数加一
    return true;
  }

  void MoveWindow(int window_num) {
    if (window_num == 0) {
      return;
    }
    window_num = std::min(window_num, split_num_);
    for (int i = 0; i < window_num; i++) {  // 将跳过的窗口请求数设为0
      index_ = (index_ + 1) % split_num_;
      counter_[index_] = 0;
    }
    window_start_time_ += window_num * window_size_;  // 更新窗口开始时间
  }

 private:
  int window_size_sum_;         // 窗口大小总和(ms)
  int max_req_num_;             // 窗口最大请求数总和
  int window_size_;             // 窗口大小
  int split_num_;               // 窗口数目
  std::vector<int> counter_;    // 各窗口请求数目
  uint64_t window_start_time_;  // 当前窗口开始时间
  int index_;                   // 当前窗口索引
};

漏桶
博客重载记录_第1张图片

#include 
using Request = int;
class LeakyBucket {
 public:
  LeakyBucket(int capacity, int rate) : capacity_(capacity), rate_(rate), left_water_(0), last_update_time_(time(nullptr)) {}

  bool IsVaild() {
    uint64_t now = time(nullptr);
    left_water_ = std::max<int>(0, left_water_ - (now - last_update_time_) * rate_);  // 更新剩余水量
    last_update_time_ = now;
    if (left_water_ >= capacity_) {
      return false;
    }
    left_water_++;
    return true;
  }

 private:
  int capacity_;               // 桶容量
  int rate_;                   // 每秒漏掉的水量
  int left_water_;             // 剩余水量
  uint64_t last_update_time_;  // 上次更新时间
};

int main() {
  std::mutex mu;                     // 保护请求队列
  std::queue<Request> reqest_queue;  // 请求队列
  LeakyBucket controller(5, 10);     // 桶容量为5,100ms漏一个
  bool live_ = true;                 // 控制线程生命周期
  auto poll = [&]() {                // 隔100ms取一个请求
    printf("enter poll thread\n");
    while (live_) {
      mu.lock();
      if (!reqest_queue.empty()) {
        Request req = reqest_queue.front();
        reqest_queue.pop();
        printf("%d request success\n", req);
      }
      mu.unlock();
      std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
    printf("leave poll thread\n");
  };
  auto push = [&]() {  // 50-100ms生成请求插入队列
    Request req = 0;
    printf("enter push thread\n");
    while (live_) {
      req++;
      bool res = controller.IsVaild();
      if (res) {
        mu.lock();
        reqest_queue.emplace(req);
        mu.unlock();
      } else {
        printf("%d request failed\n", req);
      }
      int sleep_time = rand() % 50 + 50;
      std::this_thread::sleep_for(std::chrono::milliseconds(sleep_time));
    }
    printf("leave push thread\n");
  };

  std::thread poll_thread(poll);
  std::thread push_thread(push);
  std::this_thread::sleep_for(std::chrono::minutes(1));  // 执行1分钟
  live_ = false;
  poll_thread.join();
  push_thread.join();
}

输出结果:
博客重载记录_第2张图片
博客重载记录_第3张图片

令牌环
博客重载记录_第4张图片

#include 
class TokenBucket {
 public:
  TokenBucket(int capacity, int rate) : token_num_(0), capacity_(capacity), rate_(rate), last_update_time_(time(nullptr)) {}

  bool IsVaild() {
    time_t now = time(nullptr);
    token_num_ = std::min<int>(capacity_, token_num_ + (now - last_update_time_) * rate_);  // 更新令牌数目
    last_update_time_ = now;
    if (token_num_ <= 0) {
      return false;
    }
    token_num_--;
    return true;
  }

 private:
  int token_num_;            // 当前令牌数
  int capacity_;             // 令牌桶容量
  int rate_;                 // 每秒生成的令牌数目
  time_t last_update_time_;  // 上次更新的时间
};

open系统调用流程

参考文章:走马观花: Linux 系统调用 open 七日游(一)
内核代码版本:4.19.279

linux系统调用简要介绍

操作系统为在用户态运行的进程与硬件设备进行交互提供了一组接口。在应用程序与硬件设置一个额外层具有很多优点。首先这使得编程更加容易,把用户从学习硬件设备的低级编程特性中解放出来;其次,这极大地提升了系统的安全性,因为内核在试图满足某个请求之前在接口级就可以检查这种请求的正确性;最后,更重要的是这些接口使得程序更具有移植性,因为只要内核所提供的一组接口相同,那么在任一内核之上就可以正确地编译和执行程序。Unix系统通过向内核发出系统调用(system call)实现了用户态进程和硬件设备之间的大部分接口。

《深入理解linux内核》——系统调用

【纯干货】linux内核——系统调用原理及实现
一次系统调用的完整执行过程如下:

  1. 通过特定指令发出系统调用(int $0x80、sysenter、syscall)
  2. CPU从用户态切换到内核态,进行一些寄存器和环境设置
  3. 调用system_call内核函数,通过系统调用号获取对应的服务例程
  4. 调用系统调用处理例程
  5. 使用特定指令从系统调用返回用户态(iret、sysexit、sysret)

系统调用号定义:

# arch/x86/entry/syscalls/syscall_64.tbl
#
# 64-bit system call numbers and entry vectors
#
# The format is:
#    
#
# The __x64_sys_*() stubs are created on-the-fly for sys_*() system calls
#
# The abi is "common", "64" or "x32" for this file.
#
0	common	read			__x64_sys_read
1	common	write			__x64_sys_write
2	common	open			__x64_sys_open
3	common	close			__x64_sys_close
4	common	stat			__x64_sys_newstat
5	common	fstat			__x64_sys_newfstat
6	common	lstat			__x64_sys_newlstat
7	common	poll			__x64_sys_poll
8	common	lseek			__x64_sys_lseek
9	common	mmap			__x64_sys_mmap
10	common	mprotect		__x64_sys_mprotect
11	common	munmap			__x64_sys_munmap
12	common	brk			__x64_sys_brk
13	64	rt_sigaction		__x64_sys_rt_sigaction
14	common	rt_sigprocmask		__x64_sys_rt_sigprocmask

系统调用分派表(dispatch table) sys_call_table:

// arch/x86/entry/syscall_64.c
// SPDX-License-Identifier: GPL-2.0
/* System call table for x86-64. */

#include 
#include 
#include 
#include 
#include 

/* this is a lie, but it does not hurt as sys_ni_syscall just returns -EINVAL */
extern asmlinkage long sys_ni_syscall(const struct pt_regs *);
#define __SYSCALL_64(nr, sym, qual) extern asmlinkage long sym(const struct pt_regs *);
#include 
#undef __SYSCALL_64

#define __SYSCALL_64(nr, sym, qual) [nr] = sym,

asmlinkage const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
	/*
	 * Smells like a compiler bug -- it doesn't work
	 * when the & below is removed.
	 */
	[0 ... __NR_syscall_max] = &sys_ni_syscall,
#include 
};

// 相关定义
#ifdef CONFIG_X86_64
typedef asmlinkage long (*sys_call_ptr_t)(const struct pt_regs *);
#else
typedef asmlinkage long (*sys_call_ptr_t)(unsigned long, unsigned long,
					  unsigned long, unsigned long,
					  unsigned long, unsigned long);

/*
 * Non-implemented system calls get redirected here.
 */
asmlinkage long sys_ni_syscall(void)
{
	return -ENOSYS;
}

系统调用最多6个额外参数(除系统调用号)

相关结构体与函数声明

int open(const char * pathname, int flags, mode_t mode);
pathname: 打开文件的路径名
flags: 访问模式的一些标志
mode: 文件创建后的许可权位掩码

O_RDONLY :以只读方式打开文件
O_WRONLY :以只写方式打开文件
O_RDWR :以可读可写方式打开文件
O_CREAT:如果 pathname 参数指向的文件不存在则创建此文件
O_DIRECTORY :如果 pathname 参数指向的不是一个目录,则调用 open 失败
O_EXCL :此标志一般结合 O_CREAT 标志一起使用,用于专门创建文件
O_NOFOLLOW :如果 pathname 参数指向的是一个符号链接,将不对其进行解引用,直接返回错误
O_TRUNC :调用 open 函数打开文件的时候会将文件原本的内容全部丢弃,文件大小变为 0
O_APPEND :调用 open 函数打开文件,当每次使用 write()函数对文件进行写操作时,都会自动把文件当前位置偏移量移动到文件末尾, 从文件末尾开始写入数据,也就是意味着每次写入数据都是从文件末尾开始
O_LARGEFILE:大型文件(文件大于2GB)
O_CLOEXEC:句柄在fork子进程后执行exec时就关闭

// 进程描述符
struct task_struct {
  /* Filesystem information: */
  struct fs_struct *fs;  // 文件系统信息

  /* Open file information: */
  struct files_struct *files;  // 打开文件信息
  ...
};


struct path {
  struct vfsmount *mnt;
  struct dentry *dentry;
} __randomize_layout;

struct fs_struct {
  int users;
  spinlock_t lock;
  seqcount_t seq;
  int umask;  // 打开文件时候默认的文件访问权限
  int in_exec;
  struct path root, pwd;  // 根目录与当前目录的目录项对象与安装点
} __randomize_layout;

/*
 * The default fd array needs to be at least BITS_PER_LONG,
 * as this is the granularity returned by copy_fdset().
 */
#define NR_OPEN_DEFAULT BITS_PER_LONG

struct fdtable {
  unsigned int max_fds;          // 最大容纳文件描述符数
  struct file __rcu **fd;        /* current fd array */
  unsigned long *close_on_exec;  // exec时需关闭的文件描述符
  unsigned long *open_fds;       // bit为1表示相应位置被占用,表示有效的文件对象
  unsigned long *full_fds_bits;
  struct rcu_head rcu;
};
/*
 * Open file table structure
 */
struct files_struct {
  /*
   * read mostly part
   */
  atomic_t count;
  bool resize_in_progress;
  wait_queue_head_t resize_wait;

  struct fdtable __rcu *fdt;  // 指向对应的文件描述符表
  struct fdtable fdtab;       // 默认文件描述符表
  /*
   * written part on a separate cache line in SMP
   */
  spinlock_t file_lock ____cacheline_aligned_in_smp;
  unsigned int next_fd;  // 所分配的最大描述符加一
  // 文件描述符数小于NR_OPEN_DEFAULT时fdt直接引用以下对象
  unsigned long close_on_exec_init[1];
  unsigned long open_fds_init[1];
  unsigned long full_fds_bits_init[1];
  struct file __rcu *fd_array[NR_OPEN_DEFAULT];
};

sys_open声明与定义
linux Kernel代码艺术——系统调用宏定义

// 函数声明
asmlinkage long sys_open(const char __user *filename, int flags, umode_t mode);
// 函数定义
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
{
	if (force_o_largefile())
		flags |= O_LARGEFILE;

	return do_sys_open(AT_FDCWD, filename, flags, mode);
}

SYSCALL_DEFINE3作用

#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)

#define SYSCALL_DEFINE_MAXARGS 6

#define SYSCALL_DEFINEx(x, sname, ...)                                         \
	SYSCALL_METADATA(sname, x, __VA_ARGS__)                                \
	__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)

SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
宏定义展开之后就成为:
asmlinkage long sys_open(const char __user *filename, int flags, umode_t mode);

宏展开后3表示系统参数个数,使用宏展开是为了将参数都当成long类型,进而执行寄存器的符号位扩展
asmlinkage
asmlinkage指定用堆栈传参数,用意是什么?寄存器不是更快吗
asmlinkage作用就是告诉编译器,函数参数不是用用寄存器来传递,而是用堆栈来传递的;
相关回答:

像楼上各位所说,用户调用syscall的时候,参数都是通过寄存器传进来的。中间内核由把所有的参数压栈了, 所以这个asmlinkage可以通过到gcc,恰好可以用正确的调用方式取到参数。
内核前面的那些统一处理很重要,这样后端真正的的syscall 实现函数就可以得到统一的调用方式了,而不是之间面对不同的abi。确实比较方便了。
不然每个syscall函数里面都要自己去处理不同abi,多很多重复代码。
当然也可以考虑在这个统一的处理的时候,把参数全部按照一定的规范放到寄存器。 但这个方法不能在所有的cpu架构上面都做的到。
我觉得这里的选择,“统一”要比这个“寄存器传参”要重要。 从用户切换到内核,要做大量的处理。相比较其他部分,这点参数的开销实在不算什么。

二分查找

写对二分查找不是套模板并往里面填空,需要仔细分析题意

#include 
using namespace std;
int BinaryFindEqual(const vector<int>& data, int target) {  // 等于
  // 结果可能出现在[0,n-1]区间,不存在时返回-1
  int low = 0;
  int high = data.size() - 1;
  while (low < high) {
    int mid = (low + high) / 2;  // 靠近low high都可以
    if (data[mid] == target) {
      return mid;
    } else if (data[mid] > target) {
      high = mid - 1;
    } else {
      low = mid + 1;
    }
  }
  // 压缩区间至[low,high], low==high
  if (data[low] == target) {
    return low;
  }
  return -1;
}

int BinaryFindFirstGreaterEqual(const vector<int>& data, int target) {  // 第一次大于等于
  // 结果可能落在[0,n]
  int low = 0;
  int high = data.size();
  while (low < high) {
    int mid = (low + high) / 2;  // 靠近low
    if (data[mid] >= target) {
      high = mid;
    } else {
      low = mid + 1;
    }
  }
  // 压缩区间至[low,high], low==high
  return low;
}

int BinaryFindFirstGreater(const vector<int>& data, int target) {  // 第一次大于
  // 结果可能落在[0,n]
  int low = 0;
  int high = data.size();
  while (low < high) {
    int mid = (low + high) / 2;  // 靠近low
    if (data[mid] > target) {
      high = mid;
    } else {
      low = mid + 1;
    }
  }
  // 压缩区间至[low,high], low==high
  return low;
}

int BinaryFindLastLesserEqual(const vector<int>& data, int target) {  // 最后一次小于等于
  // 结果可能落在[-1,n-1]
  if (data[0] > target) {
    return -1;
  }
  int low = 0;
  int high = data.size() - 1;
  while (low < high) {
    int mid = (low + high + 1) / 2;  // 靠近high
    if (data[mid] > target) {
      high = mid - 1;
    } else {
      low = mid;
    }
  }
  // 压缩区间至[low,high], low==high
  return low;
}

int BinaryFindLastLesser(const vector<int>& data, int target) {  // 最后一次小于
  // 结果可能落在[-1,n-1]
  if (data[0] >= target) {
    return -1;
  }
  int low = 0;
  int high = data.size() - 1;
  while (low < high) {
    int mid = (low + high + 1) / 2;  // 靠近high
    if (data[mid] >= target) {
      high = mid - 1;
    } else {
      low = mid;
    }
  }
  // 压缩区间至[low,high], low==high
  return low;
}

int BinaryFindFirstEqual(const vector<int>& data, int target) {  // 第一次等于
  // 结果可能落在[0,n-1],不存在时返回-1
  int low = 0;
  int high = data.size() - 1;
  while (low < high) {
    int mid = (low + high) / 2;  // 靠近low
    if (data[mid] > target) {
      high = mid - 1;
    } else if (data[mid] < target) {
      low = mid + 1;
    } else {
      high = mid;
    }
  }
  // 压缩区间至[low,high], low==high
  if (data[low] == target) {
    return low;
  }
  return -1;
}

int BinaryFindLastEqual(const vector<int>& data, int target) {  // 最后一次等于
  // 结果可能落在[0,n-1],不存在时返回-1
  int low = 0;
  int high = data.size() - 1;
  while (low < high) {
    int mid = (low + high + 1) / 2;  // 靠近high
    if (data[mid] > target) {
      high = mid - 1;
    } else if (data[mid] < target) {
      low = mid + 1;
    } else {
      low = mid;
    }
  }
  // 压缩区间至[low,high], low==high
  if (data[low] == target) {
    return low;
  }
  return -1;
}

int BinaryFindEqualCompare(const vector<int>& data, int target) {  // 返回第一次相等的下标
  for (int i = 0; i < data.size(); i++) {
    if (data[i] == target) {
      return i;
    }
  }
  return -1;
}

int BinaryFindFirstGreaterEqualCompare(const vector<int>& data, int target) {
  for (int i = 0; i < data.size(); i++) {
    if (data[i] >= target) {
      return i;
    }
  }
  return data.size();
}

int BinaryFindFirstGreaterCompare(const vector<int>& data, int target) {
  for (int i = 0; i < data.size(); i++) {
    if (data[i] > target) {
      return i;
    }
  }
  return data.size();
}

int BinaryFindLastLesserEqualCompare(const vector<int>& data, int target) {
  for (int i = data.size() - 1; i >= 0; i--) {
    if (data[i] <= target) {
      return i;
    }
  }
  return -1;
}

int BinaryFindLastLesserCompare(const vector<int>& data, int target) {
  for (int i = data.size() - 1; i >= 0; i--) {
    if (data[i] < target) {
      return i;
    }
  }
  return -1;
}

int BinaryFindFirstEqualCompare(const vector<int>& data, int target) {
  for (int i = 0; i < data.size(); i++) {
    if (data[i] == target) {
      return i;
    }
  }
  return -1;
}

int BinaryFindLastEqualCompare(const vector<int>& data, int target) {
  for (int i = data.size() - 1; i >= 0; i--) {
    if (data[i] == target) {
      return i;
    }
  }
  return -1;
}

using FindFunc = function<int(const vector<int>&, int)>;
void TestBinaryFind(const vector<int>& data, const vector<int>& targets, FindFunc test_fn, FindFunc right_fn, string testname) {
  for (int target : targets) {
    int res1 = test_fn(data, target);
    int res2 = right_fn(data, target);
    if (res1 != res2) {
      cout << "wrong anwer." << endl;
      cout << "res1: " << res1 << "  res2: " << res2 << endl;
    }
  }
  cout << testname << " complete." << endl;
}

int main() {
  vector<int> unique_data;
  default_random_engine e;
  uniform_int_distribution<int> u(1, 100);
  e.seed(time(0));
  for (int i = 5; i < 95; i++) {
    if (u(e) > 50) {
      unique_data.emplace_back(i);
    }
  }
  vector<int> targets;
  for (int i = 0; i <= 100; i++) {
    targets.emplace_back(i);
  }
  cout << "unique data test:" << endl;
  TestBinaryFind(unique_data, targets, BinaryFindEqual, BinaryFindEqualCompare, "BinaryFindEqual");
  TestBinaryFind(unique_data, targets, BinaryFindFirstGreaterEqual, BinaryFindFirstGreaterEqualCompare, "BinaryFindFirstGreaterEqual");
  TestBinaryFind(unique_data, targets, BinaryFindFirstGreater, BinaryFindFirstGreaterCompare, "BinaryFindFirstGreater");
  TestBinaryFind(unique_data, targets, BinaryFindLastLesserEqual, BinaryFindLastLesserEqualCompare, "BinaryFindLastLesserEqual");
  TestBinaryFind(unique_data, targets, BinaryFindLastLesser, BinaryFindLastLesserCompare, "BinaryFindLastLesser");

  vector<int> repeat_data;
  for (int i = 5; i < 95; i++) {
    while (u(e) > 30) {
      repeat_data.emplace_back(i);
    }
  }
  cout << "repeat data test:" << endl;
  TestBinaryFind(repeat_data, targets, BinaryFindFirstGreaterEqual, BinaryFindFirstGreaterEqualCompare, "BinaryFindFirstGreaterEqual");
  TestBinaryFind(repeat_data, targets, BinaryFindFirstGreater, BinaryFindFirstGreaterCompare, "BinaryFindFirstGreater");
  TestBinaryFind(repeat_data, targets, BinaryFindLastLesserEqual, BinaryFindLastLesserEqualCompare, "BinaryFindLastLesserEqual");
  TestBinaryFind(repeat_data, targets, BinaryFindLastLesser, BinaryFindLastLesserCompare, "BinaryFindLastLesser");
  TestBinaryFind(repeat_data, targets, BinaryFindFirstEqual, BinaryFindFirstEqualCompare, "BinaryFindFirstEqual");
  TestBinaryFind(repeat_data, targets, BinaryFindLastEqual, BinaryFindLastEqualCompare, "BinaryFindLastEqual");
}

你可能感兴趣的:(学习记录,个人记录)