异步刷盘与同步刷盘的区别

存储系统往往追求尽量高的吞吐,无论是传统的MySQl还是分布式存储系统Hbase,在写入的时候会尽量提升系统的吞吐。一般来说,提升系统性能的方式是先将数据写入内存中,然后再刷盘到磁盘中进行持久化。

刷盘对应于MySQL中的fsync和hbase中的flush,在mysql中标识将写入的redo log以及bin Log持久化到磁盘中,这样就保证mysql可以持续的对外提供数据服务;在hbase中表示将MainStore中的数据持久化到磁盘中。一般来说刷盘的方式有同步刷盘和异步刷盘两种。

同步刷盘和异步刷盘的区别如下:

  • 同步刷盘:当数据写如到内存中之后立刻刷盘(同步),在保证刷盘成功的前提下响应client。
  • 异步刷盘:数据写入内存后,直接响应client。异步将内存中的数据持久化到磁盘上。

同步刷盘和异步输盘的优劣:

  • 同步刷盘保证了数据的可靠性,保证数据不会丢失。
  • 同步刷盘效率较低,因为client获取响应需要等待刷盘时间,为了提升效率,通常采用批量输盘的方式,每次刷盘将会flush内存中的所有数据。(若底层的存储为mmap,则每次刷盘将刷新所有的dirty页)
  • 异步刷盘不能保证数据的可靠性.
  • 异步刷盘可以提高系统的吞吐量.
  • 常见的异步刷盘方式有两种,分别是定时刷盘和触发式刷盘。定时刷盘可设置为如每1s刷新一次内存.触发刷盘为当内存中数据到达一定的值,会触发异步刷盘程序进行刷盘。

Comment and share

Linux ps命令使用详解

ps命令常见使用方法有两种,另外我们可以通过一写参数对ps结果进行筛选和过滤。分别是:

1
2
ps -ef
ps aux

那这两个命令有什么区别呢?下面将对这两个命令进行详细说明和解释。

ps -ef

返回信息的格式:

1
2
3
4
5
6
7
8
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 9月24 ? 00:00:25 /sbin/init
root 2 0 0 9月24 ? 00:00:00 [kthreadd]
root 3 2 0 9月24 ? 00:00:00 [ksoftirqd/0]
root 5 2 0 9月24 ? 00:00:00 [kworker/0:0H]
root 7 2 0 9月24 ? 00:09:13 [rcu_sched]
root 8 2 0 9月24 ? 00:00:00 [rcu_bh]
...

对以上各列信息进行解释:

  • uid : 用户id
  • pid : 当前进程的id
  • ppid : 当前进程的父进程id
  • C : 进程占用CPU的百分比
  • STIME : 进程启动时间
  • CMD : 进程启动命令

上述返回值比较容易理解,不一一解释

ps aux

返回信息的格式:

1
2
3
4
5
6
7
8
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 120060 5392 ? Ss 9月24 0:25 /sbin/init
root 2 0.0 0.0 0 0 ? S 9月24 0:00 [kthreadd]
root 3 0.0 0.0 0 0 ? S 9月24 0:00 [ksoftirqd/0]
root 5 0.0 0.0 0 0 ? S< 9月24 0:00 [kworker/0:0H]
root 7 0.1 0.0 0 0 ? S 9月24 9:14 [rcu_sched]
root 8 0.0 0.0 0 0 ? S 9月24 0:00 [rcu_bh]
root 9 0.0 0.0 0 0 ? S 9月24 0:00 [migration/0]

对以上信息解释:

  • user : 用户名
  • pid : 进程id
  • %CPU : 进程运行占用CPU的百分比
  • %MEM : 进程占用内存的百分比
  • VSZ : 该进程使用的虚拟内存量,表示一个程序完全驻留在内存中占用的虚拟内存量
  • RSS : 该进程占用的固定内存量
  • TTY : 终端名
  • START : 进程开启时间
  • STAT : 进程的状态
  • TIME : 该进程实际使用的CPU时间
  • COMMAND : 进程运行指令

