系统监控之top

原文链接:系统监控之top
发布时间:2018-12-02 23:49:23

文章目录

简介

top可以用来实时显示linux系统的运行情况。

打印输出

top命令输出三个部分:

  • 统计区 主要由统计任务、CPU、内存、交换区的信息
  • 字段头 任务的字段头,标明字段的含义
  • 任务区 每一行打印一个任务信息

统计区

1
2
3
4
5
top - 22:32:57 up 491 days,  3:21,  3 users,  load average: 0.03, 0.06, 0.01
Tasks: 384 total, 1 running, 383 sleeping, 0 stopped, 0 zombie
Cpu(s): 1.2%us, 0.7%sy, 0.0%ni, 98.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 132118792k total, 120647324k used, 11471468k free, 2667992k buffers
Swap: 32767996k total, 919760k used, 31848236k free, 99993460k cached

字段头

1
PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND

任务区

1
2
3
4
5
6
7
19305 KentZhan  20   0  258m  56m 7364 S  3.7  0.0 183:20.17 RtTeServer
50069 KentZhan 20 0 20136 1808 1208 R 1.9 0.0 0:00.02 top
1 admin 20 0 24300 1320 1148 S 0.0 0.0 6:05.06 init
2 admin 20 0 0 0 0 S 0.0 0.0 0:00.02 kthreadd
3 admin RT 0 0 0 0 S 0.0 0.0 0:50.61 migration/0
4 admin 20 0 0 0 0 S 0.0 0.0 19:46.27 ksoftirqd/0
5 admin RT 0 0 0 0 S 0.0 0.0 0:00.00 stopper/0

含义说明

统计区

第一行:

1
top - 22:32:57 up 491 days,  3:21,  3 users,  load average: 0.03, 0.06, 0.01

当前系统时间是22:32:57,系统已经运行了491天3小时21分,目前有3个用户登录系统,最后的load average表示平均负载率,其后面的3个数字,分别代表1,5,15分钟统计的平均负载率。

关于负载率,就是一定时间内任务使用CPU的百分比,例如计算1分钟任务负载率:

load average = 所有任务使用CPU的时间/(1分钟*CPU个数)

Tasks

1
Tasks: 384 total,   1 running, 383 sleeping,   0 stopped,   0 zombie

一共有384个任务,这个任务应该就是指线程,其中1个在运行中,其他383个都在休眠中,0个stopped状态的进程,0个zombie状态的进程,stopped状态表示被跟踪的进程,比如被gdb attach的进程,zombie表示僵尸进程。

Cpu

1
Cpu(s):  1.2%us,  0.7%sy,  0.0%ni, 98.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st

us 表示用户空间中占用CPU百分比

sy 表示内核空间中占用CPU百分比

id 表示CPU空闲状态的时间百分比

wa 表示IO等待占用时间百分比

其他的几个,暂时没用到,待补充。

Mem

1
Mem:  132118792k total, 120647324k used, 11471468k free,  2667992k buffers

总共物理内存:132118792k

已经使用内存:120647324k

空闲的内存:11471468k

buffers内存:2667992k 这个内存其实是包含在已使用的内存中的。

Swap

1
Swap: 32767996k total,   919760k used, 31848236k free, 99993460k cached

关于交换的区的机制,我还不是很熟悉,后续研究补充。

字段头

1
PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND

PID 进程ID

user 执行进程的用户

PR 优先级

NI nice值,和优先级相关

VIRT 进程使用的虚拟内存大小

RES 进程使用的、未被换出的物理内存大小

SHR 共享内存大小

S 进程状态,有R、S、T、D、Z

%CPU CPU占用率

%MEM 内存占有率

TIME+ 进程一共使用CPU时间的大小

COMMAND 启动进程的命令行

任务区

1
2
3
4
5
6
7
8
19305 KentZhan  20   0  258m  56m 7364 S  3.7  0.0 183:20.17 RobotTexasServe
50069 KentZhan 20 0 20136 1808 1208 R 1.9 0.0 0:00.02 top
1 admin 20 0 24300 1320 1148 S 0.0 0.0 6:05.06 init
2 admin 20 0 0 0 0 S 0.0 0.0 0:00.02 kthreadd
3 admin RT 0 0 0 0 S 0.0 0.0 0:50.61 migration/0
4 admin 20 0 0 0 0 S 0.0 0.0 19:46.27 ksoftirqd/0
5 admin RT 0 0 0 0 S 0.0 0.0 0:00.00 stopper/0
## 这里不再解释

其他操作技巧

只监控某个进程

1
top -p $PID
1
2
3
4
5
6
7
8
9
[KentZhang@LOCAL-192-168-97-2 ~]$ top -p 19305
top - 23:32:51 up 491 days, 4:21, 3 users, load average: 0.08, 0.06, 0.01
Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie
Cpu(s): 0.3%us, 0.3%sy, 0.0%ni, 99.4%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 132118792k total, 120680696k used, 11438096k free, 2668112k buffers
Swap: 32767996k total, 919760k used, 31848236k free, 100033744k cached

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
19305 KentZhan 20 0 258m 56m 7364 S 2.0 0.0 184:42.73 RtTeServer

显示单个Cpu的统计信息

执行top后,按 1

可以清楚的看到每个CPU的使用情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[KentZhang@LOCAL-192-168-97-2 ~]$ top
top - 23:36:20 up 491 days, 4:25, 3 users, load average: 0.10, 0.09, 0.02
Tasks: 380 total, 1 running, 379 sleeping, 0 stopped, 0 zombie
Cpu0 : 3.3%us, 1.0%sy, 0.0%ni, 95.7%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu1 : 1.0%us, 1.3%sy, 0.0%ni, 97.7%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu2 : 0.3%us, 0.3%sy, 0.0%ni, 99.3%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu3 : 0.0%us, 0.3%sy, 0.0%ni, 99.7%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu4 : 0.7%us, 0.7%sy, 0.0%ni, 98.6%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu5 : 0.7%us, 1.3%sy, 0.0%ni, 98.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu6 : 0.7%us, 0.7%sy, 0.0%ni, 98.7%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu7 : 0.0%us, 0.0%sy, 0.0%ni,100.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 132118792k total, 120682656k used, 11436136k free, 2668112k buffers
Swap: 32767996k total, 919760k used, 31848236k free, 100034680k cached

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
19305 KentZhan 20 0 258m 56m 7364 S 2.3 0.0 184:47.57 RtServer
45304 admin 20 0 2954m 56m 12m S 1.0 0.0 4143:26 java

改变排序方式

在任务区默认是按CPU百分比的大小进行排序的,可以改变排序方式。

使用 “shift + >” 或者 “shift + <” 左右移动字段的排序方式。

显示完整的命令

默认command字段只显示进程的文件名,或者命令行名,不是完整的执行命令。

