Tag Archives: C

动态修改运行中进程的 rlimit

有时候发现线上运行的程序没有修改 ulimit -n ,导致文件描述符不够用,但是又不能随便重启服务,或者碰到运行中的程序可能不定时会 segment fault ,但是由于没 ulimit -c ,无法得到 core 文件调试。但是 linux 的 ulimit 命令只对当前会话有效,已经启动的进程是没法用这个命令修改限制的。这时候如果能动态修改进程的 rlimit 就会方便很多。linux 内核版本 2.6.36 以后,多了一个 prlimit 调用,可以实现动态修改某个进程的 rlimit。

例如,这个 C 写的小程序就可以放开指定进程的 core dump 限制。

#include <stdio.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/types.h>

int main(int argc, char** argv) {
    pid_t pid;
    struct rlimit new_limit;
    int result;
    if (argc < 2) {
        return 1;
    }
    pid = atoi(argv[1]);
    new_limit.rlim_cur = RLIM_INFINITY;
    new_limit.rlim_max = RLIM_SAVED_MAX;
    result = prlimit(pid, RLIMIT_CORE, &new_limit, NULL);
    return result;
}

rlimit 相关常量在 /usr/include/bits/resource.h 中有定义。这里 prlimit 的具体定义也可以通过 man 2 prlimit 查看文档。

如果有幸可以用上 util-linux 2.21 以上版本(http://www.kernel.org/pub/linux/utils/util-linux/),那么直接就会有一个 prlimit 命令可用。那就非常方便了。

但是如果内核版本较低,没有这功能又怎么办呢?其实还可以通过万能的 gdb 钩进进程调用 setrlimit 来实现。于是,可以包装一个脚本出来:

#!/bin/bash

f="0"
while getopts ":c:d:e:f:i:l:m:n:p:q:r:s:t:u:v:x" opt; do
  if [ "$f" == "1" ]; then
    echo "too many arguments" >&2;
    exit 1
  fi
  f="1"
  v="$OPTARG"
  case $opt in
  c) r=4  ;; # RLIMIT_CORE       core file size
  d) r=2  ;; # RLIMIT_DATA       data seg size  
  e) r=13 ;; # RLIMIT_NICE       scheduling priority 
  f) r=1  ;; # RLIMIT_FSIZE      file size
  i) r=11 ;; # RLIMIT_SIGPENDING pending signals
  l) r=8  ;; # RLIMIT_MEMLOCK    max locked memory
  m) r=5  ;; # RLIMIT_RSS        max memory size
  n) r=7  ;; # RLIMIT_NOFILE     open files
  q) r=12 ;; # RLIMIT_MSGQUEUE   POSIX message queues
  r) r=14 ;; # RLIMIT_RTPRIO     real-time priority
  s) r=3  ;; # RLIMIT_STACK      stack size
  t) r=0  ;; # RLIMIT_CPU        cpu time
  u) r=6  ;; # RLIMIT_NPROC      max user processes
  v) r=9  ;; # RLIMIT_AS         virtual memory
  x) r=10 ;; # RLIMIT_LOCKS      file locks

  ?) echo "bad argument $opt" >&2; exit 1 ;;
  esac
done

shift $(($OPTIND - 1))

if echo "$v" | grep -q -E "^\-?[0-9]+$"; then
  true
else
  echo "bad rlimti value $v" >&2
  exit 2
fi

if [ `echo "$v==-1 || ($v>=0 && $v<1048576)"|bc` == '0' ];then
  echo "bad rlimti value $v" >&2
  exit 2
fi

pid=$1
bin=`readlink /proc/$pid/exe`
if [ -z "$bin" ]; then
  echo "process $pid not found" >&2
  exit 3
fi

cmd='
set $rlim=&{-1ll,-1ll}
print getrlimit('$r',$rlim)
set *$rlim[0]='$v'
print setrlimit('$r',$rlim)
quit
'

result=`echo "$cmd" | gdb $bin $pid 2>/dev/null | grep '(gdb) \$2 ='`
result="${result##*= }"
exit $result

sudo ./rlimit.sh -c -1 12345

就可以 12345 放开进程的 core dump

