本文原地址: http://www.feitianzhi.com/boke/index.php/archives/47/

转载请注明出处,有疑问或错误请发邮件到xiaozhi@fslib.org 或加QQ群:869598376


概述

      软件的开发离不开配置,传统的软件设计包括前端、后台和数据库3部分,三者是密切配合的统一整体,在实际项目中往往遇到以下问题:

  1. 因项目需求不明而增加、修改、删除参数导致配置结构调整后难以同已有数据兼容;
  2. 后台参数修改后,前端需要同步修改,无法做到老版本前端与新版本后台配合(新版本后台可能修正了bug,老版本前端属于老项目),修正老项目bug需要在老版本分支上进行,代码分支多,维护困难;
  3. 配置备份困难。首先后台与数据库之间有多个访问渠道,配置备份需要停机备份;其次备份的配置无法线上热还原(有差异部分相关模块重置,无修改部分持续工作,如仅1相机参数有区别,仅停掉1相机进行重连,其余相机持续工作);最后备份的配置无法在新版本或老版本上进行还原(因为字段结构不一样);
  4. 授权困难,配置臃肿。为实现所有用户需求,软件的参数量非常庞大,而对一个具体项目可能只需要其中的很少只关联10个参数的功能,把大量的参数置于系统中会拖慢系统(一个表只有1列可以允许插入1亿行,但如有1000列,可能表就只能插入10万行了)同时软件中需要通过授权判断而限制用户在授权范围内操作,系统调试困难--授权是在做一个减法操作;

思考与假设

      “小雉视频系统”是一套集所有客户需求于一体的一套视频软件,参数的修改随客户的要求变化而变化,均为不可预知的参数需求,为每位客户单独开一个分支会增加bug修正的同步成本和升级包制作成本,“小雉视频系统”迫切需要一套廉价的高效率的配置设计方案,要求方案具备如下特性:

  1. 配置项在各个模块中申明(限制值的类型、范围、个数和有效条件(比如A参数必须要B参数为1时才有效可设置)),在模块中读取,把参数的管理放到各个模块中,各个模块只需要根据本模块的需要申明参数,实现参数的模块化(移除此模块也移除了此模块的参数);
  2. 各个模块申明配置项时如同全新开发一样申明参数即可,同历史参数的兼容归配置模块算法实现;
  3. 各个模块如果未被授权,则不申明参数,通过减少参数增加系统的容量及并发,既功能少,并发高,让授权是一个加法操作;
  4. 申明参数时指定默认值,在客户端传参时未有对应参数时读取此参数时读到的值为默认值,以此实现对历史客户端的兼容(历史客户端与新版本后台字段有差异部分使用默认值代替,保证新后台可同老客户端适配);
  5. 申明参数时同时申明中文名、注释、可选值,即把注释写入配置,在配置导出时可自动对参数注释,实现文档源码化;
  6. 配置可以热导出,导出后的配置可热导入到任意版本的后台,配置模块可自动探测到变化,自动完成对象实例的增删改动作;
  7. 配置可以导出xml,json等格式,并支持xml,json的导入;
  8. 配置可导出非默认值部分字段,也能导入,实现瘦客户端编程(服务器可以集所有功能与一体,但针对具体的项目客户,可能只需要部分功能,可在完整客户端上正确配置对应项目需求的功能参数后导出参数,瘦客户端上只针对导出参数进行开发,减少参数可提高客户端的人性化程度);

小雉配置解决方案

      基于假设“小雉配置”采用面向属性的配置设计方式,把配置项分为节点型、模板、字符串、整型、浮点和二进制共6种数据类型,每种数据类型可设置多个条件组,以条件组内的所有条件为真此条件组为真,任何一个条件组为真则参数有效;
      “小雉配置”采用C语言编写,可用于windows,linux,arm等平台,配置为单文件,本地可拷贝备份,远程可热导入导出xml和json,配置可承载数千参数(结构可类似xml任意层级嵌套)数十万级别的量(类比一张表有上千列,表可以容纳数十万行);


小雉配置数据类型

  1. 节点型类型
          类似于xml中的a->b->c,其中b节点包含c,则b为节点型类型,且a下面有且只能有一个b;节点型类型可以作为节点型节点和模板节点的子类型,可以设置中文名、注释、条件判断,不能设置默认值,可选值;
    fs_Config_node_node_add //添加节点型参数
    fs_Config_node_get_first //获取节点型参数
  2. 模板类型
          类似于xml中的a->b->c,其中b节点包含c,如b固定且只有一个则b为节点型类型,如b可以是0到多个,则b为模板类型,申明模板实质是申明一个类,配置时在a下创建此类的多个实例(创建多少个实例,a下有多少个b,b的子节点与类的结构完全相同);模板类型主要用于同类型数据的管理(比如添加相机);模板类型可以作为节点型节点和模板节点的子类型,可以设置中文名、注释、条件判断、可创建实例的个数、时间控制参数(比如有两个模板实例,第一个模板实例在0-7点生效,另外一个在7-24时生效,实现参数随时间变化的控制),不能设置默认值,可选值;
    fs_Config_node_template_add //添加节点型参数
    fs_Config_node_template__IO //获取节点型参数
  3. 字符串类型
          字符串类型是一个储存字符串数据的节点类型;字符串类型可以作为节点型节点和模板节点的子类型,可以设置中文名、注释、条件判断、可设置值的个数、字符串长度、默认值、可选值;
    fs_Config_node_string_add //添加字符串类型参数
    fs_Config_node_string_get_first //获取字符串类型参数
  4. 整型类型
          整型类型是一个储存64位有符号整数的节点类型;整型类型可以作为节点型节点和模板节点的子类型,可以设置中文名、注释、条件判断、可设置值的个数、值的范围、默认值、可选值;
    fs_Config_node_integer_add //添加整型类型参数
    fs_Config_node_integer_get_first //获取整型类型参数
  5. 浮点类型
          浮点类型是一个储存64位浮点的节点类型;浮点类型可以作为节点型节点和模板节点的子类型,可以设置中文名、注释、条件判断、可设置值的个数、值的范围、默认值、可选值;
    fs_Config_node_float_add //添加浮点类型参数
    fs_Config_node_float_get_first //获取浮点类型参数
  6. 二进制类型
          二进制类型是一个储存任意数据类型的节点类型;二进制类型可以作为节点型节点和模板节点的子类型,可以设置中文名、注释、条件判断、可设置值的个数、数据长度范围、默认值、可选值;
    fs_Config_node_binary_add //添加二进制类型参数
    fs_Config_node_binary_get_first //获取二进制类型参数