1
top -c  ## 显示完整的命令
1
2
3
4
5
6
7
8
9
10
11
12
[KentZhang@LOCAL-192-168-97-2 ~]$ top -c
top - 23:43:46 up 491 days, 4:32, 3 users, load average: 0.12, 0.08, 0.01
Tasks: 381 total, 2 running, 379 sleeping, 0 stopped, 0 zombie
Cpu(s): 4.9%us, 1.3%sy, 0.0%ni, 93.7%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 132118792k total, 120684588k used, 11434204k free, 2668112k buffers
Swap: 32767996k total, 919760k used, 31848236k free, 100034984k cached

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
64228 admin 20 0 662m 20m 4300 S 40.7 0.0 149:47.29 /usr/local/python2714/bin/python
19477 WwwCron 20 0 232m 13m 8144 S 1.7 0.0 0:00.09 /usr/local/php/bin/php /data/wwwr
19504 admin 20 0 158m 3452 2716 R 1.0 0.0 0:00.03 sudo /data/zabbix/bin/PartitionWr
18027 KentZhan 20 0 20284 2092 1324 R 0.7 0.0 0:01.31 top -c

切换内存展示的单位

按 e 键,可以切换单位,从 kb 到 mb,gb,tb等等

1
2
3
4
5
6
7
8
9
10
11
Tasks: 1135 total,   1 running, 1134 sleeping,   0 stopped,   0 zombie
%Cpu(s): 23.4 us, 4.7 sy, 0.0 ni, 69.4 id, 0.0 wa, 0.0 hi, 2.5 si, 0.0 st
KiB Mem : 65791988 total, 1209256 free, 27257884 used, 37324848 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 32357320 avail Mem

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
26183 root 20 0 1783.2m 927.7m 4.6m S 282.8 1.4 13448:00 PushProxy
45190 root 20 0 1779.1m 53.8m 6.4m S 108.2 0.1 49907:32 binanceParser
45579 root 20 0 399.9m 171.8m 6.9m S 88.8 0.3 3098:39 stock-pick
76395 root 20 0 624.2m 51.4m 6.5m S 52.2 0.1 138:56.87 monitor-core-wr
70721 root 20 0 624.1m 57.5m 6.4m S 38.8 0.1 121:43.43 monitor-core-wr

系统调用追踪之strace

原文链接:系统调用追踪之strace
发布时间:2018-11-29 00:19:37

strace 是一个常用的linux命令,应该是 trace system calls的简写形式,用来追踪一个程序的系统调用。

1、启动并跟踪程序

1
2
3
strace ./test.exe                ## 结果打印在屏幕
strace -o out.txt ./test.exe ## 结果输出在文件
strace -f -o out.txt ./test.exe ## -f 表示跟踪子进程

2、跟踪shell命令

其实和1类似:

1
2
3
4
5
6
7
8
9
10
11
12
[KentZhang@LOCAL-192-168-97-2 cpp_demo]$ strace ls  ## 跟踪ls命令
execve("/bin/ls", ["ls"], [/* 25 vars */]) = 0
brk(0) = 0xc8d000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5fae3c5000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=54632, ...}) = 0
mmap(NULL, 54632, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f5fae3b7000
close(3) = 0
open("/lib64/libselinux.so.1", O_RDONLY) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0PY\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=122056, ...}) = 0

3、追踪运行的进程

1
strace -p $pid
1
2
3
[KentZhang@LOCAL-192-168-97-2 cpp_demo]$ strace -p 19305
Process 19305 attached
futex(0x7fdeb8ffb9d0, FUTEX_WAIT, 19306, NULL

4、统计系统调用的信息

1
strace -c [程序或者命令]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[KentZhang@LOCAL-192-168-97-2 cpp_demo]$ strace -c ls
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
100.00 0.000020 1 28 mmap
0.00 0.000000 0 10 read
0.00 0.000000 0 2 write
0.00 0.000000 0 12 open
0.00 0.000000 0 14 close
0.00 0.000000 0 12 fstat
0.00 0.000000 0 16 mprotect
0.00 0.000000 0 3 munmap
0.00 0.000000 0 3 brk
0.00 0.000000 0 2 rt_sigaction
0.00 0.000000 0 1 rt_sigprocmask
0.00 0.000000 0 2 ioctl
0.00 0.000000 0 1 1 access
0.00 0.000000 0 1 execve
0.00 0.000000 0 1 fcntl
0.00 0.000000 0 2 getdents
0.00 0.000000 0 1 getrlimit
0.00 0.000000 0 1 statfs
0.00 0.000000 0 1 arch_prctl
0.00 0.000000 0 2 1 futex
0.00 0.000000 0 1 set_tid_address
0.00 0.000000 0 1 set_robust_list
------ ----------- ----------- --------- --------- ----------------
100.00 0.000020 117 2 total

每一列的含义:

所占时间的百分比 一共消耗的时间 一次调用消耗的时间(单位 微秒) 调用次数 出错次数 调用名称

5、跟踪指定的系统调用

1
2
3
4
5
-e trace=read    ## 跟踪read系统调用
-e trace=file ## 跟踪有关文件操作的系统调用
-e trace=process ## 跟踪有关进程控制的系统调用
-e trace=network ## 跟踪与网络有关的所有系统调用
-e trace=ipc ## 跟踪进程通信有关的系统调用

跟踪read系统调用

1
2
3
4
5
6
7
8
9
10
11
12
13
[KentZhang@LOCAL-192-168-97-2 cpp_demo]$ strace -e trace=read df
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0000\356\1\0\0\0\0\0"..., 832) = 832
read(3, "/dev/sda2 / ext4 rw 0 0\nproc /pr"..., 4096) = 312
read(3, "", 4096) = 0
read(3, "# Locale name alias data base.\n#"..., 4096) = 2512
read(3, "", 4096) = 0
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sda2 51475068 29970200 18883428 62% /
tmpfs 66059396 16 66059380 1% /dev/shm
/dev/sda1 999320 68336 878556 8% /boot
/dev/sda4 491806744 405585820 61231844 87% /data
/dev/sdb1 2307222740 74280 2189941600 1% /disk1
+++ exited with 0 +++

跟踪有关文件操作的系统调用

1
2
3
4
5
6
7
8
[KentZhang@LOCAL-192-168-97-2 cpp_demo]$ strace -e trace=file df
execve("/bin/df", ["df"], [/* 25 vars */]) = 0
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY) = 3
open("/lib64/libc.so.6", O_RDONLY) = 3
open("/usr/lib/locale/locale-archive", O_RDONLY) = 3
open("/etc/mtab", O_RDONLY|O_CLOEXEC) = 3
statfs("/", {f_type="EXT2_SUPER_MAGIC", f_bsize=4096,

跟踪有关进程控制的系统调用

1
2
3
4
5
6
[KentZhang@LOCAL-192-168-97-2 cpp_demo]$ strace -e trace=process date
execve("/bin/date", ["date"], [/* 25 vars */]) = 0
arch_prctl(ARCH_SET_FS, 0x7f18b3be2700) = 0
Thu Nov 29 00:08:49 CST 2018
exit_group(0) = ?
+++ exited with 0 +++

跟踪与网络有关的所有系统调用

1
2
3
4
5
6
7
[KentZhang@LOCAL-192-168-97-2 cpp_demo]$ strace -e trace=network ping 192.168.97.2
socket(PF_INET, SOCK_RAW, IPPROTO_ICMP) = -1 EPERM (Operation not permitted)
socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(1025), sin_addr=inet_addr("192.168.97.2")}, 16) = 0
getsockname(3, {sa_family=AF_INET, sin_port=htons(63882), sin_addr=inet_addr("192.168.97.2")}, [16]) = 0
ping: icmp open socket: Operation not permitted
+++ exited with 2 +++

