当前位置: 首页 > 编程日记 > 正文

s-systemtap工具使用图谱(持续更新)

整体的学习思维导图如下,后续持续更新完善
在这里插入图片描述

文章目录

        • 安装
        • 简介
        • 执行流程
        • 执行方式
        • stap脚本语法
          • 探针语法
          • API函数
          • 探针举例
          • 变量使用
        • 基本应用
          • 1. 定位函数位置
          • 2. 查看文件能够添加探针的位置
          • 3. 打印函数参数(结构体)
          • 4. 打印函数局部变量
          • 5. 修改函数局部变量(慎重)
          • 6. 打印函数返回时的变量
          • 7. 打印函数调用栈
          • 8. 嵌入C代码
          • 9. 追踪函数流程
          • 10. 跟踪特定进程
          • 11. 查看代码执行路径
          • 12. 查看内核文件函数的执行流程
          • 13. 调试指定模块
          • 14. 抓取`kill -l`相关的信号

环境: 3.10.0-957.5.1.el7.x86_64

安装

centos下安装如下rpm包, 安装前uname -r核对自己的内核版本,下载自己对应的内核版kernel包进行安装即可

rpm -ivh kernel-debuginfo-common-x86_64-3.10.0-123.el7.x86_64.rpm
rpm -ivh kernel-debuginfo-3.10.0-123.el7.x86_64.rpm
rpm -ivh kernel-debug-devel-3.10.0-123.el7.x86_64.rpm

安装systemtap

yum install systemtap-devel-2.4-14.el7.x86_64
yum install systemtap-client-2.4-14.el7.x86_64

安装成功后测试如下:

[root@node1 kernel_compile]# stap -L 'kernel.statement("sys_open")'
kernel.statement("SyS_open@fs/open.c:1063") $filename:long int $flags:long int $mode:long int

简介

systemtap 是一个非常强大的性能诊断以及内核调试工具,相比于传统的内核调试方法(开debug级别,printk,加打印。。):修改代码,编译模块,安装模块,运行模块 的调试过程,systemtap提供友好的内核调试方法,极大得节省了调试损耗的精力。
官网地址:systemtap

执行流程

在这里插入图片描述
主要分为5步,从开始到结果最终是将具有探针行为的Stap脚本转换为内核模块并加载

  • 将stap 脚本转换为解析树
  • 解析stap脚本中的符号
  • 将解析后的结果转为c代码
  • 将c代码编译成对应的ko驱动文件
  • 加载驱动,开始运行(staprun加载生成的模块,stapio将结果输出到终端)

可以使用命令stap -v xxx.stp将解析以及加载过程打印出来

[root@node1 ~]# stap -v -g vfs.stp 
Pass 1: parsed user script and 116 library script(s) using 230588virt/40808res/3156shr/38524data kb, in 190usr/10sys/195real ms.
Pass 2: analyzed script: 43 probe(s), 28 function(s), 1 embed(s), 0 global(s) using 709984virt/91896res/8712shr/81664data kb, in 880usr/190sys/1072real ms.
Pass 3: using cached /root/.systemtap/cache/b0/stap_b066af4ce5ad5d99a19d4ad9ec41fc8e_23076.c
Pass 4: using cached /root/.systemtap/cache/b0/stap_b066af4ce5ad5d99a19d4ad9ec41fc8e_23076.ko
Pass 5: starting run

执行方式

  1. 终端执行stap 命令
    stap -L 'kernel.statement("sys_read")' 查看sys_read系统调用的函数位置
  2. 执行stp脚本
    cat test.stp
    该脚本为打印调用sys_read系统调用的进程名称和进程号
    #!/usr/bin/stap
    probe begin {printf("begin to probe");
    }probe kernel.function("vfs_read") {printf("%s %d\n", execname(),pid());
    }probe end {printf("end to probe");
    }
    
    执行方式 stap test.stp 或者./test.stp

脚本方式执行更加规范,且更容易编写,所以这里建议使用脚本方式进行执行

stap脚本语法