小雉配置的简单演示

int main() {
// 创建配置
FsConfig * const pConfig = fs_Config_new__IO();
// 创建一个节点型节点
void *const node = fs_Config_node_node_add(pConfig, pConfig, "node", "节点", "测试节点", 0, 0x7);
{
    /* 在node下创建一个字符串节点,节点可以设置2个值,长度为0到10个字节 */
    void *const testString = fs_Config_node_string_add(pConfig, node, "testString", "测试字符串", "测试字符串", 0, 0x7, 0, 10, 2);
    /* 为testString添加一个default1的默认值 */
    fs_Config_node_string_add_value(pConfig, testString, FsConfig_nodeValue_default, "default1", "默认值1", "默认值1");
    /* 为testString添加一个optiona1的可选值 */
    fs_Config_node_string_add_value(pConfig, testString, FsConfig_nodeValue_optional, "optiona1", "可选值1", "可选值1");
    /* 在node下创建一个整型节点,节点可以设置2个值,取值范围为0到666666 */
    void *const testInt = fs_Config_node_integer_add(pConfig, node, "testInt", "测试整数", "测试整数", FsConfig_nodeShowType_default, 0, 0x7, 0, 666666, 2);
    /* 为testInt添加一个0的默认值 */
    fs_Config_node_integer_add_value(pConfig, testInt, FsConfig_nodeValue_default, 0, "0", "0");
    /* 为testInt添加一个100的可选值 */
    fs_Config_node_integer_add_value(pConfig, testInt, FsConfig_nodeValue_optional, 100, "100", "100");
    /* 在node下创建一个浮点节点,节点可以设置3个值,取值范围为0.0到1.0 */
    void *const testFloat = fs_Config_node_float_add(pConfig, node, "testFloat", "测试浮点", "测试浮点", 0, 0x7, 0.0, 1.0, 3);
    /* 为testFloat添加一个0.0的默认值 */
    fs_Config_node_float_add_value(pConfig, testFloat, FsConfig_nodeValue_default, 0.0, "0.0", "0.0");
    /* 为testFloat添加一个1.0的可选值 */
    fs_Config_node_float_add_value(pConfig, testFloat, FsConfig_nodeValue_optional, 1.0, "1.0", "1.0");
    /* 在node下创建一个二进制节点,节点可以设置2个值,长度为0到100个字节 */
    void *const testBinary = fs_Config_node_binary_add(pConfig, node, "testBinary", "测试二进制", "测试二进制", 0, 0x7, 1, 100, 2);
    /* 为testBinary创建一个条件组 */
    void *const condition_testBinary = fs_Config_condition_group_add(pConfig, testBinary);
    /* 向condition_testBinary添加一个条件,相对于testBinary节点向上一级的父节点中查找testInt节点,在testInt的值为0时此值有效 */
    fs_Config_condition_add_static(pConfig, condition_testBinary, 1, "testInt", FsConfig_Condition_equal, "0");
}
// 创建一个可以创建100个实例的模板
void *const template = fs_Config_node_template_add(pConfig, pConfig, "testTemplate", "测试模板", NULL, NULL, "测试模板", NULL, NULL, NULL, 0, 0x7, 100);
{
    /* 在template下创建一个字符串节点,节点可以设置2个值,长度为0到10个字节 */
    void *const testString1 = fs_Config_node_string_add(pConfig, template, "testString1", "测试字符串1", "测试字符串1", 0, 0x7, 0, 10, 2);
    /* 为testString1添加一个默认值 */
    fs_Config_node_string_add_value(pConfig, testString1, FsConfig_nodeValue_default, "default1", "默认值1", "默认值1");
    /* 为testString1添加一个可选值 */
    fs_Config_node_string_add_value(pConfig, testString1, FsConfig_nodeValue_optional, "optiona1", "可选值1", "可选值1");
    /* 在template下创建一个字符串节点,节点可以设置2个值,长度为0到10个字节 */
    void *const testString2 = fs_Config_node_string_add(pConfig, template, "testString2", "测试字符串2", "测试字符串2", 0, 0x7, 0, 10, 2);
    /* 为testString2创建一个条件组 */
    void *const condition_testString2 = fs_Config_condition_group_add(pConfig, testString2);
    /* 向condition_testString2添加一个条件,相对于testString2节点向上两级的父节点中查找node节点,再在node节点中查找testInt节电,在testInt的值为0时此值有效 */
    fs_Config_condition_add_static(pConfig, condition_testString2, 2, "node testInt", FsConfig_Condition_equal, "0");
}
// 把配置保存到文件,可使用小雉配置工具打开编辑
// 项目中可把配置发送给客户端
fs_Config_save_to_file_direct(pConfig, "test.cfg");
/* 定义一个xml模拟历史数据导入 */
{
    const char *str = "<testTemplate><testString1>sss1</testString1></testTemplate>"
            "<testTemplate><testString2>ssss2</testString2></testTemplate>"
            "<node><testInt>30</testInt><testFloat>0.5</testFloat></node>";
    FsXml *pXml = fs_Xml_new_from_string__IO(str, NULL);
    fs_Xml_analyzeAll(pXml, (struct FsXml_node*) pXml, NULL);
    FsEbml *pEbml1 = fs_Ebml_new_from_Xml__IO(pXml);
    fs_Xml_delete__OI(pXml, NULL);
    fs_Config_import_onlyData((FsEbml*) pConfig, (struct FsEbml_node*) pConfig, (struct FsEbml_node*) pConfig, (FsEbml*) pEbml1, (struct FsEbml_node*) pEbml1, NULL);
    fs_Ebml_delete__OI(pEbml1, NULL);
}
// 当前pConfig已包含导入的数据,可存盘
fs_Config_save_to_file_direct(pConfig, "test1.cfg");
// 读取历史的配置文件"test1.cfg",按目前在申明导入到pConfig中
{
    FsConfig * const pConfig1 = fs_Config_new_from_file__IO("test1.cfg", NULL);
    fs_Config_import_onlyData((FsEbml*) pConfig, (struct FsEbml_node*) pConfig, (struct FsEbml_node*) pConfig, (FsEbml*) pConfig1, (struct FsEbml_node*) pConfig1, NULL);
    fs_Config_delete__OI(pConfig1, NULL);
}
/* 把pConfig导出为json */
FsObjectBase * const pObjectBase = fs_Config_export_objectBase__IO(pConfig, FsConfig_ExportType_json_export, sizeof (FsObjectBase), 0, NULL);
printf("%s\n", pObjectBase->data);
/* 打印数据为:
 * {
 *     "node":{
 *         "testString":"default1",
 *         "testInt":"30",
 *         "testFloat":"0.500000",
 *         "testBinary":""
 *     },
 *     "testTemplate":[{
 *         "testString1":"sss1",
 *         "testString2":""
 *     },{
 *         "testString1":"default1",
 *         "testString2":"ssss2"
 *     }]
 * }
 */
/* 读取node testInt的值,打印结果为testInt=30 */
printf("testInt=%lld\n", fs_Config_node_integer_get_first(pConfig, pConfig, pConfig, "node testInt", 0, NULL));
/* 读取node testFloat的值,打印结果为testFloat=0.500000 */
printf("testFloat=%lf\n", fs_Config_node_float_get_first(pConfig, pConfig, pConfig, "node testFloat", 0, NULL));
pObjectBase->_delete(pObjectBase);
fs_Config_delete__OI(pConfig, NULL);
return 0;
}

      源码下载地址
      github:https://github.com/feitianzhi/fslib-config
      gitee:https://gitee.com/feitianzhi/fslib-config
      配置工具demo: http://www.feitianzhi.com/boke/index.php/fslib-config.html