linux下常用时间函数

原文链接:linux下常用时间函数
发布时间:2018-11-28 00:32:45

在linux下编程,有一些常用的时间函数,下面做一个总结梳理。

1、time

1
2
3
4
5
6
7
8
9
10
11
SYNOPSIS
#include <time.h>

time_t time(time_t *t);

DESCRIPTION
time() returns the time since the Epoch (00:00:00 UTC, January 1, 1970), mea-
sured in seconds.

If t is non-NULL, the return value is also stored in the memory pointed to by
t.

先说一下,time_t类型是一个有符号整数,是32位系统下,就是32位有符号数,在64位系统下,就是64位有符号数。那么time函数返回的是自从UTC时间1970年1月1日 00:00:00到现在经过的秒数,如果32位有符号数存储这个值,那么在2038年这个值将溢出,无法存储这么大的数了。

这个函数传进去一个time_t指针,会将时间写入这个传入的内存,同时time也返回这个值,总感觉有点多余,要么用返回值,要么用指针。

1
int now = time(NULL); // 常用的形式

2、localtime

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
 struct tm *localtime(const time_t *timep);
struct tm *localtime_r(const time_t *timep, struct tm *result);

struct tm {
int tm_sec; /* seconds */
int tm_min; /* minutes */
int tm_hour; /* hours */
int tm_mday; /* day of the month */
int tm_mon; /* month */
int tm_year; /* year */
int tm_wday; /* day of the week */
int tm_yday; /* day in the year */
int tm_isdst; /* daylight saving time */
};
/*
The members of the tm structure are:

tm_sec The number of seconds after the minute, normally in the range 0 to
59, but can be up to 60 to allow for leap seconds.

tm_min The number of minutes after the hour, in the range 0 to 59.

tm_hour The number of hours past midnight, in the range 0 to 23.

tm_mday The day of the month, in the range 1 to 31.

tm_mon The number of months since January, in the range 0 to 11.

tm_year The number of years since 1900.
tm_wday The number of days since Sunday, in the range 0 to 6.

tm_yday The number of days since January 1, in the range 0 to 365.

tm_isdst A flag that indicates whether daylight saving time is in effect at
the time described. The value is positive if daylight saving time
is in effect, zero if it is not, and negative if the information is
not available.

*/

这两个函数是将time_t的时间戳转化为本地且容易阅读的时间,关键是tm结构体,注释已经很清楚了。这两个函数的区别是前者是线程不安全的函数,后者是线程函数函数。你看第一个函数,平白无故的返回一个指针,很少看到这种情况,其实这个指针指向的是一个函数内部静态分配的结构体,有可能被其他线程重写,所以是不安全的,尽量用第二个。

3、gettimeofday

1
2
3
4
5
6
7
8
9
10
11
12
#include <sys/time.h>

int gettimeofday(struct timeval *tv, struct timezone *tz);

struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
struct timezone {
int tz_minuteswest; /* minutes west of Greenwich */
int tz_dsttime; /* type of DST correction */
};

这个函数也可以得到当前的时间,而且可以达到微秒级别,由第一个参数tv指针传出,第二个参数tz已经废弃,传入NULL即可。

timeval结构体,保存秒数和微秒数,也是从UTC时间1970年1月1日 00:00:00算起的。

测试程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <time.h>
#include<sys/time.h>

int main ()
{
struct timeval tv;
gettimeofday(&tv,NULL);
printf("second=%d\n", tv.tv_sec);
printf("microseconds=%d\n", tv.tv_usec);
printf("time=%d\n", time(0));
return 0;
}

输出:

1
2
3
4
[KentZhang@LOCAL-192-168-97-2 ]$ ./a.out
second=1543334965
microseconds=963832
time=1543334965

4、clock_gettime

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <time.h>

int clock_gettime(clockid_t clk_id, struct timespec *tp);
/*
clk_id 有以下五种取值,我只用过前2个,后面3个待研究。
CLOCK_REALTIME // 获取实时时间,类似time()
System-wide real-time clock. Setting this clock requires appropriate
privileges.
CLOCK_MONOTONIC // 获取系统从启动到现在经历的时间
Clock that cannot be set and represents monotonic time since some
unspecified starting point.
CLOCK_MONOTONIC_RAW (since Linux 2.6.28; Linux-specific)
Similar to CLOCK_MONOTONIC, but provides access to a raw hardware-
based time that is not subject to NTP adjustments.
CLOCK_PROCESS_CPUTIME_ID
High-resolution per-process timer from the CPU.
CLOCK_THREAD_CPUTIME_ID
Thread-specific CPU-time clock.
*/

// 传出参数的结构体,保存秒和纳秒
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};

据说这个函数是获取系统的时钟时间,和CPU的时钟有关系,所以精度才比较高。

文本编辑工具之sed

原文链接:文本编辑工具之sed
发布时间:2018-11-23 23:15:26

sed是一个流文本编辑工具,全称是 stream editor,以下仅列举我常用的几个功能。

批量替换

比如某个函数名改了,那么其他文件中调用的函数全部都要修改,如果用手动修改,那是费时费力,此时需要借助sed.

配合find 查找出所有 *.h文件,将文件中的g_pErrorLog改为LogError

注意sed 后面的 -i 一定要加上,否则不会就修改文件

1
2
find ./ -name "*.h"|xargs sed -i 's/g_pErrorLog/LogError/g'

删除空行

^ 表示匹配开头 $表示匹配结尾 d表示删除 开头和结尾在一起表示匹配空行

1
2
sed /^$/d  test.txt

添加字符

行头添加字符

在每行的头添加字符,比如行头添加”HEAD”

1
2
sed 's/^/HEAD/g' test.file

行位添加字符

在每行的行尾添加字符,比如“TAIL”

1
2
sed 's/$/TAIL/g' test.file

文本处理工具之awk

原文链接:文本处理工具之awk
发布时间:2018-11-23 23:09:40

awk是一个文本处理工具,也是一种脚本语言。我常使用的就是下面的两个功能。

基本格式:

1
awk -F [str]  'BEGIN{comand1}{commad2}END{command3}' filename

-F 后面的跟的是分割符号,可以是一个字符,也可以是字符串.

中间的命令必须用单引号括起来

BEGIN表示开始循环开始前执行的命令,执行一次

END表示循环结束后执行的命令,执行一次

commnad2表示每处理一行,就执行一次

BEGIN和END可以省略

参加下面的实例:

过滤

1
<RedisCommandWithOutReplyValue> Failed to execute command:LPUSH, key:PROC|FAST|109, value:call AddLottery(13940887,149,464,129868120,9,'269,1037,781,525,1035',129868120,0);

以上是一行错误日志,test.txt中有大量的这样格式的日志,但是我仅需要过滤出AddLottery后的第1个和第4个数字。命令如下:

1
awk '{print $8}' test.txt |awk -F ',' '{printf  "%s,%s\n",$1, $4}'|awk -F '(' '{print $2}'