探针语法
kernel.function(pattern)在内核函数的入口处放置探测点,可以获取参数$parm
kernel.function(pattern).return在内核函数返回时的出口处放置探测点,可以获取返回时的参数$parm
kernel.function(pattern).call内核函数的调用入口处放置探测点,获取对应函数信息
kernel.fuction(pattern).inline获取符合条件的内联函数
kernel.function(pattern).exported只选择导出的函数
module(moduname).fuction(pattern)在模块modulename中调用的函数入口处放置探测点
module(moduname).fuction(pattern).return在模块module中调用的函数返回时放置探测点
module(moduname).fuction(pattern).call在模块modulename中调用的函数入口处放置探测点
module(moduname).fuction(pattern).inline在模块modulename中调用的内联函数处放置探测点
kernel.statement(pattern)在内核中的某个地址处增加探针(函数、文件行号)
kernel.statement(pattern).absolute在内核中的某个地址处增加探针(函数、文件行号),精确匹配地址
module(modulename).statement(pattern)在内核模块中的某个地址处增加探针(函数、文件行号)
API函数
函数说明
execname()获取当前进程名称
pid()当前进程的ID
tid()当前线程ID
cpu()当前cpu号
gettimeofday_s()获取当前系统时间,秒
gettimeofday_usec()获取当前系统时间,微秒
ppfunc()获取当前probe的函数名称,可以知道当前probe位于哪个函数
print_backtrace()打印内核函数调用栈
print_ubacktrace()打印用户态函数调用栈
探针举例
探针名称探针含义
begin脚本开始时触发
end脚本结束时触发
kernel.function(“sys_read”)调用sys_read时触发
kernel.function(“sys_read”).call同上
kernel.function(“sys_read”).returnsys_read执行完,返回时触发
kernel.syscall.*调用任何系统调用时触发
kernel.function("*@kernel/fork.c:934")执行到fork.c的934行时触发
module(“ext3”).function(“ext3_file_write”)调用ext3模块中的ext3_file_write时触发
timer.jiffies(1000)每隔1000个内核jiffy时触发一次
timer.ms(200).randomize(50)每隔200毫秒触发一次,带有线性分布的随机附加时间(-50到+50)
变量使用
变量格式使用
$varname引用变量varname
$var->field引用结构的成员变量
$var[N]引用数组的成员变量
&$var变量的地址
@var(“varname”)引用变量varname
@var(“var@src/file.c”)引用src中file.c编译时的全局变量
@var(“var@src/file.c”)->fieldsrc/file.c 中全局结构的成员变量
@var(“var@src/file.c”)[N]src/file.c中全局数据变量
&@var(“var@src/file.c”)引用变量的地址
$var$将变量转为字符串类型
$$vars包含函数所有参数,局部变量,需以字符串类型输出
$$locals包含函数所有局部变量,需以字符串类型输出
$$params包含所有函数参数的变量,需以字符串类型输出

基本应用

1. 定位函数位置

stap -L 'kernel.function("vfs_statfs")'

kernel.function("vfs_statfs@fs/statfs.c:68") $path:struct path* $buf:struct kstatfs* $error:int

打印出函数的所处文件位置及行号,同时-L 参数支持打印函数的参数及类型
在这里插入图片描述
stap -l 'kernel.function("vfs_statfs")' -l参数打印的信息就稍微简略一点

kernel.function("vfs_statfs@fs/statfs.c:68")
2. 查看文件能够添加探针的位置

stap -L 'kernel.statement("*@fs/statfs.c")' 查看statfs.c 文件中能够添加探针的位置