上述比较重要的信息主要包括VSZ,RSS以及STAT。
VSZ表示一个程序完全驻留在内存中占用的内存量,其包括程序链接的动态链接库的大小,栈的大小以及代码段的大小。(手动算过,但是栈的大小很迷,貌似数组在栈中的大小与同类型变量的大小相同,待验证)。
RSS包括运行过程中实际被加载到内存中的动态链接库,可执行文件以及栈的大小。
STAT为当前进程的运行状态。其包括多种状态,具体解释如下所述:

  • D : 不可中断(通常是IO进程)
  • R : 正在运行或者在队列中的进程
  • S : 处在休眠状态的进程
  • T : 停止或者被追踪
  • Z : 僵尸进程
  • W : 进入内存交换
  • X : 死掉的进程
  • < : 优先级较高的进程
  • n : 优先级较低的进程
  • s : 包含子进程
  • + : 位于后台进程组

可以在上述命令的基础上对查询结果进行筛选,如我们想要对内存占用量进行排序,方法如下:

1
ps aux --sort -pMEM

Comment and share

makefile自动变量与隐晦规则推导

makefile的使用可以大大简化程序编译的过程,不过对于新手来说makefile的执行规则理解起来还是很让人迷糊的。其中最重要的原因是makefile中使用了大量的隐晦规则和自动变量来简化makefile的编写.本节将记录一下makefile自动变量和隐晦规则的推导过程以及makefile的执行流程。

1
2
%.o:%.c
$(CC) -c $(CFLAGS) -o $@ $<

对于上面的推导规则,makefile是怎么将%c文件编译汇编成.o文件的?下面将针对这个问题进行讲解

自动变量

makefile中的自动变量实质上是对一类变量的简写,当我们在模式规则中对这类变量处理的时候可以直接使用自动变量简化makefile代码的编写。自动变量包括如目标文件,依赖文件等。下面以实例的方式列出了一些常用的自动变量:

1
2
a: a.o b.o c.o d.o
g++ -c $(CFLAGS) -o $@ $<
  • $@: 表示模式规则中的目标文件,对于上面的模式规则,$@表示a
  • $<: 表示依赖中的第一个文件.对于上述规则,$<表示a.o
  • $^: 表示所有依赖文件的集合,对于上述规则$^表示a.o b.o c.o d.o
  • $+: 表示所有依赖文件的集合(不去重).对于上述规则$+表示a.o b.o c.o d.o
  • $%: 仅当目标是函数库文件(.a)文件时,表示规则中目标成员名。如一个目标是(test.a(a.o)),此时$%表示a.o, $@表示test.a
  • $?: 所有比目标新的依赖目标的集合
  • $*: 表示目标规则中%以及%之前的部分。如若目标文件为”src/test.o”,目标文件模式为”src/%.o”,此时$*表示”src/test”。

隐晦规则自动规则推导

使用makefile的makefile的隐晦自动规则推导功能也可以让我们的makefile的代码大大简化。使用隐晦规则,我们没必要为每一个类似的规则生成都去写类似的规则。makefile会自动推导依赖文件,并根据隐含规则推导出生成当前目标的命令。
如下面的makefile:

1
2
3
4
5
6
7
8
target: a.o b.o c.o
g++ -o $@ $^ $(LIB) $(INC) $(LINKER)
a.o: a.c
g++ -c $(CFLAGS) -o a.o a.c
b.o: b.c
g++ -c $(CFLAGS) -o b.o b.c
c.o: c.c
g++ -c $(CFLAGS) -o c.o c.c

上述规则没有使用隐晦规则,对于每一个.o文件的生成都写了一条规则语句.若使用隐晦规则推导,上述makefile可写为如下:

1
2
3
4
5
6
7
SOURCE := $(shell find ./ -type f -name *.c)
OBJECTS := $(patsubst *.c,*.o,$(SOURCE))
target : $(OBJECTS)
g++ -o $@ $^ $(LIB) $(INC) $(LINKER)
%.o:%.c
$(CC) -c $(CFLAGS) -o $@ $<

从上面的隐晦规则可以看出,对于.o文件的生成命令,makefile都可以由隐晦规则” $(CC) -c $(CFLAGS) -o $@ $<”推导出。即如当目标需要a.隐晦规则将推导出命令”g++ -c $(CFLAGS) -o a.o a.c”用于生成该目标.

makefile执行过程

由上一节中的makefile简单说一下makefile的执行规则。