本文原地址: http://feitianzhi.com/boke/index.php/archives/46/

转载请注明出处,有疑问或错误请发邮件到xiaozhi@fslib.org 或加QQ群:869598376


参考文章:https://www.cnblogs.com/sealin/p/13948629.html

前言:以下将会唠叨一大堆内容,建议不要读直接进入正文。现已安装了一个debian xfce的操作系统,分区表是MBR(l传统模式引导),

通过写盘工具烧录,能在烧录大多x86的板子上面运行.现有遇到一种奇葩的终端-z8350, 只支持UFFI引导, 看了bios的设置,只有寥寥

几个选项.最终判断它真的只能是UEFI引导, 不知道是bios被去掉的原因,还是这个板子或芯片原因. 由于对bios又不熟,只能从系统引导

层入手了(其实这个也不熟).那为什么不直接在8350。这款终端上面直接安装UEFI引导呢.主要两方面原因:1原本已经稍好的系统,

做了大量的系统更改,编译安装不少软件,前后升级软件依赖库的,再重新安装巨麻烦,容易漏掉哪个细节而留下bug.2 另一原因

是Debian使用UEFI安装,无法烧录到其他板子上,即使是相同型号的板子也不行。但在一些Ubuntu和fedoras的UEFI可以,发现有一

个地方不一样,fedoras的UEFI的分区表是MBR,而debian的分区表是gpt的,也许是这个原因,以前刚开始做这个debian系统时,

尝试过安装debian转换MBR分区表,要么安装失败,要么安装完又变回gpt分区表,所以就放弃这个想法了。

正文:

 1、新建一个EFI分区 (好在之前保留了一个vfat分区,可以直接拿来用,不然就只能缩容了,我的全部分挂载在/

       目录,而且还是非lvm,要对这里动刀十分麻烦。)

         (1)新建efi分区,这里使用 fdisk /dev/sdx 命令新建,然后mkfs.vfat格式化。

            还有需要将分区标记为efi分区,fdisk这个命令应该可以修改,但我没折腾,直接装gparted图像化工具改(apt install gparted)。

         (2)创建/boot/efi,目录然后将刚新建的efi分区挂载上去。

             在/etc/fstab编辑增加, (使用blkid查看分区uuid)

                UUID=xxxx-xxxx  /boot/efi vfat rw 0 0  

             # mount -a   (挂载/etc/fstab刚填写的)

        

 2、安装grub-efi

   sudo apt install grub-efi


  3、使用grub-install 生成efi引导信息 (如果不安装grub-efi会报错缺少modinfo.sh)

       # grub-install --boot-directory=/boot/efi --target=x86_64-efi --efi-directory=/boot/efi --removable

       会在/boot/efi/生成 EFI/BOOT/BOOTX64.EFI

       # grub-install --target=x86_64-efi --boot-directory=/boot/efi  --efi-directory=/boot/efi

   会在/boot/efi/生成 EFI/debian/grubx64.efi

       --boot-directory加不加也不影响最后结果,不加--boot-directory这里执行会可能报错,提示啥未注册或者环境不支持类似(记不太清),

       报错没太大关系,只要/boot/efi 目录下 生成对应的efi文件,有就可以了

4、重启进入BIOS

         进入BIOS后,找到引导项将会看到一个硬盘名字,一个是UEFI+硬盘名字, 还有一个是 写着Debian。

        (1)硬盘名字:原先的mbr传统引导

        (2)UEFI+硬盘名字:  这个是加了 --removable 用于移动设备,比如U盘

        (3)Debian:这个是没加--removable 参数,个人理解是对于非移动设备

          结果3个都可以引导进去。搞那么一个--removable,只是为了兼容性好一点,将做好这个系统,烧录到z8350也可以通过UEFI成功引导。


拓展记录:

     1,grub-pc 的-pc是指MBR分区传统引导的对应/usr/lib/grub/i386-pc

     2,grub-efi 的是指uefi引导的,安装了才会有/usr/lib/grub/i386-efi

     3,grub-pc和grub-efi这两个包冲突,只能存在一个,grub-install --target 参数依赖用到

     4,网上提到bios是否支持UEFI,查看 /sys/firmware/efi 这个目录是否存在,其实并不是很正确。

     按我实验所得出的结果是,使用传统引导就不存在/sys/firmware/efi, 而使用UEFI 引导进去

     则才会有这个目录/sys/firmware/efi ,所以并不能说明bios是否支持UEFI。

   linux使用UEFI引导可能需要把secury boot 选项关掉,由于UEFI签名的问题。

     5,由于grub-install 误操作生成一些其他的文件在/boot/efi/, 那先把这个目录所有文件清除

     6,进入grub>命令行,手动引导方法:

         (1)用 ls 查看文件 ,我的机器是列出(hd0) (hd0,msdos1) (hd0,msdos2) 

                 grub> ls (hd0,msdos2)/   查看哪个是root分区,我这里就是(hd0,msdos2)

                 grub> set root=(hd0,msdos2)

          (2) 加载/boot/grub/grub.cfg 文件,这个命令需要在set root=(hd0,msdos2)之后,不然找不到这个文件

                grub> source /boot/grub/grub.cfg   不知这条命令后面会提示no suitable video mode found

          (3) 装载内核:linux /boot/vmlinuz-xxxx root=/dev/sdx  (这里的xxxx根据你系统内核版本,按tab就可以补全, sdx 这里是根分区,根据实际情况,不能直接(hd0,msdos2)格式)

               grub> linux /boot/vmlinuz-4.9.0-9  root=/dev/sda2 

               grub> initrd /initrd.img  

         (4)启动m 看能否引导到桌面系统,反正我是成功了,如果报其他错就自行解决了。

                 grub> boot

