写个uint64_t的程序,涉及大小端的转换。 uint64_t x = 0x0123456789ABCDEF; On a 32-bit little-endian processor, it will appear in memory as EF CD AB 89 67 45 23 01 On a 64-bit little-endian processor, it will appear in memory as EF CD AB 89 67 45 23 01. On a 32-bit big-endian processor, it will appear in memory as 01 23 45 67 89 AB CD EF. On a 64-bit big-endian processor, it will appear in memory as 01 23 45 67 89 AB CD EF. 转换涉及#include uint64_t htobe64(uint64_t host_64bits); uint64_t htole64(uint64_t host_64bits); uint64_t be64toh(uint64_t big_endian_64bits); uint64_t le64toh(uint64_t little_endian_64bits); The functions with names of the form "htobenn" convert from host byte order to big-endian order. The functions with names of the form "htolenn" convert from host byte order to little-endian order. The functions with names of the form "benntoh" convert from big-endian order to host byte order. The functions with names of the form "lenntoh" convert from little-endian order to host byte order.、 看了一下头文件,判断是否是小端序,然后采用不同转换方式。涉及bits/byteswap.h头文件。没时间细看了,先记录,以后有时间再看。

Continue

零拷贝不是一个新技术了,之前一直接触不到这么底层的技术,最近看的比较多,所以从代码上研究了一下。 在应用程序做数据传输等操作涉及系统调用,而为了提高性能,就是从减少系统调用次数和减少内核空间和用户空间的数据拷贝次数入手的。 具体的我也没看代码,都是从网上总结学来的。 像mmap方式,是减少了内核空间和用户空间的数据拷贝,使用映射还是指针的能够共享内核空间。但涉及比如把一个文件内容通过网络发送的操作,还涉及内核空间的数据拷贝。 sendfile和splice就是解决内核空间的数据copy的,我看linux手册是page buffer指针的复制,所以没有做数据的copy。指针是通过pipe buffer存储的。 ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count); ssize_t sendfile64(int out_fd, int in_fd, loff_t *offset, size_t count); 这俩的区别是sendfile64适合传送大文件,offset类型也决定了适合做大文件的偏移用。但不仅仅是这里,看源码,sendfile指定offset之后,会设置复制的最大值为MAX_NON_LFS,这个值我没找,但是类型初步判断加文档判断来说,最大不到2G(看文档是不到2G).(后来还是去找了,#define MAX_NON_LFS ((1UL<<31) - 1), 文档写的是0x7ffff000 (2,147,479,552) ) 我看了一下函数实现的源码,尽量只看流程,不去细看实现,看看优化能注意的点。 通过看源码,发现最好是不指定offset这个参数,因为指定这个参数后,会多两次的内核函数调用,涉及用户空间和内核空间的数据拷贝,get_user,put_user,copy_from_user。然后统一调用do_sendfile函数。而如果offset为NULL的时候,在do_sendfile函数里,是通过文件的offset来复制数据的。所以尽量不指定offset是最好的,但如果提前设置文件offset还要涉及系统调用,具体权衡就不知道了。 在do_sendfile里没啥可以细讲的,流程大部分能猜到什么意思。主要最后调用do_splice_direct。 这里有个疑问就是,为什么offset要用指针类型,而不能直接传一个数字,我猜可能是历史遗留问题吧,可能接口没办法变动了。 do_splice_direct函数跟splice是在一个文件,可以差不多猜到,俩的实现原理是一样的了。 do_splice_direct里splice_desc sd定义输出文件的信息。 然后调用了splice_direct_to_actor,这里有一个pipe = current->splice_pipe;这个pipe是在linux的进程管理的pcb(task_struct)中,这里边有一个splice_pipe,用来存储splice()上一次使用的过的pipe。这里是判断如果current->splice_pipe不存在,就新创建一个,然后缓存到current->splice_pipe。然后调用do_splice_to,流程跟splice复制文件到pipe的流程差不多。 splice里直接调用do_splice,这里分三种情况,in和out都有pipe时,调用splice_pipe_to_pipe;in为pipe时调用do_splice_from,out为pipe时调用do_splice_to。这俩单个的也涉及offset的用户空间和内核空间复制的问题。 do_splice_from我直接看的default_file_splice_write,调用splice_from_pipe。里边初始化splice_desc sd,存了要写的文件信息,调用__splice_from_pipe,splice_from_pipe_feed里是将pip内容关联复制到文件。 do_splice_to也是直接看default_file_splice_read,初始化一个结构体splice_pipe_desc spd,看起来是存储pagebuffer信息的,具体看不太懂,也没去查,初始化spd空间,kernel_readv应该是用来吧in的page buffer内容的指针存入spd了,nr_pages_max = PIPE_DEF_BUFFERS这个值是16(看文档在内核版本2.6.35之后,可以通过fcntl的F_GETPIPE_SZ和F_SETPIPE_SZ进行设置),好像是最大页数,最后调用splice_to_pipe(pipe, &spd);好像就是从spd里刚保存的页信息关联复制数据到pipe。 vmsplice支持从用户空间复制数据到pipe,反方向的复制也支持,但是是内存数据的真复制。 tee复制管道内容,从一个复制到另一个 总结一下就是,文件的传输使用sendfile比较好,他会缓存pipe,并且少一次的系统调用。如果用splice,需要先从一个文件到pipe,然后pipe到另一个文件,虽然也没有真正复制,但是系统调用是两次。 splice可以实现类似代理服务器数据转发的功能,使用一个pipe连接两个socket。 上边说的都是PIPESIZE。在Linux 2.6.11之前,PIPESIZE和PIPEBUF实际上是一样的。在这之后,Linux重新实现了一个管道缓存,并将它与写操作的PIPEBUF实现成了不同的概念,形成了一个默认长度为65536字节的PIPESIZE,而PIPEBUF只影响相关读写操作的原子性,一般为page大小,内核每次操作量。PIPESIZE的最大值在/proc/sys/fs/pipe-max-size里进行设置。从Linux 2.6.35之后,在fcntl系统调用方法中实现了F_GETPIPE_SZ和F_SETPIPE_SZ操作,来分别查看当前管道容量和设置管道容量。

Continue

照着别人写的js库,重新写了一个c版本的,也学了一下其中的算法。为什么需要松圆盘采样这个算法进行采样,是因为存随机其实也不是纯随机,随机分布不均匀。而这样生成的伪随机(psuedorandom)数列,很大程度保证了随机的均匀性。 开始以为很难,边写边学发现其实原理不太难。 基本思想就是,初始点可以给定或者随机。 第二步根据初始点,按照一定角度生成不同方向的新点,其中这个角度是关键。通过三角函数,保证生成的新点到初始点的距离为r。 第三步判断新生成点跟周围两个区域的点的距离,保证距离大于r。如果点不合法,则改变角度,重新生成。有一个重试次数保证重新生成上限,如果达到上限还没有找到一个新点,就说明这个点有问题,进行删除。如果合法,生成的点作为一个选择点并插入队列,作为下一次判断的初始点 第四步,队列中有其他生成点,继续上边第二第三步,直到没有生成点可以用。 期间我看js版本生成随机数的时候用的自己写的伪随机生成数,然后我就看了一下用的库,发现里边用了一个Thomas Wang写的 hash生成随机数,又简单看了一下。都是位操作,也懒得找原理看。先记一下,以后用到再看原理吧。

Continue

在setup.py里只需要写很少的代码,所有配置都放在setup.cfg里。如果参数通过setup()传入,以setup.cfg里的配置为准 #!/usr/bin/env python from setuptools import setup setup( setup_requires=['pbr'], pbr=True, ) setup.cfg里配置跟ini文件差不多。 [files]定义代码包里文件的安装目录,其中packages指定要安装的包;namespace_packages制定有命名空间的包;data_files指定要安装的文件的源地址和目的地址; [entry_points]指定模块入口点的运行脚本和模块。主要定义一些控制台脚本,pbr会自动生成这些脚本,做到脚本的跨平台。等号后边就相当于脚本执行调用的函数 随便看了两眼pbr源码: console_scripts就是两行,先import,后执行。 wsgi_scripts比较多,从代码来看,可以直接当脚本启动一个server或者,返回一个app提供给wsgi调用 知道了这个,基本就了解openstack一些模块入口函数怎么找了 看了看neutron service启动命令为 /usr/bin/neutron-server --config-file /etc/neutron/neutron.conf --config-file /etc/neutron/plugin.ini neutron-server脚本在console_scripts里定义。 openstack rpm包打包项目https://github.com/openstack/rpm-packaging

Continue

今天看见了别人总结的,感觉太有用了。 num&0x1, 为1说明是奇数,为0说明是偶数。因为只看二进制位的最后一位 a % b,当底b为2的n次幂的时候,可以改写成 a & (b - 1) 原理也是按位只看最后n位的数据,高位直接不用看。 我这个还专门看了一下有多块,数量级差的太多了, 但初步能看见的时间来说,1亿次差了0.1秒,从时间成本上来说其实差别不大。 看了一下,差了四条汇编指令,还得考虑取数据的时间

Continue

写了个汇编程序,照着别人的写的,其中有个系统调用,然后就去搜了一下linux系统的系统调用表。这一搜不要紧,搜出问题来了。 我找到了两个版本,一个x86-64版本的,一个i386版本的。比如32位的sys_exit系统调用里调用号%eax为1,64位里sys_exit调用号%rax为60,这个不重要。然后我看了一下我的代码,我用了32位的调用号,功能也是32位的。第一反应就是编译的elf是32位的,然后我看了一下readelf -h 64,为64位。后来发现还有个简单命令 直接file 64就能看。然后我就觉得很不可思议。 接着我查了两个版本的头文件在哪里,看怎么用的。学到很多东西。比如查看调用头文件 printf SYS_exit | gcc -include sys/syscall.h -E - echo '#include ' | gcc -E -x c -l echo '#include ' | gcc -E -x c - | egrep '# [0-9]+ ' | awk '{print $3;}' | sort -u 发现是找的是64位的,然后我更糊涂了。主要就这几个文件 # 1 "/usr/include/x86_64-linux-gnu/asm/unistd_64.h" 1 3 4 # 14 "/usr/include/x86_64-linux-gnu/asm/unistd.h" 2 3 4 # 25 "/usr/include/x86_64-linux-gnu/sys/syscall.h" 2 3 4 # 1 "/usr/include/x86_64-linux-gnu/bits/syscall.h" 1 3 4 # 1 "/usr/include/x86_64-linux-gnu/asm/unistd_32.h" 看头文件确实是看是否定义i386,但是我其实是没用头文件的,直接写的中断号,用as编译的代码,没有使用gcc。 然后我查unistd_32.h和unistd_64.h的区别,然后找到原因了。 首先我64位系统,默认都是编译的64位二进制这个没啥问题。汇编和链接的时候可以制定生成32位: as e.s --32 -o 32.o ld 32.o -o 32 我生成32位汇编也是一样能执行的。 其次,32位汇编和64位汇编所使用的系统中断是不一样的。intel和amd都提供了硬件中断,而且还不兼容。为了兼容尽量使用的一下两个: 32位: sysenter sysexit. 64位: syscall sysret. 与这个相对应的是软件中断,那个速度比较慢,也是比较老的遗留下来的。int $0x80。 然后我代码用的是这个老的软中断。软中断用的都是32位的调用号,而且在64位的系统里,同样是可以使用的,因为他只使用低32位的寄存器。所以问题就解决了,我是用的软中断导致的,应该看软中断的调用号,而不是看编译的二进制是64位还是32位。

Continue

有时候经常感觉喘不过气来,容易耸肩紧张,幸好有时候能发现,可以调整一下。 工作生活压力都有,工作突然间赶进度。我不知道为什么一安排进度,我就容易急,一急就容易效率低下。晚上还想干别的,还想学别的,各种选择中的矛盾。睡觉总感觉睡不好还是没睡够,对睡眠不满意。 感觉又开始掉头发严重了,这个还会导致恶性循环。 仔细分析一下,发现工作和生活的压力还是相关联的,只能有侧重点。工作上我要保证进度,但不要求多么快,我做的已经够好了,没必要急。主要还是先把心态调整好。 自己的学习进度,上班时候中午和下班后抽出1到1.5个小时来做或者学习。可以分散一下晚上的压力,也可以学习知识不耽误。还有时间可以写点自己程序。晚上回去按进度来,也不用急,急也没有用。可以抽调试程序,可以安排休息时间,把时间都安排满也是不正确的。比如万一一段时间什么没干,就容易着急引发焦虑,所以需要缓冲的时间。 还有就是技术上写代码的问题,一个想写点高大上的项目,一个想写游戏。写游戏也能锻炼编程能力,能学很多东西,但对于当前的工作职业来说,似乎帮助不太大,以后基本不会转到游戏开发。但你研究技术,比如编译器,操作系统。虽然对目前工作帮助也比较小,但是长远来看,提升会更有用。

Continue

昨天下了一个软件,需要jdk8版本的,我之前开发是用的openjdk11版本的。就想找找怎么安装多个版本的,我之前知道可以知道path环境变量改变,就想找找有没有类似多版本管理的软件。 然后找到一个系统级别的update-alternatives,然后执行update-alternatives --config java,可以进行java版本的选择,我发现我之前安装过java8的jre了版本了。也没有切换,直接复制路径去执行的jar包。 然后就研究了一下update-alternatives 命令。配置文件目录为/var/lib/dpkg/alternatives/java 涉及的原理就是通过文件软链接来实现版本的切换。 ls -alh /usr/bin/java lrwxrwxrwx 1 root root 22 7月 22 2019 /usr/bin/java -> /etc/alternatives/java ls -alh /etc/alternatives/java lrwxrwxrwx 1 root root 43 7月 22 2019 /etc/alternatives/java -> /usr/lib/jvm/java-11-openjdk-amd64/bin/java 安装软件的时候会调用update-alternatives,看文档是在执行postinst 和prerm脚本的时候,自动设置的时候会根据权重选择一个版本或者软件。

Continue

先说一下pyenv的安装方式,只有两步,第一步是将github项目clone到目录,第二步是添加环境变量。所以pyenv完全可以离线安装,比如操作系统版本一致的时候,直接把装好的pyenv环境和virtualenv环境复制到目标系统就可以了。然后添加一下环境变量。 我之前安装过pyenv了,更新pyenv进入pyenv目录后,执行git pull。 然后我复制了一份新的代码,改变环境变量,切换到新pyenv目录。然后卸载之前直接安装的python版本。pyenv uninstall 3.7.3 进入.pyenv_bak,mkdir cache.将源码包放到cache目录下。 进入.pyenv_bak/plugins/python-build/share/python-build目录,编辑3.7.3文件 #require_gcc prefer_openssl11 export PYTHON_BUILD_CONFIGURE_WITH_OPENSSL=1 install_package "openssl-1.1.0j" "https://www.openssl.org/source/old/1.1.0/openssl-1.1.0j.tar.gz#31bec6c203ce1a8e93d5994f4ed304c63ccf07676118b6634edded12ad1b3246" mac_openssl --if has_broken_mac_openssl install_package "readline-8.0" "https://ftpmirror.gnu.org/readline/readline-8.0.tar.gz#e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461" mac_readline --if has_broken_mac_readline if has_tar_xz_support; then install_package "Python-3.7.3" "https://www.python.org/ftp/python/3.7.3/Python-3.7.3.tar.xz#da60b54064d4cfcd9c26576f6df2690e62085123826cff2e667e72a91952d318" ldflags_dirs standard verify_py37 copy_python_gdb ensurepip else install_package "Python-3.7.3" "https://www.python.org/ftp/python/3.7.3/Python-3.7.3.tgz#d62e3015f2f89c970ac52343976b406694931742fbde2fed8d1ce8ebb4e1f8ff" ldflags_dirs standard verify_py37 copy_python_gdb ensurepip fi 改成 install_package "Python-3.7.3" "/root/.pyenv_bak/cache/Python-3.7.3.tar.xz" ldflags_dirs standard verify_py37 copy_python_gdb ensurepip 安装 pyenv install 3.7.3 安装完成后执行pyenv virtualenvwrapper,我发现需要重新安装这个插件,不知道为什么。我之前是安装了的,可能版本问题。 然后基本就可以了。virtualenv也是可以直接复制的,所以基本可以复制分发了。

Continue

主要看pymain_run_filename,是文件执行pymain_open_filename,不是文件赋值fp = stdin,最后执行pymain_run_file(fp, pymain->filename, cf)。看这里应该交互和文件执行都在这一个里边的。 pymain_open_filename就是打开文件返回句柄。 pymain_run_file文件里,主要就是调用PyRun_AnyFileExFlags。下面应该与main.c没关系了,主要在Python/pythonrun.c里了. pymain_repl里调用 PyRun_AnyFileFlags,然后也是调用的PyRun_AnyFileExFlags。 PyRun_AnyFileExFlags里, Py_FdIsInteractive判断是否是交互模式,如果是调用PyRun_InteractiveLoopFlags,里边有个do,这个不看了。如果不是调用PyRun_SimpleFileExFlags, 先调用PyImport_AddModule,interp->modules里插入了__main__,对我没啥用。maybe_pyc_file判断是否是pyc文件,比较了文件后缀,MagicNumber. MAGIC_NUMBER是在Lib/importlib的_bootstrap_external.py文件里定义的,直接写在python文件里了。 如果像pyc文件,关闭文件句柄,重新打开文件,然后调用set_main_loader,再调用run_pyc_file。 如果不像pyc文件,调用PyRun_FileExFlags。 set_main_loader中通过loader_name,从importlib/_bootstrap_external中获取loader,源码为SourceFileLoader,pyc为SourcelessFileLoader,设置到__loader__ run_pyc_file,先比较MagicNumber是否匹配,然后PyMarshal_ReadLastObjectFromFile读取文件,其中的就不看了,不关心。PyEval_EvalCode应该是执行代码了,这个也不看了。 然后就完结了。 import 相关 第一篇里边的pymain_init_sys_path,调用pymain_get_importer,然后调用PyImport_GetImporter,然后调用get_path_importer,然后从path_hooks里找合适的import的方法进行导入 pymain_update_sys_path更新sys.path config_init_module_search_paths里有个Py_GetPath(,这里比较乱,调用了几次_PyPathConfig_Init,我也没仔细看。_PyPathConfig_Calculate里应该是初始化的path,没仔细看。 第三方库的搜索路径添加在initsite,导入Lib/site.py模块,执行addusersitepackages里添加的

Continue