1
2
3
4
5
6
7
SOURCE := $(shell find ./ -type f -name *.c)
OBJECTS := $(patsubst *.c,*.o,$(SOURCE))
target : $(OBJECTS)
g++ -o $@ $^ $(LIB) $(INC) $(LINKER)
%.o:%.c
$(CC) -c $(CFLAGS) -o $@ $<

makefile中首先声明了变量SOURCE和OBJECTS,SOURCE是当前文件夹下的所有.c文件的集合,OBJECTS是所有.c文件对应的目标文件.o的集合.

执行过程:
首先,目标target依赖所有的目标文件.o,即a.o,b.o,c.o。当需要依赖a.o时,makefile会根据隐晦规则自动推导出生成a.o文件的命令,(“g++ -c $(CFLAGS) -o a.o a.c”),生成a.o;类似的也会根据同样的过程生成b.o和c.o文件,这三个文件生成之后,再根据上述规则生成target。

makefile中常用函数

wildcard函数

函数参数:一个正则表达式
函数功能:wildcard的中文意思是通配符,它的功能类似于正则表达式,用于展开一列所有符合其参数描述的文件名,文件之间用空格分割。

实例:

1
SOURCE=$(wildcard *.cpp)

此时SOURCE的值为所有的以.cpp为后缀的文件集合,以空格隔开。

patsubst函数

函数参数: 第一个是需要匹配的样式,第二个是表示用什么替换它,第三个被处理的以空格隔开的字符串。
函数功能:其功能是一个匹配替换的函数(pattern substitute)。

实例:

1
2
#TARGET表示与SOURCE中同名的目标文件.
TARGET = $(patsubst *.cpp, *.o, $(SOURCE))

subst函数

1
$(subst <from>,<to>,<text>)

功能:字符串替换,将text中的from 替换为to
实例:

1
2
#返回值:this is b
$(subst a, b, this is a)

strip函数

1
$(strip <string>)

功能:去除头部和尾部的空格
实例:

1
2
#源字符串为"a b c ",返回值为"a b c"
$(strip a b c )

filter函数

1
$(filter <pattern...>,<text...>)

功能:过滤特定模式的字符串
实例:

1
2
#如source为a.c b.s c.h,则下列的返回值为a.c b.s
$(filter *.c *.s, $(source))

word函数

1
$(word <n>,<text>)

函数功能:取单词函数。取出中的第n个单词(从1开始),若越界,返回为空

实例:

1
2
#此函数返回值为bar
$(word 2,foo bar fun)

words函数

1
$(words <text>)

功能:统计字符串中单词的个数,返回个数
实例:

1
2
#此函数返回值为3
$(word foo bar fun)

wordlist函数

1
$(wordlist <s>,<e>,<text>)

功能:取中s-e个单词
实例:

1
2
#返回bar fun
$(wordlist 2,3, foo bar fun)

firstword函数

1
$(firstword <text>)

函数功能:取中的首个单词
实例:

1
2
#返回值为foo
$(firstword foo bar fun)

dir函数

1
$(dir ...)

功能:取给定文件名序列中的目录(即/前面的部分)。如没有/,则返回./
实例:

1
2
#返回值为/home/ ./
$(dir /home/test testfile)

notdir函数

1
$(notdir ...)

功能:取给定文件名序列中的取出非目录部分(即/后面的部分)。
实例:

1
2
#返回值为test testfile
$(notdir /home/test testfile)

suffix函数

1
$(suffix ..)

功能:取后缀函数,若没有后缀返回为空
实例:

1
2
#返回值:.c .c
$(suffix a.c b.c)

basename函数

1
$(basename ...)

功能:取前缀函数,包括目录。
实例:

1
2
#返回值:/home/test a
$(basename /home/test.cpp a.cpp)

addsuffix函数

1
$(addsuffix <suffix> <name....>)

功能:给指定文件序列添加后缀名
实例:

1
2
#返回值:a.c b.c c.c
$(addsuffix .c a b c)

addprefix

1
$(addprefix <prefix> <name...>)

功能:给指定文件序列添加前缀
实例:

1
2
#返回值:src/a src/b src/c
$(addprefix src/ a b c)

join函数

1
$(join <list1> <list2>)

功能:将两个字符串中的list对应项连接
实例:

1
2
3
4
#返回值:an 2b 3
$(join 1 2 3, a b)
#返回值:1a 2b c
$(join 1 2, a b c)

makefile中链接静态库顺序问题

在链接静态库的时候,如果多个静态库之间存在依赖关系,则有依赖的静态库之间存在顺序问题,若顺序出现错误,则可能出现函数未定义或符号找不到等错误。

静态库链接的顺序的原则是:被依赖的库一定要放在后面,因为makefile在链接静态库时的顺序是从右往左(或从后向前).如libb.a依赖于liba.a,此时的链接顺序应该是:-Llibb.a -Lliba.a。

会出现上述问题的原因是:我们在生成静态库的时候并未把依赖库的定义编到生成的库中。如

1
2
3
4
gcc -c a.c
ar cr liba.a a.o
gcc -c b.c
ar cr libb.a b.o # 虽然libb.a使用到了liba.o中的一些函数,但并不会将它们的定义包含进来,所以在链接test时需要指定这两个库

Comment and share

System V IPC 机制和key_t本质类型

System V三种IPC机制

System V IPC包括三种不同的通信机制

  • 消息队列:消息队列类似于管道,但是又有很大的差别。第一,消息队列是有边界的,所以消息队列的通信机制是通过消息进行传递,而管道是通过字节流进行通信。第二,每条消息包括一个完整的整形字段,消息队列可以通过类型来选择消息。
  • 信号量:用于同步两个进程。
  • 共享内存:共享内存允许两个进程共享同一个内存段。即一块内存被映射到不同进程的虚拟内存中。

key_t本质

System V系统调用都有一个相关的get系统调用,它与文件io中的open函数类似,不同的是它的参数是一个整数,给定一个整数之后,系统会做以下操作:

  • 使用给定key创建一个新的IPC对象,并将IPC对象标识key_t返回给调用者.
  • 若该key对应的IPC对象存在,直接将标识返回给调用者,相当于使用key换取标识,而不做创建操作.

上述IPC对象的标识类型为key_t。key_t的本质是什么?我们可以通过grep命令追踪到

在文件/usr/include/sys/ipc.h中:

1
2
3
4
#ifndef __key_t_defined
typedef __key_t key_t;
# define __key_t_defined
#endif

在文件/usr/include/bits/types.h中:

1
__STD_TYPE __KEY_T_TYPE __key_t; /* Type of an IPC key. */

在文件/usr/include/bits/typesizes.h中:

1
#define __KEY_T_TYPE __S32_TYPE

在文件中:

1
#define __S32_TYPE int

故从上面的追踪,可以看出key_t的本质为int类型.

获取key_t方法

获取ket_t的方法三种

  • 随机选取一个整数值作为key值,多个进程共享一个key值。注意此时不能使用已经存在的key
  • 在创建IPC对象的get方法中用IPC_PRIVATE作为key值,这样会导致每个调用都会创建一个新的IPC对象
  • 使用ftok函数生成一个key,这个方法产生的key接近唯一。

IPC_PRIVATE

IPC_PRIVATE本质上为0的宏定义.使用方法是直接使用IPC_PRIVATE作为key生成一个IPC对象。这种做法保证每次产生的都是全新的IPC对象。
这种做法适用于在父进程fork前创建IPC对象,从而达到子进程继承IPC对象的目的。

ftok生成key

函数原型:

1
2
#include <sys/ipc.h>
key_t ftok(char* pathname, int proj);

函数功能:

此函数的功能是根据pathname和proj的值生成key,该算法只使用proj的最低的8个有效位,应用程序必须确保pathname引用一个可以应用stat()的既有文件。否则该函数会返回-1。

在linux中,key_t一般是32位的值。它通过取proj参数的最低8个有效位,包含该文件所属的文件系统的设备的设备号的最低8个有效位以及pathname所引用文件的inode号的最低16个有效位组合而成,这样保证了唯一性。所以从上面可以看出,对于同意个文件的不同路径(inode值相同),proj值相同,此时产生的key是相同的。 

Comment and share

Linux中umask深入理解

umask(user file-creatiopn mode mask)为用户文件创建掩码,是创建文件或文件夹时默认权限的基础。通常我们可以使用chmod修改linux中文件的权限.umask的作用与chmod的效果相反,具体看下面。