参数同 ulimit ,-1 表示 unlimited。不过用这种方法,只能调整 soft rlimit ,而且没法超过原有的 hard rlimit。

修改以后,可以通过 cat /proc/<pid>/limits 查看效果

补充(感谢zolker):
对于 redhat 系,如 centos 6.2 之后可以通过

echo -n 'Max processes=10240:10240' >/proc/<pid>/limits

来修改。这里的 limit 的名称就是

cat /proc/<pid>/limits

里看到的

php扩展开发学习笔记 7

引用计数

  php采用的内存管理和垃圾回收方法是基于引用计数的。之前说过,在zval结构里有一个refcount是表示引用计数,还有一个is_ref表示是否是个引用变量。那么php代码的实际运行中,又是如何处理的呢?

  比如这样的php代码:
$a = “hello”;
$b = $a;
这时候并不像很多人认为的那样,在内存里把”hello”这个字符串复制了一份,而只是把$b指向了和$a对应的同一个zval,然后把那个zval的refcount + 1。这样避免了一次内存拷贝。但如果在这之后改变了其中一个变量的值,比如$b.= ” world”;又会如何呢?这时候才会分配一个新的zval给$b,然后把原先那个zval的refcount – 1。这就是传说中的copy on write。就是说,在改变值得时候才会有内存拷贝。

  那么引用变量又会如何呢? 比如
$a = “hello”;
$b = &$a;
和前面一样,$a, $b还是指向同一个zval。只是还要把这个zval的is_ref置为1。之后再改变$a或者$b的时候就不会再发生拷贝。那么
$a = “hello”;
$b = &$a;
$c = $a;
这时又会如何呢?因为$c并不是一个引用变量,因此不能和$a, $b共用一个zval。因此在$c = $a的时候会直接产生一个新的zval。

  因此,在php中,使用引用对改善性能并不会有多少作用,通常情况下还会使情况更糟。所以,引用还是只在真正需要的时候才用为好。

  再说说垃圾回收。每个zval都有一个refcount表示它的变量的引用数。不管对于普通变量还是引用变量都是如此。refcount的初始值一般为1。每当增加一个引用时就+1,减少一个引用,比如unset时就会-1。当refcount为0的时候,php就会把它释放掉。这就是基于引用计数的垃圾回收方法。

使用zval

初始化zval
MAKE_STD_ZVAL(zval*);
这个宏的左右是创建一个zval,完成初始化(如将ref_count置为1,isref置为false)并把指针赋给参数。

赋值
写扩展的时候不可避免的要用到把一个zval复制到另一个zval,就是类似$a = $b;的操作。对于简单的值或许手动维护引用计数之类的还不算很麻烦但对于数组,对象之类的就需要一层层递归进去,因此就有了一个zval_copy_ctor来做着件事情。
原有一个zval* p_zval_b,
zval* p_zval_a;
MAKE_STD_ZVAL(p_zval_a); //初始化p_zval_a
*p_zval_a = *p_zval_b;
zval_copy_ctor(p_zval_a);
这里,zval_copy_ctor完成了类似赋值的操作,包括引用计数处理,对于hash值的成员处理等。

 释放一个zval则是使用zval_ptr_dtor(**zval)。注意它的参数。它会释放掉为这个zval所分配的内存。

 

php扩展开发学习笔记 6

内存管理

  在C语言里,分配内存有一组alloc函数,比如malloc、calloc、realloc。php为了更方便,更安全地管理内存,自己提供了一组内存分配的函数:emalloc、ecalloc、erealloc,此外还有estrdup用于拷贝内存,efree用于释放由前面那些函数分配的内存。他们的用法和标准C函数的一样。这组e开头的函数分配的内存空间只存在于一次请求。在请求结束后,php会自动释放掉。这就减少了由于没有释放掉内存而造成的内存泄露的可能。

  如果需要分配的内存在整个进程运行时都保持,而不是在请求处理结束后自动释放,则还有一组pe开头的函数,如pemalloc、pecalloc等。这组函数比标准C函数的多一个参数,即分配的内存是否持久,当这个参数为1时,即表示分配的是持久的内存,不会随着请求结束而自动释放。如:pemalloc(sizeof(long) * 100, 1);。

  此外,为了方便使用,还提供了safe_emalloc和safe_pemalloc这两个函数。 safe_emalloc的原型如下:void *safe_emalloc(size_t size, size_t count, size_t addtl);。参数分别表示单元的大小,单元的个数,以及偏移。实际分配的空间大小就是size * count + addtl。safe_pemalloc与之类似。这两个函数还有一个作用就是避免手工做上面的计算时,可能造成整数溢出,而导致分配的内存大小小于预期或者是个负数。

 