kernel.statement("SYSC_fstatfs64@fs/statfs.c:204") $fd:unsigned int $sz:size_t $buf:struct statfs64* $st:struct kstatfs
kernel.statement("SYSC_fstatfs@fs/statfs.c:195") $fd:unsigned int $buf:struct statfs* $st:struct kstatfs
kernel.statement("SYSC_statfs64@fs/statfs.c:183") $pathname:char const* $sz:size_t $buf:struct statfs64* $st:struct kstatfs
kernel.statement("SYSC_statfs@fs/statfs.c:174") $pathname:char const* $buf:struct statfs* $st:struct kstatfs
kernel.statement("SYSC_ustat@fs/statfs.c:230") $dev:unsigned int $ubuf:struct ustat* $tmp:struct ustat $sbuf:struct kstatfs
kernel.statement("SyS_fstatfs64@fs/statfs.c:204") $fd:long int $sz:long int $buf:long int $ret:long int
kernel.statement("SyS_fstatfs@fs/statfs.c:195") $fd:long int $buf:long int $ret:long int
kernel.statement("SyS_statfs64@fs/statfs.c:183") $pathname:long int $sz:long int $buf:long int $ret:long int
kernel.statement("SyS_statfs@fs/statfs.c:174") $pathname:long int $buf:long int $ret:long int
kernel.statement("SyS_ustat@fs/statfs.c:230") $dev:long int $ubuf:long int $ret:long int
kernel.statement("calculate_f_flags@fs/statfs.c:45")
kernel.statement("do_statfs64@fs/statfs.c:150") $st:struct kstatfs* $p:struct statfs64* $buf:struct statfs64
kernel.statement("do_statfs_native@fs/statfs.c:108") $st:struct kstatfs* $p:struct statfs* $buf:struct statfs
kernel.statement("fd_statfs@fs/statfs.c:97") $fd:int $st:struct kstatfs*
kernel.statement("flags_by_mnt@fs/statfs.c:12") $mnt_flags:int
kernel.statement("flags_by_sb@fs/statfs.c:33") $s_flags:int
kernel.statement("statfs_by_dentry@fs/statfs.c:51") $dentry:struct dentry* $buf:struct kstatfs*
kernel.statement("user_statfs@fs/statfs.c:79") $pathname:char const* $st:struct kstatfs* $path:struct path
kernel.statement("vfs_statfs@fs/statfs.c:68") $path:struct path* $buf:struct kstatfs* $error:int
kernel.statement("vfs_ustat@fs/statfs.c:218") $dev:dev_t $sbuf:struct kstatfs*
3. 打印函数参数(结构体)

vfs_statfs函数如下:

int vfs_statfs(struct path *path, struct kstatfs *buf)
{int error;error = statfs_by_dentry(path->dentry, buf);if (!error)buf->f_flags = calculate_f_flags(path->mnt);return error;
}

如下脚本test.stp

#!/usr/bin/stapprobe begin {printf("begin to probe");
}probe kernel.function("vfs_statfs") { //在调用vfs_statfs处添加探针printf("%s %d\n", execname(),pid());//打印调用vfs_statfs函数的进程名称以及进程号printf("path : %s  buf : %s\n",$path->mnt->mnt_root->d_iname$,$buf$); //打印path参数的结构体成员,以及整个buf结构体probe end {printf("end to probe");
}

运行stap test.stp
输出如下:

[root@node1 ~]# stap test.stp 
begin to probesafe_timer 145286
df 2041244
path : "/"  buf : {.f_type=393, .f_bsize=140207161574688, .f_blocks=94629230035116, .f_bfree=29879, .f_bavail=18446620715516395336, .f_files=18446744071970029248, .f_ffree=4294967295, .f_fsid={...}, .f_namelen=4294967295, .f_frsize=94629230035016, .f_flags=1574682515518227985, .f_spare=[...]}
safe_timer 145286
4. 打印函数局部变量

vfs_statfs函数如下:

int vfs_statfs(struct path *path, struct kstatfs *buf)
{int error;error = statfs_by_dentry(path->dentry, buf);if (!error)buf->f_flags = calculate_f_flags(path->mnt);return error;
}

我想要查看error在函数statfs_by_dentry执行完成之后的结果,那么就在下一行处添加探针
查看如下脚本test.stp

#!/usr/bin/stapprobe begin {printf("begin to probe");
}probe kernel.statement("vfs_statfs@fs/statfs.c:73") {//探测statfs.c中的第73行printf("%s %d\n", execname(),pid());printf("error number is %d\n",$error);
}probe end {printf("end to probe");
}

这里需要注意脚本中在statfs.c的第73行增加探测点,必须填写行号正确,如果73行处没有接下来想要探测的error变量,执行报错

执行stap test.stp输出如下

begin to probe
safe_timer 145286
error number is 0
...
5. 修改函数局部变量(慎重)

我们想要将上一个打印的变量的值从0 更改为其他的数值
查看如下脚本test.stp

#!/usr/bin/stapprobe begin {printf("begin to probe");
}probe kernel.statement("vfs_statfs@fs/statfs.c:73") {printf("%s %d\n", execname(),pid());printf("error number before modify is %d\n",$error);$error=$1; //传入的第一个参数printf("error number after modify is %d\n",$error);
}probe end {printf("end to probe");
}

执行stap -g test.stp 2 将2传入,但是运行的时候需要增加-g参数
输出如下:

begin to probe
df 3173946
error number before modify is 0
error number after modify is 2
...
6. 打印函数返回时的变量

还是举例我们的vfs_statfs,这个函数主要是在ls,df,stat…类似获取文件或者文件夹属性时由系统调用SyS_statfs调用的
函数实现如下