若没有文件掩码时,文件的默认权限为0666,文件夹的默认权限为0777。

原因:

  • 创建文件一般是用来读写,所以默认情况下所有用户都具有读写权限,但是没有可执行权限,所以文件创建的默认权限为0666
  • 而文件夹的x权限表示的是打开权限,所以这个权限必须要有,所以文件夹的默认权限为0777。

用户掩码作用

上述的权限是在没有umask情况下的默认权限。但是系统为了保护用户创建文件和文件夹的权限,此时系统会有一个默认的用户掩码(umask),大多数的Linux系统的默认掩码为022。用户掩码的作用是用户在创建文件时从文件的默认权限中去除掩码中的权限。所以文件创建之后的权限实际为:

1
2
#文件创建权限
默认权限(文件0666,文件夹0777)-umask

所以在用户不修改umask的情况下,创建文件的权限为:0666-0022=0644。创建文件夹的权限为:0777-0022=0755

查看与修改默认掩码

查看用户掩码:

1
2
3
4
#以数字方式查看掩码
umask
# 以符号形式查看掩码
umask -S

可以使用umask命令直接修改掩码。

1
umask 0000

上述方法修改的掩码只在当前tty中生效.若要全局生效,可以讲umask值写在/etc/profile或者.bashrc中

Comment and share

Unix高级编程之signal

signal函数相关的细节描述详见另外两篇篇博客,这里不详细赘述:
https://langzi989.github.io/2017/09/08/C++%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88%E5%AD%A6%E4%B9%A0/
https://langzi989.github.io/2017/05/04/Wait%E5%87%BD%E6%95%B0%E8%AF%A6%E8%A7%A3/

显示信号的描述

信号的个数可以用宏NSIG获取。

显示信号的描述有三种方法:

1
2
3
4
5
6
7
8
9
#include <string.h>
//first method
char* strsignal(int sig);
//second method,
void psignal(int sig, char* msg);
//third memthod
sys_siglist[sig];

上述三种方法的区别

sys_siglist是直接存储信号描述的数组,一般情况下,推荐使用strsignal。

strsignal和psignal函数对locale敏感,会打印出当地的语言。
调用psignal会在本地的错误出输出流输出,msg:strsignalmsg;

如:

1
2
//此时错误数据流将会打印出:SIGINT:Interrupt
psignal(SIGINT, "SIGINT");

信号集

许多相关的系统调用涉及到一组不同的信号,这时候需要信号集。linux中使用sigset_t结构体来表示信号集。一般情况,信号集是使用掩码实现的,但是可能有一些是其他实现方式。
信号集结构体相关的函数.

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
#include <signal.h>
//初始化空信号集。注意不可使用memset或者静态变量全局变量初始化信号集为空,这样会出问题。因为不是所有的信号集都是通过掩码实现的
//0出错,1成功
int sigemptyset(sigset_t* set);
//初始化信号集包括所有的信号
//0出错,1成功
int sigfillset(sigset_t* set);
//向信号集中添加信号
//0出错,1成功
int sigaddset(sigset_t* set, int sig);
//从信号集中去除信号
//0出错,1成功
int sigdelset(sigset_t* set, int sig);
//检查某一信号是不是在当前信号集中。返回1在,0不在
int sigismember(const sigset_t* set, int sig);
//以下三个为GNU C中的非标准函数,
#define _GNU_SOURCE
//对两个信号集作交集存储于dest中
int sigandset(sigset_t* dest, sigset_t* left, sigset_t* right);
//对两个信号集做并集存储于dest中
int sigorset(sigset_t* dest, sigset_t* left, sigset_t* right);
//判断信号集是否为空
int sigisemptyset(const sigset_t* set);

信号掩码(进程中阻塞信号传递)

内核会为每个进程维护一个信号掩码(标识一组信号),当一个信号被传递到该进程的时候,若该信号在信号掩码中,进程会阻塞该信号的传递,直到将该信号从信号掩码中剔除。

向信号掩码中添加一个信号的方式有以下几种:

  • 当调用信号处理器程序的时候,可将引发该调用的信号自动添加到信号掩码中,这取决于sigaction函数在安装信号时使用的标志。
  • 使用sigaction函数建立信号处理程序时,可以指定一组额外信号,当调用该处理器程序时将阻塞。
  • 使用sigprocmask函数修改进程的信号掩码。

