首页
关于
Search
1
Lua使用调试库hook函数调用
333 阅读
2
傻瓜式快速搭建l2tp
329 阅读
3
Linux内核数据结构kfifo小结(TODO)
269 阅读
4
傻瓜式安装chatgpt-web工具
256 阅读
5
游戏邮件系统数据设计因素
243 阅读
项目技术
项目思考
开发环境
数据库
编程语言
生活与阅读
哲学
登录
Search
标签搜索
nodejs
npm
韭菜笔记
累计撰写
55
篇文章
累计收到
0
条评论
首页
栏目
项目技术
项目思考
开发环境
数据库
编程语言
生活与阅读
哲学
页面
关于
搜索到
55
篇与
的结果
关于IRQ亲和性资料整理笔记
中断处理也是可以设置更倾向在哪个CPU核心处理的。碰到这个问题是来自于中断机制一次QQ聊天中。在中断被频繁触发时CPU会处理不过来,常见的现象是网卡不能满负荷的跑,达到一定流量后出现了丢包。这又要从网卡接收数据开始说起。 网卡驱动启动时就会注册硬中断回调同时准备好sk_buffer。在网络包来的时候NIC会通过DMA(Direct Memory Access,不经过CPU的内存复制,也称零拷贝)把数据包从NIC内部缓存区复制到之前准备好的sk_buffer中(如果此时sk_buffer不足是会丢包,对应ifconfig命令overruns指标)。之后NIC产生一个硬中断。硬中断处理程序只会处理关键性的、短时间内可以处理完的工作,剩余耗时较长工作,会放到中断之后,由软中断来完成。硬中断也被称为上半部分。CPU收到硬中断,根据中断描述符表(Interrupt Descriptor Table,IDT)找到处理函数并调用。最后触发NET_RX_SOFTIRQ软中断处理sk_buffer中的数据(如果软中断处理不及时,轮询时CPU对应的接收队列超过netdev_max_backlog也会丢包),然后再交给内核协议栈解析处理,协议栈会得到sk_buffer,经过处理后进入socket buffer, 最后用户态取得数据。对于多队列(RSS特性)网卡, 每个队列对对应一个中断号。因为中断号的不同,可以设置不同的CPU亲和性。这样在单核CPU无法跟上网卡速度的问题就通过多核解决了。对于单队列网卡一般使用RPS+RFS配合使用。RPS(Receive Packet Steering)帮助单队列网卡将其产生的SoftIRQ分派到多个CPU内核进行处理。RFS(Receive Flow Steering)能够保证处理软中断和处理应用程序是同一个CPU,达到局部性原理。通过ethtool -S ens5可以看到各队列信息queue_{idx}_*。ls /sys/class/net/ens5/queues/在机器CPU处理很多计算任务,CPU压力紧张的情况下扩展多队列网卡是没有效果的,多队列网卡就是要通过消耗更多的CPU来提高网络性能,此时CPU压力更大,会影响计算业务。最佳实践方式是通过设定中断亲和性(修改/proc/irq/{IRQ_ID}/smp_affinity配置)把业务进程的亲和性(taskset命令)绑定到不同的CPU上,这样不会相互影响。IRQ_ID可以通过grep ens5 /proc/interrupts得到,具体设置方法网上很多。除了top可以查询中断处理统计,mpstat -P ALL 1更加具体
2021年06月26日
67 阅读
0 评论
0 点赞
套接字编程中bind和reuse实践
一般的开发中, 需要使用bind的情况是在服务端。今天遇到一个特殊的使用场景,客户端因为安全性要求,连接时不能使用内核分配的端口,需要使用指定的端口和服务器连接。自然就用到了bind接口。从bind又引入了reuse属性的使用。这里使用gethostbyname_r来做域名到IP地址的转换(curl也是如此),另外像云风skynet则使用getaddrinfo接口,后者用法更简单一些。当然他们都阻塞的。以下是验证代码://anker<lichengman2006@gmail.com> @2021-06-17T20:18:16 //gcc client.c -o client //client www.google.com 80 9090 1 #include <stdio.h> #include <stdlib.h> #include <netdb.h> #include <string.h> #include <arpa/inet.h> #include <unistd.h> int main(int argc, char *argv[]) { if (argc != 5) { fprintf(stderr,"usage: \n" "client $serverIp $serverPort $clientPort $reuse\n" "example: ./client www.google.com 80 9090 1\n"); exit(1); } int h_errnop; int HOSTENT_SIZE = 9000; struct hostent *buf = NULL; struct hostent *he = NULL; char *hostname = argv[1]; int port = atoi(argv[2]); buf = calloc(1, HOSTENT_SIZE); if(!buf) exit(1); // int gethostbyname_r(const char *name, // struct hostent *ret, char *buf, size_t buflen, // struct hostent **result, int *h_errnop); // name:Here name is either a hostname, or an IPv4 address in standard dot notation (as for inet_addr(3)), or an IPv6 address in colon (and possibly dot) notation // ret:which will be filled in on success, and a temporary work buffer buf of size buflen // result: will point to the result on success. In case of an error or if no entry is found result will be NULL. The functions return 0 on success and a nonzero error number on failure gethostbyname_r(hostname, (struct hostent *)buf, (char *)buf + sizeof(struct hostent), HOSTENT_SIZE - sizeof(struct hostent), &he, /* DIFFERENCE */ &h_errnop); if(!he){ /* failure */ he = NULL; /* set return code to NULL */ free(buf); exit(1); } char *curr; struct sockaddr_in serveraddr; for(int i = 0; (curr = he->h_addr_list[i]) != NULL; i++) { //if(he->h_addrtype == AF_INET6){ if(he->h_addrtype == AF_INET){ // only support ipv4 serveraddr.sin_family = he->h_addrtype; memcpy(&serveraddr.sin_addr, curr, sizeof(struct in_addr)); serveraddr.sin_port = htons((unsigned short)port); char s[64] = {'\0'}; inet_ntop(AF_INET, &(serveraddr.sin_addr), s, 64); printf("get ip:%s\n", s); }else{ // just print //struct sockaddr_in6 _addr; //memcpy(&_addr.sin6_addr, curr, sizeof(struct in6_addr)); struct sockaddr_in6 *paddr = (struct sockaddr_in6*)curr; char s[64] = {'\0'}; inet_ntop(AF_INET6, &(paddr->sin6_addr), s, 64); printf("get ipv6:%s\n", s); } } int sockfd; if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); } int enable_reuse = atoi(argv[4]); if(enable_reuse > 0){ enable_reuse = 1; if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable_reuse, sizeof(int)) < 0){ perror("set reuse failed\n"); exit(1); }else{ printf("set reuse succ!\n"); } } int bindport = atoi(argv[3]); struct sockaddr_in clientaddr; clientaddr.sin_family = AF_INET; clientaddr.sin_addr.s_addr = htonl(INADDR_ANY); clientaddr.sin_port = htons(bindport); if ((bind(sockfd, (struct sockaddr*)&clientaddr, sizeof(clientaddr))) != 0) { printf("socket bind failed...\n"); exit(0); } else printf("Socket successfully binded.. port:%d\n", bindport); if (connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr)) == -1) { perror("connect"); exit(1); }else{ printf("connect succ!\n"); sleep(100); close(sockfd); } }通过代码可以验证客户端可以通过bind来使用指定端口连接服务器,以下均验证bind时,reuse属性导致情况:如不使用reuse模式,在第一个程序结束时,本地端口还处于TIME_WAIT状态。此时第2个客户端无论是否reuse都是无法再次使用的,提示绑定失败。在reuse模式下导致的TIME_WAIT状态,第2个客户端可以通过reuse模式使用,但是非reuse模式会绑定失败。在reuse模式下,可以同时启动多个客户端。但前提是连接不同的服务器地址。因为如果连接两个服务器地址相同>,此时网络五元组相同,协议栈根本无法区分,会报connect: Cannot assign requested address错误。理论上讲(因为没有亲自证实),reuse模式对listen是无效的。
2021年06月26日
79 阅读
0 评论
0 点赞
关于线程栈大小设置实验
每个线程的创建都是有消耗的。这里关注线程栈大小和设置。在Redis的IO线程中也是有设置线程栈为4MB大小的。另外验证的ulimit -s设置时,只能设置为比当前更小,不能扩大。//anker<lichengman2006@gmail.com> @2021-06-20T18:12:11 //gcc thread_stack_size.c -o thread_stack_size -D_GNU_SOURCE -lpthread #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <pthread.h> size_t get_current_thread_stacksize(){ pthread_attr_t attr; size_t stacksize; //pthread_attr_init(&attr); pthread_getattr_np(pthread_self(), &attr); int err = pthread_attr_getstacksize(&attr,&stacksize); if(err != 0){ perror("cannot get thread stack size\n"); exit(err); } pthread_attr_destroy(&attr); return stacksize; } void *thread_entry(void *arg) { char * msg = (char *)arg; size_t stacksize = get_current_thread_stacksize(); printf("get %s stack size(bytes):%ld\n", msg, stacksize); } int main(int argc, char *argv[]){ if(argc != 2){ printf("usage:" "thread_stack_size $sizInMB\n" ); exit(1); } size_t main_stacksize = get_current_thread_stacksize(); printf("get main thread stack size(bytes):%ld\n", main_stacksize); pthread_t threadId; if (pthread_create(&threadId, NULL, thread_entry, "first thread") != 0) { perror("cannot create sub thread"); exit(errno); } pthread_join(threadId, NULL); pthread_attr_t attr; pthread_attr_init(&attr); if ( 0 != pthread_attr_setstacksize(&attr, 1024*1024*atoi(argv[1]))){ //if ( 0 != pthread_attr_setstacksize(&attr, 1024*1024*10)){ perror("set stack size failed."); exit(errno); } pthread_t threadId2; if (pthread_create(&threadId2, &attr, thread_entry, "second thread") != 0) { printf("cannot create sub thread"); exit(errno); } pthread_attr_destroy(&attr); pthread_join(threadId2, NULL); return 0; }pthread_attr_setstacksize函数说明提到:The stack size attribute determines the minimum size (in bytes) that will be allocated for threads created using the thread attributes object attr.他设置的是栈大小的最小值,另外系统也是有限制栈的16KB最小值PTHREAD_STACK_MIN (16384) bytes.> ulimit -s 4096 > ./thread_stack_size 5 get main thread stack size(bytes):4194304 get first thread stack size(bytes):4194304 get second thread stack size(bytes):5242880 > ./thread_stack_size 3 get main thread stack size(bytes):4190208 get first thread stack size(bytes):4194304 get second thread stack size(bytes):4194304留意以下事实:子线程和主线程的栈大小是不一致的。pthread_attr_setstacksize是设置比ulimit还小的值,系统会自动采用最大的最小值。更详细的策略可以参考glibc的allocatestack.c中的allocate_stack函数,其中有对齐等考虑因素。
2021年06月26日
29 阅读
0 评论
0 点赞
僵尸进程实验
僵尸进程产生的原因是当前还没有其他进程来回收他的尸体。每个进程死亡后系统都有部分资源被持有没有被释放。保留的资源有PID和退出码以及CPU时间、内存使用量等子进程一生的信息。如果他的父进程也已经死亡,则会由系统的INIT进程接管收尸。因为进程已经死亡,使用Kill是无法杀死他的。可能通过杀死其父进程来结束。每个进程在死亡时都会通过SIGCHILD信号通知父进程。#include <stdio.h> #include <stdbool.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <signal.h> #include <sys/wait.h> void signal_handler(int signo){ printf("this is in the sig handler:%d\n", signo); } bool register_signal(int signo){ struct sigaction act; struct sigaction oldact; act.sa_handler = signal_handler; sigemptyset(&act.sa_mask); act.sa_flags = 0; if(sigaction(signo, &act, &oldact) < 0) return false; return true; } int main(){ pid_t pid; pid = fork(); if(pid<0) { /* 如果出错 */ printf("error occurred!\n"); } else if(pid==0) { /* 子进程 */ exit(0); } else { /*进程对SIGCHLD默认动作是忽略,对SIGUSR1,SIGUSR2是终止 * 这里选用kill -SIGUSR1 $parentPID 演示。 * 如果不注册会因为终止进程而无法观察到sleep被中断 * */ register_signal(SIGUSR1); /* 父进程 */ perror("going to sleep\n"); sleep(300); /* 休眠300秒 */ perror("sleep end\n"); wait(NULL); /* 获取僵尸进程的退出信息 */ } return 0; }运行结果:[anker@ms lab]$ ./a.out going to sleep : Success this is in the sig handler:10 sleep end : Interrupted system call
2021年06月26日
13 阅读
0 评论
0 点赞
关于近期博客事件反思
本周服务器故障,导致笔记丢失。回顾事件,有多个原因导致损失。服务器应该保持有开发账号和维护账号,他们进行不同的职责。在开发账号出现问题不能登录时,可以由维护账号来挽救。事故后不应该草率决定重建。应该先确认备份的有效。在重建后再决定是否清理。类似云机器可以先回收机器但保留磁盘,在使用新服务器后挂载旧磁盘。切换环境要慎重。从Centos8切换到Ubuntu20导致环境不熟悉,导致重建各种低效。没有持续有效的备份机制。一直是有想起就备份,但是没想到最近的一次备份还是两年前。针对以上问题:服务器创建两个权限账号。对笔记进行每天定期备份。使用bypy工具,每天凌晨自动打包自动上传百度云盘备份。
2021年06月26日
30 阅读
0 评论
0 点赞
1
...
10
11