php扩展开发学习笔记 5

  有许多函数在php手册里把参数或返回值类型写为mixed。那么在扩展中是如何处理的呢?所谓mixed往往就是直接解析会返回zval。

  先看参数处理,还是前面用过的zend_parse_parameters:
zval* item;
zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, “z”, &item);
这很简单。

  然后看返回值,返回zval可以用RETURN_ZVAL宏。和前面的RETURN_LONG之类不同的是,这个宏有3个参数。第一个参数就是一个zval*。第二个和第三个参数都是0、1值。第二个参数是表示是否在返回前复制该值;第三个参数是表示在返回前是否调用析构函数。通常的用法是RETURN_ZVAL(p_zval, 1, 0);。为什么一般要把复制zval设为1呢?这和php的内存管理有关,关于内存管理后面会说。这里大概说下,因为php的垃圾回收是基于引用计数的。如果不复制一份,在函数结束后,因为返回值也是一个zval的指针,结束后php会把这个zval的引用计数-1,在一般情况下,这就会导致释放掉不该释放掉的内存,造成错误。

 

php扩展开发学习笔记 4

zval和zvalue_value

  之前一直跳过的数组和对象,以及需要返回或者处理多种类型时,都需要了解php内部是如何保存值的。

  php内部保存值的基本单位是zval。它的C结构定义如下:

typedef struct _zval_struct {
    zvalue_value value;
    zend_uint refcount;
    zend_uchar type;
    zend_uchar is_ref;
} zval;

其中is_ref表示这个值是否是一个引用。
type表示值类型:有IS_NULL、IS_BOOL、IS_LONG、IS_DOUBLE、IS_STRING、IS_ARRAY、IS_OBJECT、IS_RESOURCE这8种。
refcount表示引用计数。
value存放这个zval的值。

  在判断一个zval的值时,可以用如下的代码:

zval *a;

if (Z_TYPE_P(a) == IS_LONG){
    …
}

  zvalue_value中保存了zval的值,它的C结构定义如下:

typedef union _zvalue_value {
    long lval;
    double dval;
    struct {
        char *val;
        int len;
    } str;
    HashTable *ht;
    zend_object_value obj;
} zvalue_value;

  这是一个union型,其中的lval、dval、str、ht、obj分别表示整数、浮点、字符串、数组、对象。

  取
得一个zval的值当然也可以直接从它的value里直接取,不过更好的办法是通过一套宏来获取。Z_BVAL、Z_LVAL_P、Z_DVAL_PP这
样的形式。都以Z_开头,后面是类型,B就是boolean,L就是long,D就是double。后面可选的_P,_PP表示指针的层次。带_P是用来
取zval*的,带_PP是用来去zval**的。这些宏返回的就是这个zval的值所对应的C类型。对于字符串,则有两组宏Z_STRVAL、
Z_STRLEN。后面当然也可以加_P和_PP。分别返回char*和字符串的长度。这些宏使用很简单,例如:

zval* a;

long n;
n = Z_LVAL_P(a);

  前面用到的Z_TYPE_P其实也是一个类似的宏,用于取得zval*的类型

php扩展开发学习笔记 3

取得参数

  既然是函数总要能获取参数。在php扩展里能通过zend_parse_parameters()将参数解析为一些C的对应类型。


long foo;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, “l”, &foo) == FAILURE) {
RETURN_NULL();
}
会试图解析参数为整数。将值放入foo。失败的时候,比如没有参数,或参数不能解析为整数时返回FAILURE。

这里的”l”表示整数。其他如”b”,”d”,”s”分别表示布尔,浮点和字符串。资源数组对象等以后再说。