sigprocmask函数

1
2
#include <signal.h>
int sigprocmask(int how, const sigset_t* set, sigset_t* old);

参数:

  • how : 指定修改信号掩码的方式,有三种方式
    • SIG_BLOCK : 向指定信号中添加指定信号.
    • SIG_UNBLOCK: 将指定信号从原有的信号掩码中移除。若被移除的信号掩码不存在不报错
    • SIG_SETMASK: 直接设置(赋值),覆盖原有的值
  • set : 需要设置的新的信号掩码集
  • old: 旧的信号掩码集。可在设置信号掩码集之后回复原有的信号掩码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <unistd.h>
#include <signal.h>
int main() {
time_t start = time(NULL);
sigset_t new_set, pre;
sigemptyset(&new_set);
sigaddset(&new_set, SIGINT);
if (sigprocmask(SIG_SETMASK, &new_set, &pre) == -1) {
std::cout<< "sigprocmask set error" << std::endl;
}
while (true) {
sleep(1);
time_t end = time(NULL);
if (end - start >= 15) {
std::cout << "hahah 接触阻塞" << std::endl;
sigprocmask(SIG_SETMASK, &pre, NULL);
}
}
}

sigpending获取正在等待状态的信号

若进程接收信号被阻塞之后,我们希望获取被阻塞的信号,则可以使用sigpending函数

1
2
#include <signal.h>
int sigpending(sigset_t* set);

使用此函数的场景是:若某个进程接收到被阻塞的信号,如果希望这些信号被移出阻塞队列,此时可以通过sigpending获取被阻塞的信号,然后将此信号的处理器函数IGNORE,并将其剔除信号掩码即可。

在信号被阻塞的时候,不对信号做排队处理,即即使进程阻塞了100个SIGINT信号,此时当SIGINT从信号掩码中去除时,该进程接收的还是只是一个SIGINT信号。

sigaction函数

除了signal函数之外,sigaction系统调用是设置信号处理的另一个选择。sigaction和signal函数相比更加灵活和具有可移植性。sigaction允许在不改变信号处理器程序的情况下获取信号的默认处理方式。

Comment and share

linux中ulimit命令简单使用

系统性能是一个受关注的话题,如何通过最简单的设置来实现有效的性能调优,如何在有限资源的条件下保证程序的运作,ulimit是我们在处理问题的时候经常使用的一种手段。ulimit是一种linux系统内置的功能。下面将列举ulimit的一些常用关键字和简单用法。

ulimit命令参数

ulimit参数命令

注意ulimit设置分软硬区别,加-H为硬,-S为软.默认查询是显示的是软,但使用ulimit进行设置的时候为软硬都作用。

参数 功能 示例
-H 设置硬资源限制,一旦设置不能增加。 ulimit – Hs 64;限制硬资源,线程栈大小为 64K。
-S 设置软资源限制,设置后可以增加,但是不能超过硬资源设置。 ulimit – Sn 32;限制软资源,32 个文件描述符。
-a 显示当前所有的 limit 信息。 ulimit – a;显示当前所有的 limit 信息。
-c 最大的 core 文件的大小, 以 blocks 为单位。 ulimit – c unlimited; 对生成的 core 文件的大小不进行限制。
-d 进程最大的数据段的大小,以 Kbytes 为单位。 ulimit -d unlimited;对进程的数据段大小不进行限制。
-f 进程可以创建文件的最大值,以 blocks 为单位。 ulimit – f 2048;限制进程可以创建的最大文件大小为 2048 blocks。
-l 最大可加锁内存大小,以 Kbytes 为单位。 ulimit – l 32;限制最大可加锁内存大小为 32 Kbytes。
-m 最大内存大小,以 Kbytes 为单位。 ulimit – m unlimited;对最大内存不进行限制。
-n 可以打开最大文件描述符的数量。 ulimit – n 128;限制最大可以使用 128 个文件描述符。
-p 管道缓冲区的大小,以 Kbytes 为单位。 ulimit – p 512;限制管道缓冲区的大小为 512 Kbytes。
-s 线程栈大小,以 Kbytes 为单位。 ulimit – s 512;限制线程栈的大小为 512 Kbytes。
-t 最大的 CPU 占用时间,以秒为单位。 ulimit – t unlimited;对最大的 CPU 占用时间不进行限制。
-u 用户最大可用的进程数。 ulimit – u 64;限制用户最多可以使用 64 个进程。
-v 进程最大可用的虚拟内存,以 Kbytes 为单位。 ulimit – v 200000;限制最大可用的虚拟内存为 200000 Kbytes。