本文原地址: http://feitianzhi.com/boke/index.php/archives/45/

转载请注明出处,有疑问或错误请发邮件到xiaozhi@fslib.org 或加QQ群:869598376


参考文章:https://blog.csdn.net/JustDoIt_201603/article/details/106629059

一、cat /proc/meminfo 各字段详解

/ $ cat /proc/meminfo
MemTotal:         877368 kB  :所有可用RAM大小(即物理内存减去一些预留位和内核的二进制代码大小)(HighTotal + LowTotal),系统从加电开始到引导完成,BIOS等要保留一些内存,内核要保留一些内存,最后剩下可供系统支配的内存就是MemTotal。这个值在系统运行期间一般是固定不变的。
MemFree:           22516 kB  :LowFree与HighFree的总和,被系统留着未使用的内存,MemFree是说的系统层面
MemAvailable:     470244 kB  :应用程序可用内存数。系统中有些内存虽然已被使用但是可以回收的,比如cache/buffer、slab都有一部分可以回收,所以MemFree不能代表全部可用的内存,这部分可回收的内存加上MemFree才是系统可用的内存,即:MemAvailable≈MemFree+Buffers+Cached,它是内核使用特定的算法计算出来的,是一个估计,MemAvailable是说的应用程序层面
Buffers:            1772 kB  :用来给文件做缓冲大小
Cached:           459224 kB  :被高速缓冲存储器(cache memory)用的内存的大小(等于 diskcache minus SwapCache )
SwapCached:           16 kB  :被高速缓冲存储器(cache memory)用的交换空间的大小,已经被交换出来的内存,但仍然被存放在swapfile中。用来在需要的时候很快的被替换而不需要再次打开I/O端口
Active:           333148 kB  :在活跃使用中的缓冲或高速缓冲存储器页面文件的大小,除非非常必要否则不会被移作他用. (Active(anon) + Active(file))
Inactive:         330384 kB  :在不经常使用中的缓冲或高速缓冲存储器页面文件的大小,可能被用于其他途径. (Inactive(anon) + Inactive(file))
Active(anon):     104368 kB  :活跃的与文件无关的内存(比如进程的堆栈,用malloc申请的内存)(anonymous pages),anonymous pages在发生换页时,是对交换区进行读/写操作
Inactive(anon):   104508 kB  :非活跃的与文件无关的内存(比如进程的堆栈,用malloc申请的内存)
Active(file):     228780 kB  :活跃的与文件关联的内存(比如程序文件、数据文件所对应的内存页)(file-backed pages) File-backed pages在发生换页(page-in或page-out)时,是从它对应的文件读入或写出
Inactive(file):   225876 kB  :非活跃的与文件关联的内存(比如程序文件、数据文件所对应的内存页)
Unevictable:        6708 kB  :
Mlocked:            1428 kB  :
HighTotal:        261888 kB  :高位内存总大小(Highmem是指所有内存高于860MB的物理内存,Highmem区域供用户程序使用,或用于页面缓存。该区域不是直接映射到内核空间。内核必须使用不同的手法使用该段内存)
HighFree:           5680 kB  :未被使用的高位内存大小
LowTotal:         615480 kB  :低位内存总大小,低位可以达到高位内存一样的作用,而且它还能够被内核用来记录一些自己的数据结构
LowFree:           16836 kB  :未被使用的低位大小
SwapTotal:        614396 kB  :交换空间的总大小
SwapFree:         611044 kB  :未被使用交换空间的大小
Dirty:                40 kB  :等待被写回到磁盘的内存大小
Writeback:             0 kB  :正在被写回到磁盘的内存大小
AnonPages:        209224 kB  :未映射页的内存大小
Mapped:           280668 kB  :设备和文件等映射的大小
Shmem:              1084 kB  :
Slab:              59840 kB  :内核数据结构缓存的大小,可以减少申请和释放内存带来的消耗
SReclaimable:      34196 kB  :可收回Slab的大小
SUnreclaim:        25644 kB  :不可收回Slab的大小(SUnreclaim+SReclaimable=Slab)
KernelStack:        7504 kB  :常驻内存,每一个用户线程都会分配一个kernel stack(内核栈)
PageTables:        15508 kB  :管理内存分页页面的索引表的大小
NFS_Unstable:          0 kB  :不稳定页表的大小
Bounce:                0 kB  :
WritebackTmp:          0 kB  :
CommitLimit:     1053080 kB  :根据超额分配比率('vm.overcommit_ratio'),这是当前在系统上分配可用的内存总量,这个限制只是在模式2('vm.overcommit_memory')时启用。CommitLimit用以下公式计算:CommitLimit =('vm.overcommit_ratio'*物理内存)+交换例如,在具有1G物理RAM和7G swap的系统上,当`vm.overcommit_ratio` = 30时 CommitLimit =7.3G
Committed_AS:   16368536 kB  :目前在系统上分配的内存量。是所有进程申请的内存的总和,即时所有申请的内存没有被完全使用,例如一个进程申请了1G内存,仅仅使用了300M,但是这1G内存的申请已经被 "committed"给了VM虚拟机,进程可以在任何时间使用。如果限制在模式2('vm.overcommit_memory')时启用,分配超出CommitLimit内存将不被允许
VmallocTotal:     245760 kB  :可以vmalloc虚拟内存大小
VmallocUsed:           0 kB  :vmalloc已使用的虚拟内存大小
VmallocChunk:          0 kB  :最大的连续未被使用的vmalloc区域

1、Inactive(anon) 和 Inactive(file),分别表示anonymous pages和mapped pages。