其中,布尔对应的C类型是zend_bool。其实就是0,1。浮点对应的是double。

对于字符串,则需要传入两个参数,分别用于存放字符串的值和长度。如
char *name;
int name_len;
zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, “s”,
&name, &name_len);
此处由zend_parse_parameters分配的内存不需要手动释放。

如果需要解析多个参数,可以如下面的例子:

long foo;
char *name;
int name_len;
zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, “sl”,
&name, &name_len, &foo);

 

 

php扩展开发学习笔记 2

函数返回值

  php的api里定义了很多宏来实现扩展里函数的返回值。RETURN_BOOL、RETURN_LONG、RETURN_DOUBLE这3个宏分别用于返回对应的值。只在参数里填入需要返回的值即可。如RETURN_LONG(1024);。对于string,则有RETURN_STRING和RETURN_STRINGL。其中后者可以指定字符串的长度,前者只是以作为结束标志。第二个参数表示是否复制字符串的值。如果char数组是在栈空间内分配的(如作为局部变量的字符串常量)就需要指定为1。

  实际上,php的扩展中,函数的返回值是通过一个return_value的变量来传递的。如果煮一下PHP_FUNCTION所展开的内容的话,会发现return_value实际上是这个函数的一个参数。前面那几个宏所作的事情就是,对return_value赋值然后return。对return_value赋值则还有一组RETVAL_开头的宏。如RETVAL_BOOL等。

 

php扩展开发学习笔记 1

  开发环境设置见《用vc 2008编译php扩展》。

  写一个扩展最基本的就是编写函数。我这里是用skel生成了一个algorithm的扩展的骨架。

  php扩展中的函数用PHP_FUNCTION宏定义。首先在.h文件中写一个定义,如skel生成的代码为例:PHP_FUNCTION(confirm_algorithm_compiled);
然后在.c文件中写函数的实现。
PHP_FUNCTION(confirm_algorithm_compiled)
{
    //…
}
  这和传统的C编程很像。括号内就是函数的名字。这里没有参数列表,函数的参数是通过其他途径获取的。然后,还需要在扩展的函数入口表里添加一条:PHP_FE(confirm_algorithm_compiled, NULL)。这样在php里才能找到这个函数。这里FE应该就是function entry的缩写。

zend_function_entry algorithm_functions[] = {
    PHP_FE(confirm_algorithm_compiled, NULL)
    {NULL, NULL, NULL} /* Must be the last line in algorithm_functions[] */
};

  这里的{NULL, NULL, NULL}的作用如skel生成的代码中的注释所说,是函数入口表的结束标志。

 

 

Windows上创建硬链接

  Unix上可以方便的创建软链接和硬链接。可以提供额外的访问文件的接口而无需复制文件,大大减小了重复文件的空间浪费和维护问题,也减少了大文件复制的开销。
  Windows上只能创建硬链接(快捷方式有点类似软链接,不过还是有不小的区别的),要求Win2000以上,而且只能在NTFS分区上用,也不能跨分区创建链接。但起码比没有好。
  下面这个程序就是用来创建硬链接的。建立以后,修改一下其中一个文件,看一下另一个是不是也一起变了。^_^

  用法:编译以后,比如生成nthl.exe。
     nthl 目标链接 原始文件
  如果发生错误,会输出错误信息。

源代码:

/* nthl.cpp */
/**
 * 创建硬链接
 *
 * @author XieZhenye
 *
*/
#ifdef _WIN32_WINNT
    #undef _WIN32_WINNT
#endif
#define _WIN32_WINNT 0x0500

#include
#include
using namespace std;

int main(int argc, char *argv[]){
    if(argc < 3){
        cout<<"not enough arguments. expected 2 but "<        return 0;
    }
    
    if(CreateHardLinkA(argv[1], argv[2], NULL)==0){
        cout<<"Hard link create failed. Error code: "<    }
    return 0;
}

  程序很简单,但我自己编译的时候碰到了不少麻烦。MS自己的东西,用VC6怎么也编译不通过,最后还是用Dev C++才编译出来的。

;>()>;>