ulimit参数作用范围

  • 针对单个tty生效:在tty中直接运行ulimit命令的作用范围是只对当前tty生效
  • 针对单个用户生效:将命令添加到.bashrc中,将对当前用户生效
  • 针对所有用户生效:将命令添加到/etc/security/limits.conf中,可以设置针对特定用户或者所有用户的限制。

Comment and share

C++函数指针学习

使用函数指针的优点

使用函数指针有助于我们设计出更优秀,更简洁更高效的程序。在下面的情景中我们常用到函数指针:

  • 使用函数指针作为参数
  • 使用函数指针作为返回值
  • 使用函数指针作为回调函数
  • 使用函数指针数组
  • 类的静态方法和非静态方法的函数指针
  • 使用函数指针实现动态绑定
  • 在结构体中定义函数

使用函数指针提高函数的效率

当通过switch case多用多个相同类型的函数的时候,这个时候使用函数指针可以大大简化函数代码并可以明显的提高程序的执行效率。例:

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
68
#include <iostream>
#include <time.h>
#include <string>
#include <sys/time.h>
using namespace std;
#define StartTime(id) struct timeval __now1105##id;\
gettimeofday(&__now1105##id, 0);
#define EndTime(id) struct timeval __now21105##id; \
gettimeofday(&__now21105##id, 0); \
printf("timer_%s spend time:%d us\n",#id,(__now21105##id.tv_sec-__now1105##id.tv_sec)* 1000000 + (__now21105##id.tv_usec-__now1105##id.tv_usec));
double add(double a, double b) {
return a + b;
}
double sub(double a, double b) {
return a - b;
}
double multi(double a, double b) {
return a * b;
}
double div(double a, double b) {
return a/b;
}
typedef double (*op)(double, double);
void func1(double a, double b, int flag) {
switch(flag) {
case 0:
add(a, b);
break;
case 1:
sub(a, b);
break;
case 2:
multi(a, b);
break;
case 3:
div(a, b);
break;
default:
break;
}
}
//使用函数指针调用函数
void func2(double a, double b, op cb) {
cb(a, b);
}
int main() {
StartTime(func1);
for (int i = 0; i < 100000; i++) {
func1(0.2, 0.034, 3);
}
EndTime(func1);
StartTime(func2);
for (int i = 0; i < 100000; i++) {
func2(0.2, 0.034, multi);
}
EndTime(func2);
}

由上面可以看出,由于上述switch,case中调用的函数类型(返回值类型,参数个数以及对应的类型)完全一致,我们将函数指针以参数的形式传到处理函数中。
运行上述函数的结果如下:

1
2
timer_func1 spend time:2861 us
timer_func2 spend time:2178 us

可以看出使用函数指针的效率远远高于switch case

函数指针用做回调函数

来源于wiki
在计算机程序设计中,回调函数是指通过函数参数传递到其他代码的,某一块可执行代码的引用。这一设计允许底层代码调用在高层定义的子程序。如Linux C中的signal函数就是这样一个例子。

signal底层的其中一个实现版本如下:由其实现可以看出信号处理的回调函数的主要功能是将处理信号的函数指针替换为用户高层自定义的函数地址fun,从而达到当接收到该信号时底层代码调用高层定义代码的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Sigfunc *
signal(int signo, Sigfunc *func)
{
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if (signo == SIGALRM) {
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT;
#endif
} else {
act.sa_flags |= SA_RESTART;
}
if (sigaction(signo, &act, &oact) < 0)
return(SIG_ERR);
return(oact.sa_handler);
}

当我们使用signal函数的时候,如下:

1
2
3
4
5
6
7
8
9
10
11
12
#include <signal.h>
#include <stdio.h>
void signalcb(int signum) {
printf("this is the signal:%d", signum);
}
int main() {
signal(SIGINT, signalcb);
pause();
return 0;
}

函数指针的本质及声明方法以及赋值

函数指针的本质

函数指针类似于数据指针,其本质实质上是一类函数(返回值类型相同,参数个数以及对应的参数类型相同)的入口地址,即该可执行代码在内存中的起始地址。函数指针可以让我们通过函数地址去调用该函数。有利于实现函数的动态绑定,即在运行时才决定去调用哪个函数。

函数指针的声明方法

函数指针的声明方法有两种,包括:

  • 普通函数的函数指针和类静态成员函数的函数指针
  • 类非静态成员函数的函数指针

普通函数的函数指针和类静态成员函数的函数指针:
int (funptr)(int qa, int qb);
类非静态成员函数的函数指针:
int MyClass::(
funptr)(int qa, int qb);

注意上述函数指针声明时(*funptr)中的括号不能省略,若省略,有可能会产生歧义,其意义就变成了返回值为int*的函数定义了

上述两种函数指针声明不兼容的原因如下:(《深度探索C++对象模型》)
获取类的静态和非静态函数的函数指针的地址都是其在内存中实际的地址。那为什么非静态成员的指针需要绑定(指明类)?原因是类的非静态成员需要操作类的数据成员,所以类的非静态成员需要绑定this指针找到类的数据成员。故对nonstatic函数取地址是不完整的。

函数指针的赋值与使用

函数指针赋值

对于普通指针和类的静态成员指针,有两种赋值方式:

1
2
funptr = fnc1
funptr = &fun1

对于类的非静态成员指针,只能用上面第二种形式赋值。为了保证形式的一致性和避免二义性,一般统一使用取地址符号进行赋值可避免错误出现。

1
funptr = &classInstance.func();

函数指针使用

函数指针的使用类似于赋值。
对于普通指针和类的静态成员函数指针的调用,有两种调用方式:

1
2
3
funptr(1,2);
(*funptr)(1,2);

对于类的静态成员函数,只能用上述第二种方式进行调用,为了保持一致性和避免二义性,一般统一使用使用*来解引用函数指针进行调用.

函数指针作为参数

函数指针是一个类型,将函数指针作为参数传入函数中与其他参数类似。使用方式与上述函数指针用做回调函数中signal接收参数的方式相同。这里不再详解

函数指针作为返回值

既然函数指针是函数的入口地址,所以函数指针也可以作为函数的返回值返回。不过函数指针作为函数的返回值的写反比较复杂。
若一个函数func只有一个参数int, 其返回值类型是float (*) (float, float);则其函数原型如下:

1
float (*func(int op)) (float, float);

函数指针作为函数返回值的原则是,将函数名以及参数写到*后,函数指针的返回值放在最前面,函数指针的参数放在最后面。

由上面的知识我们可以分析一下signal函数原型。

1
void (*signal)(int signo,void (*func)(int)))(int);

其定义了一个有两个参数分别为int和void (func)(int),返回值为void (func)(int)的函数。

其本质上是将函数处理指针替换为用户自定义的函数指针。那为什么需要返回值为void (func)(int)呢?原因是*signal函数的返回值是旧的信号处理函数的指针,我们可以通过这个指针暂时改变signal函数处理信号的方式。之后可以通过返回的指针恢复该信号默认的处理方式。

注意:signal的信号处理其函数中的int参数的含义是:当信号到达的时候,内核将该信号以整形的方式传给处理器函数,即为void (*func)(int sig)中的sig.

使用typedef定义函数指针

一般情况下,如果通过普通的方式定义函数指针,在使用的很不方便。这个时候我们可以通过typedef定义函数指针的新类型。通过typedef定义新类型时与普通类型定义新类型方式不同。对于普通类型,定义方式如下:

1
typedef int64_t int64;

但是对于函数指针,定义方式如下:

1
2
3
4
5
6
7
//此处定义个名字为func的类型,它表示函数指针float (*func)(float, float)类型
typedef float (*func)(float, float);
float function(float a, floatb);
//使用func定义变量
func test = &function;

Comment and share

  • page 1 of 1

魏传柳(2824759538@qq.com)

author.bio


Tencent


ShenZhen,China