用户进程的内存页分为两种:与文件关联的内存(比如程序文件、数据文件所对应的内存页)和与文件无关的内存(比如进程的堆栈,用malloc申请的内存),前者称为file pages或mapped pages,后者称为anonymous pages;其中LRU lists包括如下几种,在/proc/meminfo中都有对应的统计值:

  LRU_INACTIVE_ANON  –  对应 Inactive(anon)
  LRU_ACTIVE_ANON  –  对应 Active(anon)
  LRU_INACTIVE_FILE  –  对应 Inactive(file)
  LRU_ACTIVE_FILE  –  对应 Active(file)
  LRU_UNEVICTABLE  –  对应 Unevictable

Inactive list里的是长时间未被访问过的内存页,Active list里的是最近被访问过的内存页,LRU算法利用Inactive list和Active list可以判断哪些内存页可以被优先回收。

2、MemAvailable

应用程序可用内存数。系统中有些内存虽然已被使用但是可以回收的,比如cache/buffer、slab都有一部分可以回收,所以MemFree不能代表全部可用的内存,这部分可回收的内存加上MemFree才是系统可用的内存,即:MemAvailable≈MemFree+Buffers+Cached,它是内核使用特定的算法计算出来的,是一个估计值。

3、VmallocUsed

通过vmalloc分配的内存都统计在/proc/meminfo的 VmallocUsed 值中,但是要注意这个值不止包括了分配的物理内存,还统计了VM_IOREMAP、VM_MAP等操作的值,譬如VM_IOREMAP是把IO地址映射到内核空间、并未消耗物理内存,所以我们要把它们排除在外。从物理内存分配的角度,我们只关心VM_ALLOC操作,这可以从/proc/vmallocinfo中的vmalloc记录看到。

4、KernelStack:

Kernel stack(内核栈)是常驻内存的,既不包括在LRU lists里,也不包括在进程的RSS/PSS内存里,所以我们认为它是kernel消耗的内存。统计值是/proc/meminfo的KernelStack。64bit 系统的 task_struct size 是16KB,  32bit的系统task_struct size为 8KB,每一个用户线程都会分配一个kernel stack(内核栈),内核栈虽然属于线程,但用户态的代码不能访问,只有通过系统调用(syscall)、自陷(trap)或异常(exception)进入内核态的时候才会用到,也就是说内核栈是给kernel code使用的。
 

内存黑洞:

进程通过将memoryinfo中的内存大小相加起来,发现总是比真实内存小,那是因为有内存黑洞的存在,我们知道,Kernel的动态内存分配通过以下几种接口:

alloc_pages/__get_free_page: 以页为单位分配

vmalloc: 以字节为单位分配虚拟地址连续的内存块

slab allocator

vmalloc和slab分配的内存都会被记录在meminfo中,但通过alloc_pages/__get_free_page分配的内存,没有在/proc/meminfo中统计,不知道有多少,就像个黑洞。

参考博客《android cat /proc/meminfo 字段分析》

本文原地址: http://www.feitianzhi.com/boke/index.php/archives/44/

转载请注明出处,有疑问或错误请发邮件到xiaozhi@fslib.org 或加QQ群:869598376


概述

      本文描述的内容归属于"小雉视频NVR子系统"的功能描述;

背景

      一项目在您入场时可能已存在很多历史的安防储存设备,要把已有储存设备上的视频接入自己的客户端可能需要对很多开发量,同时如需要在移动端播放这些回放视频更是存在一定的技术壁垒;
      本文介绍使用"小雉视频NVR子系统"配置gb28181接入第三方的直播及回放视频,"小雉视频NVR子系统"负责把接入的回放视频转rtsp,hls,gb28181协议输出,同时"小雉视频NVR子系统"会把访问过的视频进行缓存,让新的客户端不再从前端储存中获取(如两个rtsp,两个hls,两个gb28181客户端取同一路流的相近时间的回放视频,"小雉视频NVR子系统"只向前端储存设备取一次流);

配置

小雉视频NVR子系统

  • 在海康nvr中配置使用gb28181接入到"小雉视频NVR子系统",如下图;

小雉视频NVR子系统

  • vlc效果展示(上图为gb28181回放转hls,下图为gb28181回放转rtsp),如下图;

小雉视频NVR子系统

小雉视频NVR子系统

猜您可能喜欢

小雉系统安装:http://www.feitianzhi.com/boke/index.php/archives/11/
小雉系统网络配置:http://www.feitianzhi.com/boke/index.php/archives/15/
小雉系统硬盘配置:http://www.feitianzhi.com/boke/index.php/archives/16/
小雉系统远程升级:http://www.feitianzhi.com/boke/index.php/archives/14/
使用Google Authenticator为小雉系统增加动态密码功能:http://www.feitianzhi.com/boke/index.php/archives/17/
GB28181 级联 CDN 回放:http://www.feitianzhi.com/boke/index.php/archives/37/
小雉视频系统负载均衡之GB28181多线负载均衡:http://www.feitianzhi.com/boke/index.php/archives/28/
小雉视频系统GB28181-2016配置:http://www.feitianzhi.com/boke/index.php/archives/41/

本文原地址: http://www.feitianzhi.com/boke/index.php/archives/43/

转载请注明出处,有疑问或错误请发邮件到xiaozhi@fslib.org 或加QQ群:869598376


背景

      端口共享主要是为在一些受限环境(如防火墙限制只有http的80可被外网访问)中让自己的业务正常运行(rtsp,hls,gb28181等业务正常运行);
      端口共享技术并不是新技术,如sslh可让ssh和https共用一个端口,haproxy可让ssh,http,https等多种协议共享一个端口;
      小雉系统的主要应用为视频应用,需要同时支持webrtc,rtsp,hls,rtmp,gb28181,sip,rtp,http等协议的端口共享技术并能承载万兆流量方案才能满足小雉系统的需要;
      常见的端口共享方案(如haproxy)采用代理方式,承载万兆流量需要显著增加硬件配置,同时也无法完成视频端口共享的需要;