int vfs_statfs(struct path *path, struct kstatfs *buf)
{int error;error = statfs_by_dentry(path->dentry, buf);if (!error)buf->f_flags = calculate_f_flags(path->mnt);return error;
}

这里我们想要查看一下函数返回时error变量的值,查看如下test1.stp

#!/usr/bin/stapprobe begin {printf("begin to probe\n");
}probe kernel.function("vfs_statfs").return { //在调用vfs_statfs处添加探针printf("%s %d\n", execname(),pid());//打印调用vfs_statfs函数的进程名称以及进程号printf("error's return value is %d\n", @entry($error));//打印局部变量需使用@entry($varname)
}probe end {printf("end to probe");
}

输出如下:

begin to probe
safe_timer 752879
error's return value is 0
safe_timer 752879
error's return value is 0
7. 打印函数调用栈

我们想要打印某一个系统调用的调用栈,可以执行如下脚本

#!/usr/bin/stapprobe kernel.function("vfs_statfs") {printf("%s %d\n", execname(),pid());printf("----------------------------------\n");print_backtrace();//打印vfs_statfs在内核态的调用栈printf("----------------------------------\n");
}

如果过程中发现调用栈打印不全,则尝试如下两种办法解决

  1. 执行时增加参数--all-modules,类似如:stap --all-modules test.stp,探测所有的系统模块
  2. 检查stap版本,像我的环境版本为version 2.4/0.158, rpm 2.4-14.el7导致调用栈没有任何打印;需要升级stap的库才行,执行yum install systemtap -y即可,升级之后我的版本version 4.0/0.176, rpm 4.0-10.el7_7,这个时候成功打印内核调用栈

输出如下:

safe_timer 752879
----------------------------------0xffffffff98677430 : vfs_statfs+0x0/0xc0 [kernel]0xffffffff98677725 : user_statfs+0x55/0xa0 [kernel]0xffffffff98677797 : SYSC_statfs+0x27/0x60 [kernel]0xffffffff9867799e : SyS_statfs+0xe/0x10 [kernel]0xffffffff98b5fddb : system_call_fastpath+0x22/0x27 [kernel]0x7fcb8bcfe787
----------------------------------

如果想要通过print_ubacktrace()函数来打印用户态的系统调用,则需要安装glibc的符号调试包glibc-debuginfo从而能够对libc库进行调试

8. 嵌入C代码

如下脚本test.stp,想要获取系统调用了vfs_statfs函数的次数

#!/usr/bin/stap
global count = 0;//全局变量
function getcount:long(task:long) //C函数计算次数
%{int count = (int) STAP_ARG_task;count ++;STAP_RETURN(count);
%}probe begin {printf("begin to probe\n");
}probe kernel.function("vfs_statfs") {printf("%s %d\n", execname(),pid());printf("----------------------------------\n");print_ubacktrace();printf("----------------------------------\n");count = getcount(count);printf("c code caculate the count is %d\n", count);
}probe end {printf("end to probe\n");
}

最终输出如下,可以看到count一直在增加:

[root@node1 ~]# stap -g test.stp 
WARNING: Missing unwind data for a module, rerun with 'stap -d /usr/lib64/libc-2.17.so'
begin to probe
safe_timer 752879
----------------------------------0x7fcb8bcfe787 [/usr/lib64/libc-2.17.so+0xef787/0x3c8000]
----------------------------------
c code caculate the count is 1
safe_timer 752879
----------------------------------0x7fcb8bcfe787 [/usr/lib64/libc-2.17.so+0xef787/0x3c8000]
----------------------------------
c code caculate the count is 2
systemd-journal 564
----------------------------------0x7f6176e2f7b7 [/usr/lib64/libc-2.17.so+0xef7b7/0x3c8000]
----------------------------------
c code caculate the count is 3
safe_timer 752879

以上出现的warning是因为系统并未安装glibc的调试库导致的
关于Stap嵌入C代码需要注意以下几点

  1. 格式上:C语言代码要在每个大括号前加%前缀,是%{…… %} 而不是%{ …… }%;
  2. 获取脚本函数参数要用STAP_ARG_前缀,即getcount函数使用的是STAP_ARG_task来获取传入的count参数
  3. 一般long等返回值用STAP_RETURN,一般字符串则使用snprintf、strncat等方式把字符串复制到STAP_RETVALUE里面
