Linux 高性能 获取时间

基础 常用时间函数 从The Linux Programming Interface 上看,获取时间最常用的函数是 gettimeofday,常见的中间件也是用该函数么? 1 2 3 #include <sys/time.h> int gettimeofday(struct timeval *tv, struct timezone *tz); Returns 0 on success, or –1 on error Redis 扫了下redis的命令,发现redis有个time command. 调用该command能够返回当前服务器的时间戳,从redis源码 )上看,redis也是调用了gettimeofday获取当前的时间 1 2 3 4 5 6 7 8 9 10 void timeCommand(client *c) { struct timeval tv; gettimeofday(&tv,NULL); addReplyArrayLen(c,2); addReplyBulkLongLong(c,tv.tv_sec); addReplyBulkLongLong(c,tv.tv_usec); }… Continue reading Linux 高性能 获取时间

深入理解零拷贝技术

hi,大家好,内存拷贝是比较耗时操作,零拷贝是常用优化手段,今天分享的文章就是Linux系统零拷贝技术,Kafka和MySQL开源组件都用到这个核心技术,希望大家可以掌握。 DMA 与零拷贝技术 注意事项:除了 Direct I/O,与磁盘相关的文件读写操作都有使用到 page cache 技术。 作者:Spongecaptain 原文:https://spongecaptain.cool/SimpleClearFileIO/ 1. 数据的四次拷贝与四次上下文切换 很多应用程序在面临客户端请求时,可以等价为进行如下的系统调用: File.read(file, buf, len); Socket.send(socket, buf, len); 例如消息中间件 Kafka 就是这个应用场景,从磁盘中读取一批消息后原封不动地写入网卡(NIC,Network interface controller)进行发送。 在没有任何优化技术使用的背景下,操作系统为此会进行 4 次数据拷贝,以及 4 次上下文切换,如下图所示: 如果没有优化,读取磁盘数据,再通过网卡传输的场景性能比较差: 4 次 copy: CPU 负责将数据从磁盘搬运到内核空间的 Page Cache 中; CPU 负责将数据从内核空间的 Socket 缓冲区搬运到的网络中; CPU 负责将数据从内核空间的 Page Cache 搬运到用户空间的缓冲区; CPU 负责将数据从用户空间的缓冲区搬运到内核空间的 Socket 缓冲区中; 4 次上下文切换: read… Continue reading 深入理解零拷贝技术

从创建进程到进入main函数,发生了什么?