方案设计要求

  • 配置层支持为每个端口分配不同的协议,实现简易的权限控制(如连A端口的用户只有rtsp和hls视频权限,连B端口的用户有rtsp,rtmp,GB28181的权限);
  • 模块只提供数据匹配的模式,匹配的数据由具体的协议开发者后期添加(模块如内置直接识别各种协议,则此模块的开发者需要了解rtsp,hls,rtmp,webrtc,gb28181,rtp,http等多种协议,难度高;同时即使识别为http协议后可能还需要进行url和其他参数进行分类后传给不同的业务模块);
  • 模块匹配应根据各业务提供的识别码(rtsp,hls,rtmp,webrtc,gb28181,rtp,http各协议的开发者向本模块注册识别码)进行匹配,匹配后把socket直接交于对应的业务模块,而不通过代理中转(不中转则不需要承担各业务的大流量数据,本模块消耗的资源相较视频应用可忽略,同时中转方式需要根据内容(如sip内容识别rtp数据)也是本模块无法完成的);

端口共享模块应用

      小雉系统的端口共享技术已应用于《使用cloudflare免费cdn为小雉系统的http,rtsp,rtmp,gb28181,hls,webrtc,ftp,ssh等服务加速》,"小雉私有接口","GB28181"等应用;
      小雉今后开发的新模块都将支持"小雉的端口共享模块",但仍支持传统的模式,如《小雉视频系统GB28181-2016配置》文章描述的按传统方式使用小雉的gb28181依然有效;
      本处以把端口1111配置为同时支持"小雉私有接口","GB28181 sip","GB28181 rtp"为例说明"端口共享模块"的使用方法如下图:
端口共享模块
      附上添加"GB28181 sip"识别码的代码(就是识别连接的第一个字节是否为"R")

const char * key[] = {"R"}; // 只有一个识别码为"R"
unsigned short keyLen[] = {1}; // 每个识别码的长度
unsigned char caseInsensitive[] = {0}; // 每个识别码是否大小写敏感,0-敏感,1-不敏感
unsigned char protocol[] = {FsBindClassify_P_Match_protocol_1_sip}; // 识别码的抓取方式
fs_bindClassify_add_protocol(pBindClassify, 1, protocol, caseInsensitive
    , keyLen, key, cb, externP1, externP2, mask);

猜您可能喜欢

小雉系统安装:http://www.feitianzhi.com/boke/index.php/archives/11/
小雉系统网络配置:http://www.feitianzhi.com/boke/index.php/archives/15/
小雉系统硬盘配置:http://www.feitianzhi.com/boke/index.php/archives/16/
小雉系统远程升级:http://www.feitianzhi.com/boke/index.php/archives/14/
使用Google Authenticator为小雉系统增加动态密码功能:http://www.feitianzhi.com/boke/index.php/archives/17/
GB28181 级联 CDN 回放:http://www.feitianzhi.com/boke/index.php/archives/37/
小雉视频系统负载均衡之GB28181多线负载均衡:http://www.feitianzhi.com/boke/index.php/archives/28/
小雉视频系统GB28181-2016配置:http://www.feitianzhi.com/boke/index.php/archives/41/

本文原地址: http://www.feitianzhi.com/boke/index.php/archives/42/

转载请注明出处,有疑问或错误请发邮件到xiaozhi@fslib.org 或加QQ群:869598376


先来看看海康、大华、宇视三个安防厂家摄像机的H265 国标PS封装格式案例:

首先是海康:

海康

接下来看大华:

大华

再接下来看看宇视的:

宇视
      可以看到,三个厂家稍稍有一点区别,海康是将VSP/SPS/PPS/SEI/IDR分开单独打包成一个PES包,宇视和大华则是将它们放到一个PES包里。其实两种方式都是符合ps打包规范的。
      H265的ps打包与H264的PS打包方式一致,区别仅在于PSM中stream_type的不同:H264是0x1B,H265是0x24。另外就是H265的帧类型相比H264要多出来一倍,H264nalu-type占了5bit,H265则是6bit。

猜您可能喜欢

小雉系统安装:http://www.feitianzhi.com/boke/index.php/archives/11/
小雉系统网络配置:http://www.feitianzhi.com/boke/index.php/archives/15/
小雉系统硬盘配置:http://www.feitianzhi.com/boke/index.php/archives/16/
小雉系统远程升级:http://www.feitianzhi.com/boke/index.php/archives/14/
使用Google Authenticator为小雉系统增加动态密码功能:http://www.feitianzhi.com/boke/index.php/archives/17/
GB28181 级联 CDN 回放:http://www.feitianzhi.com/boke/index.php/archives/37/
小雉视频系统负载均衡之GB28181多线负载均衡:http://www.feitianzhi.com/boke/index.php/archives/28/
小雉视频系统GB28181-2016配置:http://www.feitianzhi.com/boke/index.php/archives/28/

本文原地址: http://www.feitianzhi.com/boke/index.php/archives/41/

转载请注明出处,有疑问或错误请发邮件到xiaozhi@fslib.org 或加QQ群:869598376


      小雉视频系统从3.165.3509版本开始支持GB28181-2016版本,SIP指令及视频流均同时支持tcp和udp;

GB28181端口配置

  • 1路具体的gb28181视频配置(储存,rtsp,rtmp,hls,gb28181转发)

1路具体的gb28181视频配置(储存,rtsp,rtmp,hls,gb28181转发)

猜您可能喜欢

小雉系统安装:http://www.feitianzhi.com/boke/index.php/archives/11/
小雉系统网络配置:http://www.feitianzhi.com/boke/index.php/archives/15/
小雉系统硬盘配置:http://www.feitianzhi.com/boke/index.php/archives/16/
小雉系统远程升级:http://www.feitianzhi.com/boke/index.php/archives/14/
使用Google Authenticator为小雉系统增加动态密码功能:http://www.feitianzhi.com/boke/index.php/archives/17/
GB28181 级联 CDN 回放:http://www.feitianzhi.com/boke/index.php/archives/37/
小雉视频系统负载均衡之GB28181多线负载均衡:http://www.feitianzhi.com/boke/index.php/archives/28/

本文原地址: http://www.feitianzhi.com/boke/index.php/archives/40/

转载请注明出处,有疑问或错误请发邮件到xiaozhi@fslib.org 或加QQ群:869598376


背景

      小雉系统中使用的rtsp,rtmp等视频协议需要主动连接才能正场工作,在小雉处在公网,相机在内网时,小雉无法主动连接内网的设备--小雉需要通过一定的方式打通内网与外网;
      小雉系统的一些部署环境仅能支持部分7层协议,如只支持http,sip,websocket等标准协议,其他协议均会被防火墙拒绝--小雉要打通内外网必须使用一常见的http,websocket协议;
      小雉系统内置大量的视频应用,使用时需要大带宽承载,小雉致力于技术研发而非内容研发,仅在少部分时间需要运行(如演示时需要大带宽),每月总流量仅几百G,阿某云按出口带宽收费的vps会让小雉支付过多的成本(40M带宽最便宜的vps一个月大概2500,1000G流量某阿云大概800);