输出:

1
13940887,129868120

统计

接着上面的日志,假设还有一个需求:

对上面输出的结果,第一个数字要累加求和:

sum.txt 内容如下:

1
2
3
13940887,129868120
13940882,129868122
13940880,129868121

命令:

1
awk -F ',' 'BEGIN{sum=0;}{sum+=$1;}END{print sum}' sum.txt

输出:

1
41822649

STL中map的使用详解

原文链接:STL中map的使用详解
发布时间:2018-11-14 18:34:04

map的本质

map本质上一个平衡二叉树(更准确地说是红黑树),那么每个节点存放一个数据,默认是key和value打包成一个数据pair,以pair的形式存放在节点的,由此来看,pair里面可以放任何数据,前提是pair必须可以比较大小,当然也可以自定义比较函数,而map的第三个参数就是指定自定义key的比较函数的。

map的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//map的定义
//第一 第二 是key和value的类型
//第三个参数是关于Key的比较函数 默认是less 参见less的定义——就是比较2个数据,小为真
//第四是节点数据存储的方式 默认是pair的方式 即key与value打包在一起
template < class Key, // map::key_type
class T, // map::mapped_type
class Compare = less<Key>, // map::key_compare
class Alloc = allocator<pair<const Key,T> > // map::allocator_type
> class map;

//less 是一个函数对象 重载()运算符
//所谓的比较函数定义格式为 bool cmp(const T& x, const T& y) 实现这个函数即可
template <class T> struct less : binary_function <T,T,bool>
{
bool operator() (const T& x, const T& y) const {return x<y;}// 默认是降序排列
};

map的特点

  • map本质是一个红黑树
  • 每个节点存放一个数据
  • map的节点是有序的

正是因为map的内部数据是有序的,才能保证map的高效查找,数据有序,pair必须是可比较的,必须有比较函数。

如果key是整数、字符串,则不需要提供比较函数,但是如果key是自己定义的数据类型,比如是一个结构体,那就需要自己编写比较函数了。

自定义排序函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <map>
#include <stdio.h>
#include <string>

using namespace std;
struct DATA {
int uid;
string name;
bool operator()(const DATA& d1, const DATA& d2)
{
//大的排前面
return d1.uid > d2.uid;
}
};

typedef pair<DATA, string> DATA_PAIR;

struct CmpDATA //这样单独定义一个函数对象也可以的
{
bool operator()(const DATA& d1, const DATA& d2)
{
//大的排前面
return d1.uid > d2.uid;
}
};

int main()
{
//关键的一步,定义map时,传入自定义的比较函数
map<DATA, string, DATA> mapData;
//map<DATA, string, CmpDATA> mapData;

DATA data1 = {1001, "jim"};
DATA data2 = {1002, "jack" };
DATA data3 = {1003, "lucy" };
DATA data4 = {1004, "james" };

//插入数据
mapData.insert(pair<DATA, string>(data1, "China"));
mapData.insert(pair<DATA, string>(data3, "English"));
mapData.insert(pair<DATA, string>(data4, "Japan"));
mapData.insert(pair<DATA, string>(data2, "American"));
//遍历
map<DATA, string>::iterator it;
for (it = mapData.begin();
it != mapData.end();
it++)
{
printf("uid=%d, name:%s\n", it->first.uid, it->first.name.c_str());
}

//查找
it = mapData.end();
it = mapData.find(data2);
if (it != mapData.end())
{
printf("find data2, uid=%d, value=%s\n", it->first.uid, it->second.c_str());
}
else
{
printf("not find data2");
}
//直接索引
printf("print data3, value=%s\n", mapData[data3].c_str());

return 0;
}

输出:

1
2
3
4
5
6
uid=1004, name:james
uid=1003, name:lucy
uid=1002, name:jack
uid=1001, name:jim
find data2, uid=1002, value=American
print data3, value=English

函数对象

就是定义的类重载了operator() 操作符。对象可以像函数指针一样使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
using namespace std;

class FuncObject
{
public:
int operator()(int a, int b)
{
return a + b;
}
};

int main()
{
FuncObject f;
int sum = f(10, 20);//调用对象
printf("sum=%d\n", sum);
return 0;
}

函数对象可以代替函数指针,比指针更加灵活,因为对象是可以有状态的。

标准库定义了一组算术、关系与逻辑函数对象类

例如 less、greater

1
2
3
4
5
6
7
8
9
10
int main()
{
less<int> cmp;//定义对象
printf("result1=%d\n", cmp(12,20));

greater<string> cmpStr;//定义对象
printf("result2=%d\n", cmpStr("C", "B"));

return 0;
}

输出:

1
2
result1=1
result2=1

pair的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
namespace std {
template < class T1, class T2 >
struct pair {
T1 first;
T2 second;
// constructors
};
}

//其实就是包含了2个对象分别是 first second

std::make_pair(10, 'D');//可以用make_pair创建一个pair对象
template <class T1,class T2>
pair<T1,T2> make_pair (T1 x, T2 y)
{
return ( pair<T1,T2>(x,y) );//返回pair对象
}

insert操作

map的insert表示插入数据,如果key已经存在,将不做任何操作,key不存在,则插入数据。

1
pair<iterator,bool> insert (const value_type& val);

关于返回值:

  • 返回值是一个pair
  • pair 的 first 表示 在map中,等于k值在map中迭代器
  • pair的second的是一个bool值,false表示没有插入key,说明key已经存在map中了,true表示插入成功

由以上可知,insert操作,和通过下标索引操作还是不一样的。

通过下标操作一定会修改map,例如map[k1]=v1,如果k1存在,会修改k1的值,k1不存在,则插入key的键值对。

tcpdump简明实用教程

原文链接:tcpdump简明实用教程
发布时间:2018-11-10 12:03:53

文章目录

网络包的五元组

在网络中,一个五元组确定一种网络包,五元组包括:

  • 协议,tcp或者udp等等
  • 源IP
  • 目的IP
  • 源端口
  • 目的端口

那么tcpdump在抓包的时候也是围绕这几个参数设置来过滤需要的数据包。下面来逐步学习tcpdump的使用。

包输入

网络数据是通过网卡来收发数据,但是一台计算机有可能有多个网卡,到底监听哪个网卡的数据呢?所以需要指定网口。

需要用参数-i来指定,i表示interface。

1
2
sudo tcpdump -i eth0  # 抓取eth0上的数据包
sudo tcpdump -i lo # 抓取lo上的数据包

这里需要强调两点:

  • 使用tcpdump,需要sudo权限
  • 测试本地通信的程序,数据经过lo网口,要监听lo网口,不要监听eth0端口

网络数据包非常多,需要过滤自己需要的数据包,下面介绍过滤的方法。

包过滤

因为一个网口的数据包很多,但是我只想要我需要的,我要把我需要的过滤出来,这就叫过滤。

1、IP过滤

1
2
3
sudo tcpdump -i eth0 host 192.168.202.92      # 过滤包括192.168.202.92的数据包
sudo tcpdump -i eth0 src host 192.168.202.92 # 过滤包括源IP为192.168.202.92的数据包
sudo tcpdump -i eth0 dst host 192.168.202.92 # 过滤包括目的IP为192.168.202.92的数据包

