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

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
    ", 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
", execname(),pid());//打印调用vfs_statfs函数的进程名称以及进程号printf("path : %s  buf : %s
",$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
", execname(),pid());printf("error number is %d
",$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
", execname(),pid());printf("error number before modify is %d
",$error);$error=$1; //传入的第一个参数printf("error number after modify is %d
",$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
");
}probe kernel.function("vfs_statfs").return {  //在调用vfs_statfs处添加探针printf("%s %d
", execname(),pid());//打印调用vfs_statfs函数的进程名称以及进程号printf("error's return value is %d
", @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
", execname(),pid());printf("----------------------------------
");print_backtrace();//打印vfs_statfs在内核态的调用栈printf("----------------------------------
");
}

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

  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
");
}probe kernel.function("vfs_statfs") { printf("%s %d
", execname(),pid());printf("----------------------------------
");print_ubacktrace();printf("----------------------------------
");count = getcount(count);printf("c code caculate the count is %d
", count);
}probe end { printf("end to probe
");
}

最终输出如下,可以看到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
",thread_indent(4),ppfunc());
}
probe kernel.function("sys_read").return { printf("%s <- %s
",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
");
}probe syscall.* //探测所有的系统调用
{ procname = execname();if (procname =~ "sshd.*"){  //使用stp脚本中的通配符匹配所有的sshd服务的子进程printf("%s[%d]: %s -> %s
", 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
");
}probe kernel.statement("do_statfs_native@fs/statfs.c:*")
{ printf("%s
",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
");
}probe module("ceph").function("*@mds_client.c").call{  //监控mds_client.c文件中所有的函数,调用时打印printf("%s -> %s 
", thread_indent(4), ppfunc());
}probe module("ceph").function("*@mds_client.c").return{ //监控mds_client.c文件中所有的函数,返回时打印printf("%s <- %s 
", 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
");
}probe module("ceph").function("*@mds_client.c").call{ if(target() == pid()) { //使用target过滤我们输入的进程ipprintf("%s -> %s 
", thread_indent(4), ppfunc());}
}probe module("ceph").function("*@mds_client.c").return{ if(target() == pid()) { printf("%s <- %s 
", 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
", pid(), execname(),signal[tid()], target[tid()], int_arg(1));delete target[tid()];delete signal[tid()];}
}

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

更多相关:

  • 草色新雨中, 松声晚窗里。之前我们学习 Power Query 都是用鼠标就完成了很多复杂的操作。虽然 PowerQuery 已经将大部分常用功能内置成到功能区。基本能完成我们大部分的报表自动化功能。但是总有些复杂的或者个性化的问题是开发团队没有预先想到的,这时我们就需要学习 M 语言。一、M 语言在哪里?M语言的函数公式有三个地...

  • 前言从2020年3月份开始,计划写一系列文档--《小白从零开始学编程》,记录自己从0开始学习的一些东西。第一个系列:python,计划从安装、环境搭建、基本语法、到利用Django和Flask两个当前最热的web框架完成一个小的项目第二个系列:可能会选择Go语言,也可能会选择Vue.js。具体情况待定,拭目以待吧。。。基本概念表达式表...

  • 1.1函数1.1.1什么是函数函数就是程序实现模块化的基本单元,一般实现某一功能的集合。函数名:就相当于是程序代码集合的名称参数:就是函数运算时需要参与运算的值被称作为参数函数体:程序的某个功能,进行一系列的逻辑运算return 返回值:函数的返回值能表示函数的运行结果或运行状态。1.1.2函数的作用函数是组织好的,可重复使用的,用来...

  • 原标题:基于Python建立深度神经网络!你学会了嘛?图1 神经网络构造的例子(符号说明:上标[l]表示与第l层;上标(i)表示第i个例子;下标i表示矢量第i项)单层神经网络图2 单层神经网络示例神经元模型是先计算一个线性函数(z=Wx+b),接着再计算一个激活函数。一般来说,神经元模型的输出值是a=g(Wx+b),其中g是激活函数(...

  • 在学习MySQL的时候你会发现,它有非常多的函数,在学习的时候没有侧重。小编刚开始学习的时候也会有这个感觉。不过,经过一段时间的学习之后,小编发现尽管函数有很多,但是常用的却只有那几个。今天小编就把常用的函数汇总一下,为大家能够能好的学习MySQL中的函数。MySQL常使用的函数大概有四类。时间函数、数学函数、字符函数、控制函数。让我...

  • #include int main(int args,char ** argv) {int map[3][3]={{1,2,3},{4,5,6},{7,8,9}};int **pMap=(int **)map;printf("%d ",map);//数组的首地址printf("%d ",*(map+1));//数...

  • awk格式化使用printf函数,类似于C语言中的printf函数 比如 awk '{printf "%s ", $1}' test1 上面的方式是awk每次处理一行,然后进行替换的,如果我们想要传入多个参数,此时就需要多个格式化...

  • 【目的】   定义一个结构体类,其中的成员变量数组长度不定,根据实例化的对象指定长度,所以想到用指针实现 【现状】   指针可以指向任意长度数组,但结构体类只分配指针本身4字节长度,所以无法扩展     1 /** 2 *****************************************************...

  • 1.有一个四位正整数,组成这个四位数的四个数字各不相同,如果把它们的首尾互换,第二位与第三位互换,组成一个新的四位数。原四位数为新四位数的4倍,请找出一个这样的四位数。 #include int main() {int a,b,c,d,e,f;for(a=1000;a<10000;a++){b=a%10;c=a/1...

  • if条件语句  非零即真   0即假 if(表达式){     //成立之后要处理的事情 }   以atm小程序为例 //判断用户选择的操作     if (operation == 1){            //输入密码         printf("输入密码 ");     }          if (operation...