探索

      偶然发现https://www.vultr.com/提供的云服务器可按流量计费,1000G一个月5刀(30多一个月),出口带宽百兆以上;
      vultr服务器到一些地方的网非常不稳定,延时高,丢包率大,差时只有几十kb甚至不通,完全满足不了小雉的需要;
      综合本文的背景,小雉把视线看向了cloudflare这种免费的cdn服务商,经测试,使用cloudflare后,http服务的速度能稳定维持在30m/S以上;

确定方案

      使用https://www.vultr.com/提供的云服务器作为测试用例,使用cloudflare提供的免费cdn进行加速实现小雉的专网加速;

vultr注册

  • 首先注册vultr账号,地址:https://www.vultr.com/
  • 注册好第一次进去会提示让你完善信息,首次最少充值10刀, 之后进入控制台界面,点击products,然后点右上角的加号添加server!

小雉系统

  • 如图配置,server选哪里的都可以,但是强烈建议选日本(最近上线了韩国的,也挺好用的),真的比其他地方的快好多

小雉系统

  • 这里要注意了,系统选centos8,centos7,之后配置选5刀的就可以,1000G,1G内存足够用了(小雉系统只有200M)

小雉系统

  • 选好了就可以deploy now了,之后会自动跳转到products节目,这时候你的server是installing的状态,等大概一分钟,就会running。
  • 之后点右边的三个点(server刚起来也可能是叫manage,反正就是最右边的)进入server detail。(如果之后哪天想改下ip,也可以在这里点destroy然后重新建一个)

小雉系统

  • 这里是你的server的一些详细信息,记住你的ip address和password,密码需要点那个小眼睛才能看到。(大家可以看见这里月流量是1000G,我们所有人半个月也就用了100多G,所以5刀的流量足够了)

小雉系统

小雉系统配置(本处用到的域名在下节中给出教程)

准备一个域名(准备小雉专网客户端配置时用到的域名)

      事先得准备一个可用的域名,自己买也好,还是免费注册一个都行,这里推荐一个免费注册域名的地方:www.freenom.com

  • 先注册一个账号登陆上去,点击菜单的Services,选择Register a New Domain

小雉系统

  • 在输入框里输入想要注册的域名:如lovechinacc,点击Check Availability,选择一个域名点击Get it now!

小雉系统

  • 完成后点击Checkout进入下一步

小雉系统

  • 选择免费的 12 个月期限,点击Continue下一步

小雉系统

  • 进入到结算页面后需要填写一些信息,其中红色框出来的地方如实填写,其他的随便填,最关键的是地区,如果你没国外的手机号,老老实实选择 China,不然会校验到你当前 IP 所属国家和你选择的不符会失败。

小雉系统

  • 出现下面的情况说明域名购买成功了

小雉系统

  • 如果购买成功的信息里面有如下图红框圈出来的信息则说明失败了

小雉系统

域名套上 Cloudflare(如不使用cdn加速,小雉专网客户端配置时直接填写小雉专网服务器的ip)

小雉系统

  • 输入事先注册好的域名,点击Add site;如果出现无法添加的情况,请耐心等待一会,刚注册的域名 DNS 还未来得及解析。

小雉系统

  • 选择一个计划,这里我们选择第一个免费的就行了,选择完后点击Confirm plan

小雉系统

  • 点击Add Record按钮添加 2 条 A 记录,Value 指向在vultr购买小雉服务器的IP,点击Proxy status栏中的云朵,让其变成灰色(这个很重要),弄完后点击Continue按钮继续

小雉系统

  • 此时会提示你将域名的 DNS 解析到 Cloudflare,其中有 2 个 Nameserver 是需要用到的。

小雉系统

  • 这里需要重新到 freenom 修改下 DNS,点击Services,选择My Domains,找到之前注册的域名,点击右侧的Manage Domain

小雉系统
小雉系统

  • 点击Management Tools选择Nameservers

小雉系统

  • 选择第二个选项自定义,填写上面的 2 个 Nameserver,点击Change Nameservers保存。

小雉系统

  • 回到 Cloudflare,刷新之前的页面,如果跳转到下面所示控制台页面就成功了,如果没有耐心等待一会,解析需要时间。

小雉系统
      到此Cloudflare加速配置完成了,使用wget下载存于小雉系统/fs/project/data/web/tt1的文件(tt1文件可使用 “ dd if=/dev/zero of=/fs/project/data/web/tt1 bs=1024000 count=1000 ”命令制作),出现如下图,说明Cloudflare及域名解析配置成功。
小雉系统

小雉加速效果

      经以上配置后加速配置应已完成,下图可看到已识别为websocket。
小雉系统

  • ssh到作小雉专网客户端的机器执行ifconfig,可看到多了一个tun0的虚拟网口,并分配了ip。

小雉系统

  • 在作小雉专网客户端的小雉机器上分同时使用tun0接口(使用Cloudflare加速)和普通链路(直接访问vultr的公网ip)下载tt1文件,可看到Cloudflare加速明显。

小雉系统

  • 正常使用谷歌搜索“小雉系统”。

小雉系统

猜您可能喜欢

小雉系统安装: http://www.feitianzhi.com/boke/index.php/archives/11/
小雉系统网络配置:http://www.feitianzhi.com/boke/index.php/archives/15/
小雉系统硬盘配置:http://www.feitianzhi.com/boke/index.php/archives/16/
小雉系统远程升级:http://www.feitianzhi.com/boke/index.php/archives/14/
使用Google Authenticator为小雉系统增加动态密码功能:http://www.feitianzhi.com/boke/index.php/archives/17/
小雉配置工具:http://www.feitianzhi.com/boke/index.php/fslib-config.html
资源下载:http://www.feitianzhi.com/boke/index.php/ziyuanxiazai.html

本文原地址: http://www.feitianzhi.com/boke/index.php/archives/38/

转载请注明出处,有疑问或错误请发邮件到xiaozhi@fslib.org 或加QQ群:869598376