host表示五元组的中的IP,如果进一步限制,就继续在前面加 src 或者 dst进行修饰限制。

2、端口过滤

1
2
3
sudo tcpdump -i eth0 port 19648
sudo tcpdump -i eth0 src port 19648
sudo tcpdump -i eth0 dst port 19648

这个和上面的IP过滤是相似的格式,不用多解释。

3、IP和端口组合过滤

1
2
3
sudo tcpdump -i eth0 dst host 192.168.202.92 and port 80     # 既包含特定的IP又要包含特定的端口
sudo tcpdump -i eth0 src host 192.168.202.92 and port 80 # 既包含特定的源IP又要包含特定的端口
sudo tcpdump -i eth0 dst host 192.168.202.92 and dst port 80 # 既包含特定的目的IP又要包含特定的目的端口

这个组合就是对上面的IP和端口过滤的方式进行组合,这里使用 and 关键字,表示与的意思。

同时还有 or 关键字,or表示或的意思。

1
sudo tcpdump -i eth0 dst host 192.168.202.92 or port 19761 # 满足条件之一即可

1
2
3
sudo tcpdump -i eth0 not dst host 192.168.202.92 # 过滤目的IP不是192.168.202.92的数据包
sudo tcpdump -i eth0 not dst host 192.168.202.92 and not port 80 # 过滤目的IP不是192.168.202.92同时端口不是80的
sudo tcpdump -i eth0 dst host 192.168.202.92 or not src port 80 # 过滤目的IP是192.168.202.92 或者 源端口不是80的

4、协议过滤

协议过滤,常用的协议有ip,arp,tcp,udp,icmp等等,只需要和上面的方式进行组合即可。

1
2
3
sudo tcpdump -i eth0 udp # 过滤所有UDP包
sudo tcpdump -i eth0 udp and host 192.168.202.92 # 过滤ip是192.168.202.92的udp包
sudo tcpdump -i eth0 udp and host 192.168.202.92 and port 40948 # 过滤ip是192.168.202.92且端口是40948的udp包

其他的组合方式类似。

以上的包过滤方式,我认为已经可以解决大多数问题了。下面介绍一下其他的参数使用。

包输出

1、保存包到文件

有时候需要将抓到的包写入文件,然后再用wireshark打开分析。

1
sudo tcpdump -w udp.pacp -i eth0 udp

这里使用 -w 参数,将过滤到包保存到udp.pcap文件中。

2、指定包的数量

  • -c 指定过滤一定数量的包
1
2
3
4
5
6
7
8
[KentZhang@local103-122 ~]$ sudo tcpdump -c 2 -i eth0 udp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
11:39:01.511005 IP server.ha.xss.com.syslog > 10.10.100.252.syslog: SYSLOG cron.info, length: 97
11:39:01.511415 IP server.ha.boyaa.com.42251 > dc01.boyaa.com.domain: 18958+ PTR? 252.100.10.10.in-addr.arpa. (44)
2 packets captured
7 packets received by filter
0 packets dropped by kernel

3、显示IP和端口号

1
2
3
4
5
6
7
[KentZhang@local103-122 ~]$ sudo tcpdump -i eth0 udp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
11:26:34.504652 IP server.ha.com.syslog > 10.10.100.252.syslog: SYSLOG daemon.info, length: 50
11:26:34.505276 IP server.ha.com.syslog > 10.10.100.252.syslog: SYSLOG user.info, length: 43
11:26:34.505788 IP server.ha.com.syslog > 10.10.100.252.syslog: SYSLOG user.info, length: 44
11:26:34.506273 IP server.ha.com.syslog > 10.10.100.252.syslog: SYSLOG user.info, length: 43

上面的过滤效果可以看到,源IP、源端口、目的端口,均没有显示出来,显示是域名和进程名。所以需要使用 -n参数,

  • -n 显示IP
  • -nn 显示IP和端口
1
2
3
4
5
6
[KentZhang@local103-122 ~]$ sudo tcpdump -nn -i eth0 udp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
11:30:35.397586 IP 192.168.202.92.514 > 10.10.100.252.514: SYSLOG kernel.info, length: 48
11:30:35.397717 IP 192.168.202.92.514 > 10.10.100.252.514: SYSLOG kernel.notice, length: 108
11:30:37.290627 IP 192.168.202.92.35260 > 192.168.100.51.53: 2401+ AAAA? jobs123.com. (32)

4、显示时间戳

  • -tttt 显示时间戳
1
2
3
4
5
[KentZhang@local103-122 ~]$ sudo tcpdump -nn -tttt  -i eth0 udp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
2018-11-10 11:32:51.202533 IP 192.168.202.92.514 > 10.10.100.252.514: SYSLOG kernel.info, length: 48
2018-11-10 11:32:51.202683 IP 192.168.202.92.514 > 10.10.100.252.514: SYSLOG kernel.notice, length: 108

5、以ASCII码打印

  • -A 以ASCII码打印
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[KentZhang@local103-122 ~]$ sudo tcpdump -nn -A -tttt  -i eth0 udp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
2018-11-10 11:33:30.306305 IP 192.168.202.92.514 > 10.10.100.252.514: SYSLOG kernel.notice, length: 108
E.....@.@.@Z...\