9. 追踪函数流程

我们想要知道当前函数被哪个进程调用,且在该函数处执行了多长时间,可以使用如下脚本进程探测
trace_sys_read.stp

#!/usr/bin/stapprobe begin {printf("begin to probe");
}probe kernel.function("sys_read").call {printf("%s -> %s\n",thread_indent(4),ppfunc());
}
probe kernel.function("sys_read").return {printf("%s <- %s\n",thread_indent(-4),ppfunc());
}

其中thread_indent()函数为/usr/share/systemtap/tapset/indent.stp中实现的一个stap脚本,该函数的功能是增加函数执行时间(微妙),进程名称(pid)打印出来,传入的参数是打印空格的个数
输出如下:

0 msgr-worker-1(2445831):    -> SyS_read
31 ps(2445722):    <- SyS_read
0 ps(2445722):    -> SyS_read
1 ps(2445722):    <- SyS_read
0 sed(2445856):    -> SyS_read
2 sed(2445856):    <- SyS_read
24 msgr-worker-1(2445831):    <- SyS_read

发现Sys_read系统调用被某个进程调用时的开始到返回的执行时间,这个信息对内核代码的流程分析非常有利

10. 跟踪特定进程

除了跟踪具体的某个函数被哪个进程调用之外我们还能够跟踪一个进程所调用过的函数
如下脚本,我们追踪sshd进程调用的系统调用