介绍

      "相机控制"指云台转动,图像变倍,聚焦,光圈控制,预置位控制,3D定位,ptz设置与获取,快门,增益,白平衡控制,亮度,色度,对比度,清晰度控制;
      "相机控制"因一个客户端的控制会影响其他客户端的图像观看和其他业务(比如大领导正在看,突然被其他用户给转了下相机)为每个客户端分配合适的优先级来解决相机控制中的优先级问题;

优先级控制

      "小雉视频系统"中的优先级控制综合了"视频分析","相机锁定","相机控制共享","相机级联"等应用场景,设计出支持相机控制抢占,控制独占,控制共享的优先级控制;
      "小雉视频系统"的优先级核心是在每个指令中增加一个"priority"参数,如相机转动的接口为:

{
    "command_type":"cameractrl",
    "command_for_uuid":"1",
    "ctrl_type":"turn",
    "priority":"0",
    "speedx":"23040",
    "speedy":"0",
    "time":"1.000000"
}

      "priority"的定义如下:

流媒体控制支持优先级控制[0-65534](数值越大优先级越高)
     0:默认值;
     非0:表示特权连接,需要实现连接保持,连接断开自动回0;
         1-比分析优先级低的定义;
         2-255:保留;
         256-29999:比分析优先级低的定义;
         30000-34999:视频分析专用的优先级;
         35000-64999:比分析优先级高的定义:
         65000-65534:保留;
     注:非0优先级中,
          偶数表示可合并的优先级,即如有多个拥有相同偶数优先级的客户端访问同一相机,这些所有客户端的指令会按照新指令覆盖旧指令方式生效;
          奇数表示独占优先组,即如有多个拥有相同奇数优先级的客户端访问同一相机,只有第一个客户端的指令会生效,其他客户端的指令会被拒绝;
流媒体控制支持多指令合并(按高优先级覆盖低优先级,新指令覆盖旧指令方式合并)
      如某用户A以优先级a向流媒体发起指令让相机左转30S,在用户A与流媒体保持TCP连接期间
           如B用户以比a高的优先级b发起指令,流媒体应立即响应B用户的指令,发送信息通知A有比他高的优先级用户控制了相机(信息不保证一定可达),并更改当前控制优先级为b(注:如B断开连接,流媒体把当前优先级置0,而非还原为a优先级);
           如B用户以与A相同的优先级a发起指令,流媒体应立即响应B用户的指令,当A用户与B用户都断开连接后,流媒体才会把当前优先级置0;
           如B用户以小于A的优先级a发起指令,流媒体应不会响应B用户的指令,当A用户断开连接后,流媒体才会把当前优先级置0;
           如A用户使用新优先级b发起指令,b>a,流媒体立即相应A用户指令,并更改当前控制优先级为b,当A用户断开连接后,流媒体会把当前优先级置0;
           如A用户使用新优先级b发起指令,b<a,
                如当前以a优先级保持连接的用户仅有A,流媒体立即相应A用户指令,并更改当前控制优先级为b,当A用户断开连接后,流媒体会把当前优先级置0;
                否则,不响应A用户的指令;

级联控制

      "级联控制"指小雉与小雉之间级联,最下级的小雉会综合所有上级小雉所有用户的优先级进行权衡,此处特别说明的是此级联协议的其他用途;

  1. 控制通知用途
    用tcp连接向小雉发送如下指令,则其他客户端(用小雉私有的ebml,xml,json接口或gb28181,onvif)发送的控制命令都会转发一份给此连接

    {

    "command_type":"controlInfo",
    "command_for_uuid":"camera_ctrl",
    "type":"1",
    "return_type":"json"

    }

  2. 自定义控制要求
    如推送rtsp流A给小雉转gb28181后分享给其他平台,并且编写一个程序使用tcp发送以下数据给小雉,小雉在收到其他平台对流A的GB28181控制信息时会通过tcp连接转发控制信息,并会判断控制的成败以返回给其他平台

    {

    "command_type":"controlInfo",
    "command_for_uuid":"camera_ctrl",
    "type":"2",
    "return_type":"json"

    }

猜您可能喜欢

小雉系统安装:http://www.feitianzhi.com/boke/index.php/archives/11/
小雉系统网络配置:http://www.feitianzhi.com/boke/index.php/archives/15/
小雉系统硬盘配置:http://www.feitianzhi.com/boke/index.php/archives/16/
小雉系统远程升级:http://www.feitianzhi.com/boke/index.php/archives/14/
使用Google Authenticator为小雉系统增加动态密码功能:http://www.feitianzhi.com/boke/index.php/archives/17/

本文原地址: http://www.feitianzhi.com/boke/index.php/archives/37/

转载请注明出处,有疑问或错误请发邮件到xiaozhi@fslib.org 或加QQ群:869598376


GB28181级联

流媒体服务器-CDN级联
      "GB28181一般级联"实质是多个sip服务器的级联,转发sip指令到视频源中的sip服务器执行,各级中的流媒体负责转发视频流;
      "GB28181 CDN 级联"在各级增加了CDN服务器,对回放视频流进行缓存,在视频回放时,优先判断本级的cdn服务器是否有对应时间视频,有视频则直接使用本级cdn缓存数据,不向下级请求流,cdn无对应时间视频,则向下级请求视频转发并进行缓存;

小雉级联回放

      小雉系统采用"GB28181 CDN 级联"技术方案实现,同时增加了多种转码服务器,以满足不同设备的需要;

  1. RTSP转码
          RTSP转码器与sip服务器和cdn服务器对接,实现gb28181直播流转rtsp直播流,支持使用rtsp进行视频回放,可用于安防客户端开发;
  2. RTMP转码
          RTMP转码器与sip服务器和cdn服务器对接,实现gb28181直播流转rtmp直播流,支持使用rtmp进行视频回放,可用于老系统web视频直播与回放;
  3. HLS转码
          HLS转码器与sip服务器和cdn服务器对接,实现gb28181直播流转hls直播流,支持使用hls进行视频回放,可用于android,iphone,ipad,qq,微信等H5的直播回放;

猜您可能喜欢

小雉系统安装:http://www.feitianzhi.com/boke/index.php/archives/11/
小雉系统网络配置:http://www.feitianzhi.com/boke/index.php/archives/15/
小雉系统硬盘配置:http://www.feitianzhi.com/boke/index.php/archives/16/
小雉系统远程升级:http://www.feitianzhi.com/boke/index.php/archives/14/
使用Google Authenticator为小雉系统增加动态密码功能:http://www.feitianzhi.com/boke/index.php/archives/17/