d......t..<5>kernel: type=1700 audit(1541820810.269:155): dev=et
2018-11-10 11:33:37.386001 IP 192.168.202.92.52124 > 192.168.100.51.53: 55230+ AAAA? jobs.com. (32)
E..<.a@.@..n...\..d3...5.(...............jobs.xss.com.....
2018-11-10 11:33:37.386405 IP 192.168.100.51.53 > 192.168.202.92.52124: 55230* 1/1/0 CNAME[|domain]
...d3...\.5.....n.............jobs.com..............X...ops-uploa
2018-11-10 11:33:37.386476 IP 192.168.202.92.64472 > 192.168.100.51.53: 45448+ A? jobs.com. (32)
E..<.a@.@..n...\..d3...5.(...............jobs.xss.com.....
2018-11-10 11:33:37.386796 IP 192.168.100.51.53 > 192.168.202.92.64472: 45448* 2/0/0 CNAME[|domain]
E..o=.@.......d3...\.5.[...............jobs.xss.com..............X...ops-uploa

6、以ASCII和二进制同时打印

  • -XX 以ASCII和十六进制同时打印
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[KentZhang@local103-122 ~]$ sudo tcpdump -nn -XX -tttt  -i eth0 udp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
2018-11-10 11:36:35.697994 IP 192.168.202.92.514 > 10.10.100.252.514: SYSLOG kernel.notice, length: 108
0x0000: 0cda 4118 a65c f01f afe1 f61b 0800 4500 ..A..\........E.
0x0010: 0088 0000 4000 4011 405a c0a8 ca5c 0a0a ....@.@.@Z...\..
0x0020: 64fc 0202 0202 0074 fa90 3c35 3e6b 6572 d......t..<5>ker
0x0030: 6e65 6c3a 2074 7970 653d 3137 3030 2061 nel:.type=1700.a
0x0040: 7564 6974 2831 3534 3138 3230 3939 352e udit(1541820995.
0x0050: 3636 343a 3135 3729 3a20 6465 763d 6574 664:157):.dev=et
2018-11-10 11:36:37.516281 IP 192.168.202.92.55188 > 192.168.100.51.53: 29533+ AAAA? jobs.xss.com. (32)
0x0000: 0cda 4118 a65c f01f afe1 f61b 0800 4500 ..A..\........E.
0x0010: 003c 9c03 4000 4011 eecc c0a8 ca5c c0a8 .<..@.@......\..
0x0020: 6433 d794 0035 0028 b01a 735d 0100 0001 d3...5.(..s]....
0x0030: 0000 0000 0000 046a 6f62 7305 626f 7961 .......jobs.boya
0x0040: 6103 636f 6d00 001c 0001 a.com.....

谈谈如何记忆参数的使用

关于tcpdump的使用教程非常多,但是大部分都是极其相似,如果没有按照自己的思维方式进行总结个归纳,那么这个命令的使用很容易忘记。如果等到需要使用的时候再去网上查,那么筛选信息的成本就非常高,这就降低了自己的工作效率。

根据以上我总结的模块,可以看到,tcpdump的参数分为三个部分:

  • 输入 -i 指定网口
  • 过滤 端口 ip 协议的组合过滤
  • 输出 写文件或打印输出

其中在过滤中,端口、IP、协议需要使用and、or、not组合。

所以一个tcpdump的命令格式就是:

1
tcpdump (输出参数) (输入参数) (过滤参数)

使用模块化的方式,更容易记忆,更清楚参数的排列,以及组合方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[KentZhang@local103-122 ~]$ sudo tcpdump -c 2 -nn -XX -tttt -i eth0 tcp and host 192.168.202.92
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
2018-11-10 11:59:43.674912 IP 192.168.202.92.55225 > 192.168.100.17.6379: P 4250024641:4250024733(92) ack 511953561 win 2396
0x0000: 0cda 4118 a65c f01f afe1 f61b 0800 4500 ..A..\........E.
0x0010: 0084 18e3 4000 4006 71d2 c0a8 ca5c c0a8 ....@.@.q....\..
0x0020: 6411 d7b9 18eb fd52 3ac1 1e83 ca99 5018 d......R:.....P.
0x0030: 095c b035 0000 2a35 0d0a 2437 0d0a 6576 .\.5..*5..$7..ev
0x0040: 616c 7368 610d 0a24 3430 0d0a 3332 3336 alsha..$40..3236
0x0050: 6334 3436 6433 6238 3736 3236 3566 6534 c446d3b876265fe4
2018-11-10 11:59:43.675481 IP 192.168.100.17.6379 > 192.168.202.92.55225: P 1:5(4) ack 92 win 229
0x0000: f01f afe1 f61b 0cda 4118 a65c 0800 4500 ........A..\..E.
0x0010: 002c 2141 4000 3e06 6bcc c0a8 6411 c0a8 .,!A@.>.k...d...
0x0020: ca5c 18eb d7b9 1e83 ca99 fd52 3b1d 5018 .\.........R;.P.
0x0030: 00e5 b5b8 0000 2a30 0d0a 0000 ......*0....
2 packets captured
43 packets received by filter
0 packets dropped by kernel

goroutine原理分析

原文链接:goroutine原理分析
发布时间:2018-11-10 09:40:02

文章目录

进程和线程

在讲解goroutine之前,先来熟悉一下进程和线程的概念,因为只有通过概念之前对比,才能更加理解这些概念。

进程-——拥有资源的人

计算机的使用,大都是以进程为单位来管理的,比如我打开电脑版微信,桌面启动一个微信程序,本质上计算机启动了一个为微信进程,打开浏览器、播放器等等类似,当然有的应用软件不只启动一个进程。

在windows下,可以通过任务管理器看到启动的进程;在linux下,可以通过ps -aux命令,看到所有的进程。

由此,可以大致了解到,所谓进程就是运行的程序

如果安装完QQ软件,你不运行QQ,那么QQ就是一堆静态的躺在硬盘上的文件,当你一旦点击运行,那么计算机便创建了一个QQ进程,这个时候,就可以输入账号和密码登录QQ了,这就是和QQ进程交互的过程。

到这里,可以知道,进程是CPU和内存有关系的,因为程序的运行需要CPU和内存,更专业的说法就是进程就是程序在内存中的镜像,因为只有把程序载入内存才能运行。

上面讲了那么多废话,无非就是引入进程的以下特征:

  • 进程是计算机资源管理的基本单位,例如内存分配、描述符分配、环境变量等等。
  • 进程之间相互独立,互不干扰。

由于第一点资源上的独立,第二点也就是自然成立了。

线程——真正干活的人

由于进程从宏观的上就可以看到,所以容易理解,但是线程是比进程更小的单位,貌似就不容易那么理解了。世间万事都是如此,更微观的现象,了解的成本的就越高,了解的人就越少。首先,线程是由进程创建,并且可以创建多个线程,正确的说是,线程的创建、运行、销毁都是由进程控制。

第一点:进程的拥有的资源,所有线程的线程都可以”看见”,都可以访问到。

第二点:线程是CPU执行的基本单位。

一点不能理解,既然进程分配资源的基本单位,拥有资源,那么这些资源给谁用呢?就是线程,线程来使用这些资源,所有的线程都可以使用到。线程使用这些资源干嘛呢?干活。

这里需要补充一点,CPU每次只能执行一个线程,那么其他的线程只能排队等待,至于这个线程要”霸占”CPU多久,取决于系统的线程调度算法,一般是执行一定的时间就让出CPU,然后排队等待。

下面做一个简单的比喻:

进程就好比一个公司,拥有很多资源,包括办公室、电脑、食堂、班车等等,而且公司与公司之间相互独立,互不影响。那么线程就是公司里的员工,每天都要工作,每个人都是基本的人力单位,所有的员工都可以使用公司的资源。所以进程与进程的独立性很强,基本不受对方的影响。但是一个进程的所有线程,就不那么独立了,因为使用同一个进程的资源,有时候就产生”矛盾”了,你看一个公司的所有员工之前经常发生摩擦和争吵。

多线程和多协程

多线程——多个人干多件事

刚才说了,一个进程拥有多个线程,就好比一家公司有好多员工,每个员工的工作任务不同,有的人写代码、有的人设计UI、有的人负责运营、有的人负责人事,他们的工作基本上是并行的。我之所以说基本上是并行的,是因为有时候他们之间也需要相互等待,比如软件没有开发完,就不能让测试进行测试,更不能发布到线上。

整体而言,一个公司的人越多,做事情的速度越快。但是不是全然正确,因为《人月神话》,因为一件工作的粒度不能无限细分下去,举个极端的例子,一车砖头,10个人1小时搬完,请问10万个人搬需要多少小时?是1万分之小时吗?也就是0.36秒?算了吧,10万个人排队就超过1小时,这时候人多反而降低效率了。当然,我举的这个例子很极端,只是为了说明问题。

一个小案例

之前做过这样一个需求:有一个目录下会生成大量的文件,需要及时转移到另外一个目录下,文件的大小2KB—2GB之间,我最开始的做法是配置多个线程来转移文件,因为多个线程读取同一个目录,所以必须采用互斥锁。

线程的具体做法是:

1
2
3
4
5
//配置8个线程
lock();
filelist =GetFiles();//获取50个文件
unlock();
Move(filelist);//开始转移

经过测试就发现一个问题,假如这某一个时候,生成的文件都比较大,都是2GB,那么文件大,文件的数量就少,只能少量线程可以搬运文件,有的线程都空闲着,根本无法发挥多线程的优势。

另一种情况时,生产的文件都很小,只有1KB那么大,但是生成的速度很快,几秒钟便可以生成近百万个文件,如果这个时候,每个线程每次只获取50个文件,那么线程就需要多次访问互斥锁,性能反而降低,怎么办?改进方法:每个线程每次获取1万个文件,性能可以提高不少。

这个案例就涉及到任务粒度划分的问题,第一种情况,每个文件2G,任务粒度很大,一个文件只能由一个线程来处理,多线程没啥优势。第二种情况,每个文件1KB,任务粒度太小,每个线程根本没有饱和,造成线程争夺资源损耗性能。

总结:

  • 多线程的优势显而易见,可以同时执行多种任务。
  • 多线程合作执行同一任务时,其执行效果和任务的粒度有关系。

多协程——一个人干多件事

协程,是一个线程更小的单位,由线程创建,由于线程是CPU调度执行的最小单元,那么第一个结论就是:

一个线程里协程,是不能并发的,因此协程之间不用加锁。

一个人每天早上上班后,开始投入工作,认真写代码,此时主协程在工作,代码写了250行,突然上级让TA过去开会,这时候放下手头的工作,创建一个开会的协程,开始进入会议模式,记下100行会议纪要,突然TA的电话响了,停止会议,创建接电话的协程,进入接电话模式,5分钟后电话结束,接电话的协程结束,回到刚才的切换的协程,即开会的协程,然后继续接着100行会议纪要继续记录,等会议结束,会议协程结束,回到刚才切换的协程,即写代码的协程,继续接着250行代码继续写。

以上的过程,大概就是协程切换的过程,是一个人串行的干多件事,干完一件事,就回到上一件事继续接着干。

本质

其实协程的切换就是函数栈帧的切换,以为线程的结构就是栈帧、程序指针、各种寄存器等等,CPU拿到这些东西就可以执行一个线程了。

思考:

一个线程应该至少包含哪些东西?

CPU是如何通过机器指令执行程序的。所谓的栈,只是空间,但是在机器指令中,都是地址,变量、函数都是地址。

goroutine的原理

最近学习和使用golang已经有半年多了,对于一个C/C++程序员来说,golang只是一门语言而已,并没有什么神奇之处,正如侯捷所说:代码面前,了无秘密。

一门编程语言,最终还是要依赖操作系统实现各种功能,你看golang对应每个操作系统,都有一个版本,windows的话,你就需要下载windows版本的golang ,linux系统就要下载linux版本的golang。

说到这里,就不得不说golang的一个重要功能,就是协程——goroutine,利用关键go就可以轻易的开启协程,编写并发程序,利用chan就可以实现协程之间的通信。

上面,我们说了,真正的协程是不能并发的,因为一个协程在线程内部,而线程又是CPU执行的最小单位。但是goroutine是天生可以并发的,在语言层面获得支持,所以golang的强大,其实是golang的运行时干了太多的事情。

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"time"
)

func main() {
fmt.Println("hello world")
time.Sleep(time.Second * 100)
} // hello.go

编译运行,查看这个进程的线程数量,执行:

1
top -H -p `pidof hello`
1
2
3
4
5
54605 KentZhan  20   0  3100 1072  588 S  0.0  0.0   0:00.00 hello
54606 KentZhan 20 0 3100 1072 588 S 0.0 0.0 0:00.00 hello
54607 KentZhan 20 0 3100 1072 588 S 0.0 0.0 0:00.00 hello
54608 KentZhan 20 0 3100 1072 588 S 0.0 0.0 0:00.00 hello
54609 KentZhan 20 0 3100 1072 588 S 0.0 0.0 0:00.00 hello

结果发现,一个简单的hello.go运行后,竟然开启了5个线程(包括主线程),那么goroutine之所以能轻松并发,是这些线程的支持,这些线程来执行应用层goroutine的任务。

GM模型

由上面可知,go的运行时启动多个线程来执行多个goroutine任务,最开始go的调度器是GM模型。

G:表示goroutine,应用层开启的任务。

M:表示golang运行时开启的线程(machine),刚才在我的机器看到的是5个hello线程,当然这些线程的个数和CPU的核数,以及当前的goroutine的数量有关系,和当前的goroutine的行为有一定的关系。

这样的话,多个G相当于是任务队列,多个M构成线程池,然后每个M取出一定量的任务来执行,看起来很完美,但是实际中存在一些问题。

  • 任务队列需要加锁,参考多线程可知,加锁在一定的任务粒度下会损耗性能。
  • 假如M上正在执行任务阻塞,比如调用系统调用,那么这个M上的其他任务得不到执行。我之前在想,当goroutine调用系统调用的时候,M不能把当前的G切出去吗?执行下一个G,等系统调用返回,再继续执行。通过读libco源码才知道,系统调用只能阻塞,只不过libco采用了hook,改写了read函数,在新的read函数里,先epoll,等有数据了,在调用系统调用。所以,系统调用只能阻塞并且等待返回结果。
  • M频繁地调用系统调用阻塞,就把自己其他任务传递给其他的M,造成的一定的性能损耗。

GPM模型

于是在G和M之间引入P,

P:是M调度G的一个中间层,可以理解为是对CPU的抽象,因为它的个数是由CPU的核数确定,可以由runtime.GOMAXPROCS(num)指定,程序运行后不会再改变。

G要想到M上执行,必须先绑定一个P,然后P在M上执行,所以我说P是G和M的中间层,P的数量决定了,同时最多有几个G在执行,P数数量小于等于CPU的核数。P可以控制整个程序的并发程度。

由P来完成一部分M的任务,之前是M从任务队列取任务,现在是P从任务对列取任务,放到自己的本地队列,当M上执行的G阻塞时,P与M分离,这个阻塞的G仍然和M绑在一起继续阻塞等待系统调用返回。那么P就可以继续和其他的M结合,你看M和G就解耦了,解决了GM模型存在的第二和第三个问题。此时,M只执行任务,P只分发任务,解耦了之前的M执行任务,又要管理任务的耦合。

这时候,M面对的不是G了,M只需找到一个P去结合,然后执行P中的G。

关于goroutine底层的线程的数量

测试程序一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
"fmt"
"os"
"time"
)

func WriteFile(num int) {
file := fmt.Sprintf("%d.txt", num)
fp, err := os.OpenFile(file, os.O_CREATE|os.O_RDWR, 0666)
if nil != err {
fmt.Printf("openFile failed, err:%s\n", err.Error())
return
}
data := "Hello"
for {
fp.Write([]byte(data))
}
}

func main() {
for i := 0; i < 30; i++ {
go WriteFile(i)
}
time.Sleep(time.Second * 60)
} //writefile.go 启动30个协程不断地写文件。

测试结果如下:我的机器8核,64位系统,golang 1.11

1
2
3
4
5
6
7
8
9
Tasks: 384 total,   2 running, 382 sleeping,   0 stopped,   0 zombie
Cpu(s): 1.0%us, 0.8%sy, 0.0%ni, 98.2%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 132118792k total, 126218820k used, 5899972k free, 2641128k buffers
Swap: 32767996k total, 920348k used, 31847648k free, 105824260k cached

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
24480 KentZhan 20 0 5736 1684 604 S 640.4 0.0 0:38.15 writefile
44955 KentZhan 20 0 225m 11m 10m R 65.2 0.0 2:56.84 smbd
23999 KentZhan 20 0 20140 1816 1204 R 1.9 0.0 0:00.01 top
1
2
[KentZhang@LOCAL-192-168-97-2 ~]$ pstree -p 24480|wc -l
34

这个进程CPU占有率640%,一共启动34个线程,加上主线程就是35个,业务层代码启动了30个协程。

测试程序二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "time"

func sleep() {
for {
time.Sleep(1 * time.Second)
}
}

func main() {

for i := 0; i < 30; i++ {
go sleep()
}

time.Sleep(60 * time.Second)
} // sleep.go 开启30个协程,每个线程不断sleep
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[KentZhang@LOCAL-192-168-97-2 ~]$ pstree -p 28276
sleep(28276)─┬─{sleep}(28277)
├─{sleep}(28278)
├─{sleep}(28279)
├─{sleep}(28280)
├─{sleep}(28281)
├─{sleep}(28282)
├─{sleep}(28283)
├─{sleep}(28284)
├─{sleep}(28285)
├─{sleep}(28286)
├─{sleep}(28330)
└─{sleep}(28332)
[KentZhang@LOCAL-192-168-97-2 ~]$ pstree -p 28276|wc -l
12

CPU占有率很少,因为斗都在休眠中,后台线程12个,加上主线程一共13个,这个程序也是启动30个协程。

测试程序三

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
"fmt"
"os"
"time"
)