前几天,读者群里有小伙伴提问:从进程创建后,到底是怎么进入我写的main函数的? 今天这篇文章就来聊聊这个话题。 首先先划定一下这个问题的讨论范围:C/C++语言 这篇文章主要讨论的是操作系统层面上对于进程、线程的创建初始化等行为,而像Python、Java等基于解释器、虚拟机的语言,如何进入到main函数执行,这背后的路径则更长(包含了解释器和虚拟机内部的执行流程),以后有机会再讨论。所以这里就重点关注C/C++这类native语言的main函数是如何进入的。 本文会兼顾叙述Linux和Windows两个主要平台上的详细流程。 创建进程 第一步,创建进程。 在Linux上,我们要启动一个新的进程,一般通过fork + exec系列函数来实现,前者将当前进程“分叉”出一个孪生子进程,后者负责替换这个子进程的执行文件,来执行子进程的新程序文件。 这里的fork、exec系列函数,是操作系统提供给应用程序的API函数,在其内部最终都会通过系统调用,进入操作系统内核,通过内核中的进程管理机制,来完成一个进程的创建。 操作系统内核将负责进程的创建,主要有下面几个工作要做: 创建内核中用于描述进程的数据结构,在Linux上是task_struct 创建新进程的页目录、页表,用于构建新进程的内存地址空间 在Linux内核中,由于历史原因,Linux内核早期并没有线程的概念,而是用任务:task_struct来描述一个程序的执行实例:进程。 在内核中,一个任务对应就是一个task_struct,也就是一个进程,内核的调度单元也是一个个的个task_struct。 后来,多线程的概念兴起,Linux内核为了支持多线程技术,task_struct实际上表示的变成了一个线程,通过将多个task_struct合并为一组(通过该结构内部的组id字段)再来描述一个进程。因此,Linux上的线程,也称为轻量级进程。 系统调用fork的一个重要使命就是要去创建新进程的task_struct结构,创建完成后,进程就拥有了调度单元。随后将开始可以参与调度并有机会获得执行。 加载可执行文件 通过fork成功创建进程后,此时的子进程和父进程相当于一个细胞进行了有丝分裂,两个进程“几乎”是一模一样的。 而要想子进程执行新的程序,在子进程中还需要用到exec系列函数来实现对进程可执行程序的替换。 exec系列函数同样是系统调用的封装,通过调用它们,将进入内核sys_execve来执行真正的工作。 这个工作细节比较多,其中有一个重要的工作就是加载可执行文件到进程空间并对其进行分析,提取出可执行文件的入口地址。 我们使用C、C++等高级语言编写的代码,最终通过编译器会编译生成可执行文件,在Linux上,是ELF格式,在Windows上,称之为PE文件。 无论是ELF文件还是PE文件,在各自的文件头中,都记录了这个可执行文件的指令入口地址,它指示了程序该从哪里开始执行。 这个入口指向哪里,是我们的main函数吗?这里卖一个关子,先来解决在这之前的一个问题:进程创建后,是如何来到这个入口地址的? 不管在Windows还是Linux上,应用线程都会经常在用户空间和内核空间来回穿梭,这可能出现在以下几种情况发生时: 系统调用 中断 异常 从内核返回时,线程是如何知道自己从哪里进来的,该回到应用空间的哪里去继续执行呢? 答案是,在进入内核空间时,线程将自动保存上下文(其实就是一些寄存器的内容,比如指令寄存器EIP)到线程的堆栈上,记录自己从哪里来的,等到从内核返回时,再从堆栈上加载这些信息,回到原来的地方继续执行。 前面提到,子进程是通过sys_execve系统调用进入到内核中的,在后面完成可执行文件的分析后,拿到了ELF文件的入口地址,将会去修改原来保存在堆栈上的上下文信息,将EIP指向ELF文件的入口地址。这样等sys_execve系统调用结束时,返回到用户空间后,就能够直接转到新的程序入口开始执行代码。 所以,一个非常重要的特点是:exec系列函数正常情况下是不会返回的,一旦进入,完成使命后,执行流程就会转向新的可执行文件入口。 另外需要提一下的是,在Linux上,除了ELF文件,还支持一些其他格式的可执行文件,如MS-DOS、COFF 除了二进制的可执行文件,还支持shell脚本,这个情况下将会将脚本解释器程序作为入口来启动 从ELF入口到main函数 上面交代了,一个新的进程,是如何执行到可执行文件的入口地址的。 同时也留了一个问题,这个入口地址是什么?是我们的main函数吗? 这里有一个简单的C程序,运行起来后输出经典的hello world: #include <stdio.h> int main() {     printf(“hello, world!\n”);     return 0; } 通过gcc编译后,生成了一个ELF可执行文件,通过readelf指令,可以实现对ELF文件的分析,这里可以看到ELF文件的入口地址是0x400430: 随后,我们通过反汇编神器,IDA打开分析这个文件,看一下位于0x400430入口的地方是什么函数? 可以看到,入口地方是一个叫做 _start 的函数,并不是我们的main函数。 在_start的结尾,调用了 __libc_start_main 函数,而这个函数,位于libc.so中。 你可能疑惑,这个函数是哪里冒出来的,我们的代码中并没有用到它呢?… Continue reading 从创建进程到进入main函数,发生了什么?

例用lsof命令恢复linux误删除文件(正在使用的文件)

首先我们要知道lsof这个命令是做什么用的,简单的讲就是列出当前打开的程序,进程或者用户所使用的所有文件(是正在使用的)   在这之前我们要搞清楚一个理论知识:那就是句柄(这里我用缓存来理解的,这样更容易理解)及空间释放问题   打个比方:在生产环境中,有维护人员正在用 tail 命令查看一个日志,然后另一个维护人员用rm -rf 命令把这个日志给删除了。但是这时其实你的空间还没有真正的释放,因为为还有用户占用着这个文件,所以在缓存中这个文件还是存在的。那么我们就可以通这个缓存把文件给恢复回来。下面我们开始实验   1.首先我们用下面的命令查看我们的 less /var/log/messages 然后再按我们键盘上的 ctrl + z 先暂停(注意,这时候我们的 less 命令占用着这个文件)   less /var/log/messages 复制代码 2.然后我们用 rm -rf /var/log/messages 删除这个文件   rm -rf /var/log/messages 复制代码 3.你可以 ls 看下我们的这个文件已经被删除了,这时候不要惊慌,我们现在来恢复,首先,我们要使用 lsof 命令找到 less 这个程序占用着哪些文件   lsof | grep deleted   #过滤出已经被删除的所有文件(这里被删除的意思是程序在占用且被删除),在最后一列找到我们需要恢复的文件 复制代码 4.刚上面的命令的结果如下: $ lsof | grep deleted #过滤出less这个程序所占用的所有文件,在最后一列找到我们需要恢复的文件 rsyslogd   903    root    4w… Continue reading 例用lsof命令恢复linux误删除文件(正在使用的文件)