首页
关于
Search
1
Lua使用调试库hook函数调用
429 阅读
2
傻瓜式快速搭建l2tp
385 阅读
3
游戏邮件系统数据设计因素
328 阅读
4
Linux内核数据结构kfifo小结(TODO)
313 阅读
5
傻瓜式安装chatgpt-web工具
296 阅读
项目技术
项目思考
开发环境
数据库
编程语言
生活与阅读
哲学
登录
Search
标签搜索
nodejs
npm
Typecho
累计撰写
55
篇文章
累计收到
34
条评论
首页
栏目
项目技术
项目思考
开发环境
数据库
编程语言
生活与阅读
哲学
页面
关于
搜索到
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日
73 阅读
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日
87 阅读
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日
30 阅读
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