func WriteFile(num int) {
file := fmt.Sprintf("%d.txt", num)
fp, err := os.OpenFile(file, os.O_CREATE|os.O_RDWR, 0666)
if nil != err {
fmt.Printf("openFile failed, err:%s\n", err.Error())
return
}
data := "Hello"

for i := 0; i < 200000; i++ {
fp.Write([]byte(data))
}

}

func main() {
for i := 0; i < 30; i++ {
go WriteFile(i)
}
time.Sleep(time.Second * 20000)
}//sleep.go 修改了写文件的次数为20万次,之后协程退出。

但是30个协程任务执行完后,全部退出,主协程休眠中,但是底层34的个线程依然还在,没有销毁。

结论

加上上面的hello.go,一共也就三个测试程序,按理说,我是得不到什么结论的,但是阅读了其他资料,在上自己的推论,可得到一下我自己的结论。

1、执行线程的数量是不定的,根据需要创建,我的机器上最少是6个。

2、协程越多,执行线程未必越多,取决于于协程是否忙碌,忙碌的协程越多,执行线程就越多。

3、执行线程根据任务繁忙程度来创建,任务执行完,这些线程依然还在,没有销毁。

IP地址转换总结

原文链接:IP地址转换总结
发布时间:2017-03-08 22:33:56