#!/usr/bin/stap
probe begin {printf("begin to probe\n");
}probe syscall.* //探测所有的系统调用
{procname = execname();if (procname =~ "sshd.*"){ //使用stp脚本中的通配符匹配所有的sshd服务的子进程printf("%s[%d]: %s -> %s\n", procname,pid(),name,ppfunc()); //name为sshd内部函数,ppfunc为该函数调用的系统调用}
}

输出如下,非常直观

sshd[2087388]: write -> SyS_write
sshd[2087388]: clock_gettime -> SyS_clock_gettime
sshd[2087388]: select -> SyS_select
sshd[2087388]: rt_sigprocmask -> SyS_rt_sigprocmask
sshd[2087388]: rt_sigprocmask -> SyS_rt_sigprocmask
sshd[2087388]: clock_gettime -> SyS_clock_gettime
sshd[2087388]: write -> SyS_write
sshd[2087388]: clock_gettime -> SyS_clock_gettime
sshd[2087388]: select -> SyS_select
11. 查看代码执行路径

我们来看一个有意思且非常便捷准确的阅码方式,如下代码为内核处理文件属性的逻辑
在这里插入图片描述
分支相对来说较多,我们想要知道当前系统针对该函数的处理过程,走到哪个分支,则执行如下探测脚本

#!/usr/bin/stapprobe begin
{printf("begin to probe\n");
}probe kernel.statement("do_statfs_native@fs/statfs.c:*")
{printf("%s\n",pp());
}

输出结果如下:

[root@node1 stap]# stap trace_code.stp 
begin to probe
kernel.statement("do_statfs_native@fs/statfs.c:109")
kernel.statement("do_statfs_native@fs/statfs.c:113")
kernel.statement("do_statfs_native@fs/statfs.c:146")
kernel.statement("do_statfs_native@fs/statfs.c:148")

执行过程的具体行号已经打印出来了,此时对照代码即可知道内核处理该函数时如何进行分支处理

12. 查看内核文件函数的执行流程

我们想要查看一个源码文件中函数的执行流程时怎么查看呢?因为上文已经描述了如何跟踪特定进程的执行过程,对代码稍作修改如下

#!/usr/bin/stapprobe begin
{printf("begin to probe\n");
}probe module("ceph").function("*@mds_client.c").call{ //监控mds_client.c文件中所有的函数,调用时打印printf("%s -> %s \n", thread_indent(4), ppfunc());
}probe module("ceph").function("*@mds_client.c").return{//监控mds_client.c文件中所有的函数,返回时打印printf("%s <- %s \n", thread_indent(-4), ppfunc());
}

输出如下:

[root@node3 stap]# stap trace_mdsclient.stp 
begin to probe0 kworker/3:2(582080):    -> delayed_work 6 kworker/3:2(582080):        -> __ceph_lookup_mds_session 8 kworker/3:2(582080):            -> get_session 10 kworker/3:2(582080):            <- get_session 12 kworker/3:2(582080):        <- __ceph_lookup_mds_session 14 kworker/3:2(582080):        -> con_get 16 kworker/3:2(582080):            -> get_session 16 kworker/3:2(582080):            <- get_session 18 kworker/3:2(582080):        <- con_get

如果我们想要监控指定的进程在指定的内核文件中的执行过程,可以使用如下代码进行监控

#!/usr/bin/stapprobe begin
{printf("begin to probe\n");
}probe module("ceph").function("*@mds_client.c").call{if(target() == pid()) {//使用target过滤我们输入的进程ipprintf("%s -> %s \n", thread_indent(4), ppfunc());}
}probe module("ceph").function("*@mds_client.c").return{if(target() == pid()) {printf("%s <- %s \n", thread_indent(-4), ppfunc());}
}

执行如下stap -x pid trace_mdsclient.stp 即可对指定的进程idpid的过滤,输出其在mds_client.c代码中的执行流程

13. 调试指定模块

如我想要调试ceph模块,基本的脚本编写语法我们已经在前文脚本语法中提到过,类似如下module("ceph").function(""),整体的调试方式和我们前面描述的内核调试方式类似
需要注意调试模块之前需要将模块拷贝到目录/usr/lib/modules/`uname -r`/extra/ 之下才能够正常调试
如何检测能够正常调试一个自己的模块呢,使用如下命令
这里使用ceph模块中的ceph_statfs来做测试
stap -l 'module("ceph").function("ceph_statfs")',显示如下输出即可

module("ceph").function("ceph_statfs@fs/ceph/super.c:55")
14. 抓取kill -l相关的信号

想要抓取系统中哪个进程发送的kill信号

global target
global signal
probe nd_syscall.kill
{target[tid()] = uint_arg(1);signal[tid()] = uint_arg(2);
}probe nd_syscall.kill.return
{if (target[tid()] != 0) {printf("%-6d %-12s %-5d %-6d %6d\n", pid(), execname(),signal[tid()], target[tid()], int_arg(1));delete target[tid()];delete signal[tid()];}
}

stap test.stp会抓取发送kill信号的进程

相关文章:

2014 Super Training #8 C An Easy Game --DP

原题&#xff1a;ZOJ 3791 http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode3791 题意&#xff1a;给定两个0-1序列s1, s2&#xff0c;操作t次&#xff0c;每次改变m个位置&#xff0c;求把s1改变为s2的方法总数。解法&#xff1a; DP&#xff0c;s1和s2哪些位置…

qq分享组件 android,移动端,分享插件

移动端&#xff0c;分享插件发布时间&#xff1a;2018-06-26 10:03,浏览次数&#xff1a;762最近有一个活动页需要在移动端浏览器分享网页到微信&#xff0c;QQ。虽然每一个浏览器都有分享到微信的能力&#xff0c;但不是每个都提供接口供网页来调用。及时有提供&#xff0c;浏…

MySQL中更改表操作

2019独角兽企业重金招聘Python工程师标准>>> 添加一列&#xff1a; alter table t_stu add tel char(20); 删除一个列&#xff1a; alter table t_stu drop column tel; 添加唯一约束&#xff1a; alter table t_stu add constraint uk_srname unique(scode); 添加主…

Maven-环境配置

二更 可算搞好了&#xff0c;除了下面外&#xff0c;我找到了setting.xml里面的东西&#xff0c;给出来就好了&#xff0c;简单就是mvn -v弄好后&#xff0c;setting.xml里面写好的话&#xff0c;直接加入&#xff0c;然后让eclipse下载jar包 然后就可以运行网上的基本项目了 1…

分布式存储(ceph)技能图谱(持续更新)

一下为个人结合其他人对分布式存储 所需的技能进行总结&#xff0c;绘制成如下图谱&#xff0c;方便针对性学习。 这里对分布式存储系统接触较多的是ceph,所以在分布式存储系统分支上偏向ceph的学习。 如有分类有问题或者分支不合理&#xff0c;欢迎大家批评指正&#xff0c;目…

android如何查看方法属于哪个类,Android Studio查看类中所有方法和属性

css-关于位置当你设置一个你想要相对的模块为relative 你这个模块为absolute 则你的这个absolute会相对relative的那个模块进行移动.微信公众平台自动回复wechatlib&period;jar的生成及wechatlib解析微信公众平台出来有一段时日了,官方提供的自动回复的接口调用大致是这么些…

读javascript高级程序设计03-函数表达式、闭包、私有变量

一、函数声明和函数表达式 定义函数有两种方式&#xff1a;函数声明和函数表达式。它们之间一个重要的区别是函数提升。 1.函数声明会进行函数提升&#xff0c;所以函数调用在函数声明之前也不会报错&#xff1a; test(); function test(){ alert(1); } 2.函数表达式不会进行函…

集成公司内部的多个子系统(兼容B/S和C/S),实现单点登录功能的多系统的统一入口功能...

有一句话也挺有意思的&#xff0c;一直在模仿但从未超越过&#xff0c;文章里的技术也都是相对简单的技术&#xff0c;但是实实在在能解决问题&#xff0c;提高效率。现在人都懒得瞎折腾&#xff0c;能多简单就多简单&#xff0c;谁都不希望总是做一些重复的工作&#xff0c;我…

mysql无法远程连接

在mysql的mysql数据库下&#xff1a; select user,host from user;(查看&#xff0c;没有本机的访问权限) grant all privileges on *.* to root"xxx.xxx.xxx.xxx" identified by "密码";(xx为本机ip,%为所有IP) flush privileges; select user,host from …

哈希表的分类,创建,查找 以及相关问题解决

总体的hash学习导图如下&#xff1a; 文章目录定义分类字符hash排序hash链式hash&#xff08;解决hash冲突&#xff09;创建链式hash查找指定数值STL map(hash)哈希分类 完整测试代码应用&#xff08;常见题目&#xff09;1. 回文字符串&#xff08;Longest Palindrome&#x…

android 自定义音乐圆形进度条,Android自定义View实现音频播放圆形进度条

本篇文章介绍自定义View配合属性动画来实现如下的效果实现思路如下&#xff1a;根据播放按钮的图片大小计算出圆形进度条的大小根据音频的时间长度计算出圆形进度条绘制的弧度通过Handler刷新界面来更新圆形进度条的进度具体实现过程分析&#xff1a;首先来看看自定义View中定义…

jsp error-page没有生效

1、首页检查web.xml中的配置&#xff0c;确保路径是正确的 <error-page> <error-code>404</error-code> <location>/error.jsp</location> </error-page> 2、然后再检查error.jsp文件内容是否有问题&#xff0c;比如只有<head>&…

CTO(首席技术官)

CTO&#xff08;首席技术官&#xff09;英文Chief Technology Officer&#xff0c;即企业内负责技术的最高负责人。这个名称在1980年代从美国开始时兴。起于做很多研究的大公司&#xff0c;如General Electric&#xff0c;AT&T&#xff0c;ALCOA&#xff0c;主要责任是将科…

把数组排成最小的数

题目 输入一个正整数数组&#xff0c;把数组里所有数字拼接起来排成一个数&#xff0c;打印能拼接出的所有数字中最小的一个。例如输入数组{3&#xff0c;32&#xff0c;321}&#xff0c;则打印出这三个数字能排成的最小数字为321323。 思路 一  需要找到字典序最小的哪个排列…

shell脚本自动执行,top命令无输出

shell脚本在系统启动时推后台自动执行&#xff0c;发现其中/usr/bin/top -n 1 -c -b -u ceph 命令并无输出 但是系统启动之后手动执行脚本&#xff0c;&推后台脚本中的top仍然能够正常输出&#xff0c;仅仅是系统发生重启&#xff0c;该功能就不生效了 stackoverflow 推荐…

0709 C语言常见误区----------函数指针问题

1.函数指针的定义 对于函数 void test(int a, int b){ // } 其函数指针类型是void (* ) (int , int)&#xff0c; 注意这里第一个括号不能少&#xff0c; 定义一个函数指针&#xff0c;void (* pfunc)(int , int) ,其中pfunc就是函数指针类型&#xff0c; 它指向的函数类型必须…

android 定时换图片,android 视频和图片切换并进行自动轮播

刚入android没多久&#xff0c;遇到的比较郁闷的问题就是子线程主线程的问题&#xff0c;更改UI界面&#xff0c;本想做一个图片的轮播但是比较简单&#xff0c;然后就试试实现视频跟图片切换播放进行不停的循环播放。也用过不少控件&#xff0c;但是知其然不知其所以然&#x…

Win8:Snap 实现

Win8允许分屏的操作&#xff0c;所以我们必须让我们App能对Snap模式视图做出反应&#xff0c;这样也便于通过Store的审核。如果项目中在Snap展现的功能不大&#xff0c;我们可以仅用一张logo代替&#xff0c;类似系统的商店应用。 我的项目实现效果&#xff1a; 实现思路是在你…

ping命令使用及其常用参数

PING (Packet Internet Groper)&#xff0c;因特网包探索器&#xff0c;用于测试网络连接量检查网络是否连通&#xff0c;可以很好地帮助我们分析和判定网络故障。Ping发送一个ICMP(Internet Control Messages Protocol&#xff09;即因特网信报控制协议&#xff1b;回声请求消…

g-gdb工具使用图谱(持续更新)

如下为整个GDB的学习导图

android bitmap 转drawable,android Drawable转换成Bitmap失败

错误代码&#xff1a;08-07 06:42:30.482 28497-28497/app.tianxiayou E/AndroidRuntime﹕ FATAL EXCEPTION: mainProcess: app.tianxiayou, PID: 28497java.lang.RuntimeException: Unable to start activity ComponentInfo{app.tianxiayou/app.tianxiayou.AppInfoActivity}: …

微软职位内部推荐-Software Development Engineer II

微软近期Open的职位:Job Title:Software Development EngineerIIDivision: Server & Tools Business - Commerce Platform GroupWork Location: Shanghai, ChinaAre you looking for a high impact project that involves processing of billions of dollars, hundreds of …

Lync与Exchange 2013 UM集成:Exchange 配置

有点长时间没有更新文章了&#xff0c;也是由于工作的原因确实忙不过来&#xff0c;好在博客上还有这么多朋友支持&#xff0c;非常的欣慰啊。很久没有给大家带来新东西&#xff0c;真的非常抱歉&#xff0c;今天跟大家带来的是Lync Server 2013与Exchange Server 2013统一消息…

「Java基本功」一文读懂Java内部类的用法和原理

内部类初探 一、什么是内部类&#xff1f; 内部类是指在一个外部类的内部再定义一个类。内部类作为外部类的一个成员&#xff0c;并且依附于外部类而存在的。内部类可为静态&#xff0c;可用protected和private修饰&#xff08;而外部类只能使用public和缺省的包访问权限&#…

从一致性hash到ceph crush算法演进图谱(持续更新)

参考文档&#xff1a; https://ceph.com/wp-content/uploads/2016/08/weil-crush-sc06.pdf Ceph剖析&#xff1a;数据分布之CRUSH算法与一致性Hash

[原]unity3d之http多线程异步资源下载

郑重声明&#xff1a;转载请注明出处 U_探索 本文诞生于乐元素面试过程&#xff0c;被面试官问到AssetBundle多线程异步下载时&#xff0c;愣了半天&#xff0c;同样也被深深的鄙视一回&#xff08;做了3年多u3d 这个都没用过&#xff09;&#xff0c;所以发誓要实现出来填补一…

android首页图片轮播效果,Android_Android自动播放Banner图片轮播效果,先看一下效果图支持本地图 - phpStudy...

Android自动播放Banner图片轮播效果先看一下效果图支持本地图片以及网络图片or本地网络混合。使用方式&#xff1a;android:id"id/banner"android:layout_width"match_parent"android:layout_height"230dip">核心代码&#xff1a;int length …

mongodb 入门

在NOSQL的多个数据库版本中,mongodb相对比较成熟,把学mongodb笔记整理在这&#xff0c;方便以后回顾。这笔记预计分三部分&#xff1a; 一&#xff0c;基础操作&#xff0c;二、增删改查详细操作&#xff0c;三、高级应用。一、在linux在安装mongodb&#xff0c;在linux下安装m…

springboot 学习笔记(三)

&#xff08;三&#xff09;用jar包启动springboot项目 1、首先需要在pom文件中添加依赖&#xff0c;spring-boot-starter-parent包含有打包的默认配置&#xff0c;如果要修改的话要可以进行重新定义&#xff0c;具体内容参考https://docs.spring.io/spring-boot/docs/2.1.1.RE…

搜索:深搜/广搜 获取岛屿数量

题目描述&#xff1a; 用一个二维数组代表一张地图&#xff0c;这张地图由字符“0”与字符“1”组 成&#xff0c;其中“0”字符代表水域&#xff0c;“1”字符代表小岛土地&#xff0c;小岛“1”被 水“0”所包围&#xff0c;当小岛土地“1”在水平和垂直方向相连接时&#xf…