一、IP地址结构体相关定义

本文只讨论IPv4的情况,IPv6的相关转换,在项目中我也使用过,基本类似。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct sockaddr_in {

sa_family_t sin_family; /* address family: AF_INET */

in_port_t sin_port; /* port in network byte order */

struct in_addr sin_addr; /* internet address */

};

struct in_addr {

in_addr_t s_addr;

};

in_addr_t 是32位无符号数。

二、将点分十进制的字符串IP转换为整数IP

1
2
3
in_addr_t inet_addr(const char *cp);  /*返回网络字节序*/

in_addr_t inet_network(const char *cp); /*返回主机字节序*/

它们都有一个小缺陷,当转换”255.255.255.255”会失败,认为这个地址无效。

1
int inet_aton(const char *string, struct in_addr*addr);/*addr是传出参数,将得到网络字节的整数IP*/

这个函数比上面两个函数更完善,建议IPv4使用这个函数。

1
int inet_pton(int af, const char *src, void *dst);/*dst是传出参数,是in_addr的地址,得到网络字节序*/

这个函数支持IPv4和IPv6版本,由参数af来确定是哪个IP版本,取值为AF_INET和AF_INET6.

三、将整数IP转换为点分十进制字符串IP

1
2
3
char* inet_ntoa(struct in_addr in); /*in是网络字节序,该函数线程不安全,尽量少用*/

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);/*src是in_addr地址,是网络字节序*/

也是支持IPv4和IPv6版本,注意缓冲区的长度。

gdb调试命令总结

原文链接:gdb调试命令总结
发布时间:2016-06-09 18:56:33

之前已经写过一篇博客《Linux下使用gdb调试C/C++程序》,是一些简单的用法,今天的这一篇可以算是进阶篇。

一、关于多线程的使用命令

       1、thread

        查看当前线程的ID,这ID是GDB对线程的编号,从1开始的。以下所说的线程ID,都是这种ID。

        2、info thread 

        查看当前可以被调试的线程,ID前面有*号的表示GDB当前所在的线程。

        3、info threads

         查看所有的线程

        4、thread  thread_number

        GDB切换到线程ID为number的线程

       5、thread  apply  thread_number  command

        针对某一个线程执行GDB命令,例如  thread  apply  5  c ,只针对线程ID为5的线程执行continue命令,在多线程调试中,由于一个断点,

        会在多个线程中停住,但是有时候,只想观察某一个线程,就得采用这样的做法。

        扩展:thread  apply  all  bt ,查看所有线程的调用栈。all 表示所有线程。

       5、set  scheduler-locking   on/off

       可理解为GDB的线程调度锁,与4中的功能类似,当为on时,表示打开锁,GDB只在当前线程执行,不会切换到其他线程。

       为off,则关闭锁。

二、关于GDB启动的使用命令

        1、gdb  -p   process_id

         调试一个正在运行的进程,有时候要调试一个fork出来的子进程,它正在运行,必须这样的做才行。

          process_id是进程ID,通过ps -aux或者pidof相关命令得到。

         扩展:直接启动GDB,执行 attach  process_id 也可以有一样的效果。

        2、gdb  –args  ./test.exe   100

        有时候程序启动需要传入参数,因此gdb 后面必须跟–args选项。因此这个命令表示用GDB启动test.exe程序,该程序传入参数100。

         扩展:另一种做法:1、gdb  ./test.exe      2. set  args  100   3、run

三、其他命令

        1、set  var  number=3

         number是一个整型变量,设定它的值为3,即修改变量的值。

       2、b   15   if  number==5

         设置条件断点,如果number等于5,将在15行停住。当然行号也可以函数名。

       3、set  print  element  0

        在用p命令打印string类型时,若字符串比较长,比如大概超过400字节,就只打印出一部分,这个命令,可以突破此限制。