unraid docker的坑

最近疫情被🔒在家,实在没事做,就重新试了下unraid,因为我还是对它很感兴趣的。 去年测试的时候,docker的启动非常的慢,主要原因在于docker.img会越来越大,读取这个的时候,速度会很慢。我不知道之前的版本是否可以设置,现在的版本(6.9.2)可以设置为目录。理论上这样会快一些了。 不过,这次我主要在virtualbox中测试的,主要是验证下掉盘,加盘相关的操作。顺便验证下docker容器的物理ip分配。我还是很喜欢用物理ip的。结果发现分配为Custom:br0 之后,手动设置ip之后,完全无法访问, 无论我如何调整,都不可以,容器访问不了外部,外部也访问不了容器。还有一个问题是docker的这个选项Host access to custom networks, 只要一开,docker服务启动后,我就连webgui都没办法访问了。

网上翻遍了之后,我只好去看看virtualbox的网卡相关选项了,我注意到virtualbox的网卡有个混杂模式,关于混杂模式,可以看下网上的解释, 这个选项默认是关着的。我把这个选项开了之后,所有的都表现正常了。 Host access to custom networks 也可以打开了,这样unraid主机可以和docker容器互相访问,就像两台主机一样。

改天我在真机上试试,看是否还是相同的效果,至少知道这个问题的原因在网卡身上了。

<30天自治操作系统>(day 2) 读后感

这两天无聊,随便找个写代码的练手。就就开始读这本书了。

day.2代码部分使用intel的汇编格式,nasm 的编译器。我在想如何将它尽可能的用C来写。

考虑到我只有linux下的gcc编译器,又不想装太多,所以就在linux下来试试吧。

编译的脚本流程:

gcc -fno-pie -m16 --static -c day2.c -o day2.o
ld -f elf32 -m elf_i386 -fno-pie --script=boot.ld -shared --map --static day2.o -o day2

这里必须指定编译为16位的,这样才能在实模式下正确运行,如果是32位模式的话,调用call会出现call飞到其他位置,为了这个内容,我调试了一下午。

正常编译的可执行文件包含了很多其他用不到的内容,比如PE格式之类,对于loader而言,这些都是无法执行的,因此需要定制编译的脚本, 下面是脚本的内容:


ENTRY(_entry)
OUTPUT_FORMAT(binary)

SECTIONS
{
. = 0x7c00;

. = ALIGN(4);
.text :
{
*(.text)
*(.text.*)
}
. = ALIGN(4);
.data :
{
*(.data)
*(.data.*)

}

. = ALIGN(4);
.rodata :
{
*(.rodata)
*(.rodata.*)
}

. = ALIGN(4);
.sdata :
{
*(.sdata)
*(.sdata.*)
}

. = ALIGN(4);
.scommon :
{
*(.scommon)
*(.scommon.*)

}

. = .;

. = ALIGN(16);
__got_start = .;
.got : { *(.got) }
__got_end = .;

. = ALIGN(4);
.sbss :
{
*(.sbss)
*(.sbss.*)

}
.bss :
{
*(.bss)
*(.bss.*)

}
. = ALIGN(16);
.rel.dyn : { *(.rel.dyn) }
.dynamic :{ *(.dynamic)}

.interp : { *(.interp) }
.dynstr : { *(.dynstr) KEEP(*(.dynstr))}
.dynsym : { *(.dynsym) KEEP(*(.dynsym))}
.bootflag1 0x1FE+0x7C00 : {*(.bootflag1)}

}

其实大部分都没啥用,主要是定义入口函数为_entry, 输出格式为binary而非elf格式,代码的起始位置为0x7c00.

默认的nasm程序和gcc编译器生成的二进制文件,在制作启动盘的时候,都缺少启动磁盘和启动分区的标志。使用脚本的话,就方便很多了,可以直接将启动的标记写入到指定位置去。 也就是上面的bootflag1 和 bootflag2。因为day2中的数据非常的少,所以不考虑加载更多内容,因此将data的数据放到标记之前,这样启动的时候读取一个分区的数据就将要用到的 data放入到第一个分区中了,也因此, 这部分的代码+数据不能超过512-4个字节

下面是helloworld bootloader的c代码版本:


void _init();
void _fin();

__attribute__ ((naked)) void _entry()
{
__asm("jmp *%0":
:"r"(_init));
}

char msg[] = "\n\nhello, world\n";

void show_char(char c)
{
__asm("mov %0, %%al":
:"r"(c));
__asm("mov $0x0e, %ah");
__asm("mov $15, %bx");
__asm("int $0x10");
}

void _init()
{
int i =0;
while(i&lt;sizeof(msg)) {
show_char(msg[i]);
i++;
}
_fin();
}

void _fin()
{
__asm("hlt");
}
unsigned char __attribute__((section (".bootflag1"))) bootflag1[] __attribute__ ((aligned (1))) = {0x55,0xaa};//0xfff0aa55;

系统起始部分其实啥也没有,所以不可避免的还是要使用汇编相关的代码,只是使用C来组织下而已, 注意的是,这里的入口函数是_entry, 而非一般c语言的main函数,这在编译的自定义脚本中声明了。函数的naked属性声明,在没有使用变量的情况下,不会生成栈相关的处理。

1. 两数之和 (leetcode)

今日有朋友问我一些python入门的免费教程。不知怎的就想到了刷题,就想看看是怎么个流程, 然后就搜索了下,网上推荐的网站:https://leetcode-cn.com/

为了看这个检查的流程,就找了个最简单的,语言使用最近经常使用的javascript.刚开始的时候, 无论如何执行,执行结果都没有。后来发现是浏览器的广告拦截插件的原因。于是关闭后刷新总算 执行正常了。

因为两数之合很简单,就随便写了个常规的流程,提交后发现可以看到执行效率的排名,内存占用的排名。 比如下面的常规的流程:

  //方法1:
//执行用时: 100 ms
//内存消耗: 41.3 MB
    /**
     * @param {number[]} nums
     * @param {number} target
     * @return {number[]}
     */
    var twoSum = function(nums, target) {
    for(let i =0; i < nums.length; i++) {
            for(let j=i+1; j < nums.length; j++) {
                if(nums[i]+nums[j] === target) {
                    return [i,j];
                }
            }
        }
    return [-1,-1];
    };

我的提交显示 执行效率超过46%(100ms),内存超过44%(41.3 MB)

这激发了我的兴趣,就想着如何优化或者负优化吧。于是就有了下面更多的提交:

//方法2:
//执行用时: 520 ms
//内存消耗: 42.6 MB
  /**
   * @param {number[]} nums
   * @param {number} target
   * @return {number[]}
   */
  var twoSum = function(nums, target) {
  for(let i =0; i < nums.length; i++) {
      let test = target - nums[i];
      let index = nums.findIndex((e, j)=> j != i && e == test );
      if(index >= 0) {
          return [i, index];
      }
  }
  return [-1,-1];
  };

上面这个方法似乎效率超越了2%, 我不知道哪个比这个还要慢了。

  //方法3:
//执行用时: 72 ms
//内存消耗: 41.7 MB
    /**
     * @param {number[]} nums
     * @param {number} target
     * @return {number[]}
     */
    var twoSum = function(nums, target) {
        let obj = {};
    for(let i =0; i < nums.length; i++) {
        if(obj[nums[i]] != undefined) {
            return [obj[nums[i]], i];
        } else {
            obj[target-nums[i]] = i;
        }
    }
    return [-1,-1];
    };

上面的方法使用了json的格式,利用判断key是否存在,但json格式的key只能是字符串,利用题目中的 索引都是数字的特性,简单优化了下类型:

    //方法4:
  //执行用时: 64 ms
//内存消耗: 41.6 MB
  /**
   * @param {number[]} nums
   * @param {number} target
   * @return {number[]}
   */
  var twoSum = function(nums, target) {
  let obj = [];
  for(let i =0; i < nums.length; i++) {
      if(obj[nums[i]] != undefined) {
          return [obj[nums[i]], i];
      } else {
          obj[target-nums[i]] = i;
      }
  }
  return [-1,-1];
  };

数组的方式比object的方式快一些

  //方法5:
//执行用时: 60 ms
//内存消耗: 41.5 MB
    /**
     * @param {number[]} nums
     * @param {number} target
     * @return {number[]}
     */
    var twoSum = function(nums, target) {
    let obj = [];
    for(let i =0, j=nums.length-1; i <= j; i++, j--) {
        if(obj[nums[i]] != undefined) {
            return [i, obj[nums[i]]];
        } else {
            obj[target-nums[i]] = i;
            if(obj[nums[j]] != undefined) return [obj[nums[j]],j];
            else
            obj[target-nums[j]] = j;
        }
    }
    return [-1,-1];
    };

上面的方式还是使用数组的方式,只是将循环减半了。因此复杂度也少了些了。

    //方法6
 //执行用时: 60 ms
//内存消耗: 41.4 MB 
  /**
   * @param {number[]} nums
   * @param {number} target
   * @return {number[]}
   */
  var twoSum = function(nums, target) {
  let obj = new Map();
  for(let i =0, j=nums.length-1; i <= j; i++, j--) {
      if(obj.has(nums[i])) {
          return [i, obj.get(nums[i])];
      } else {
          obj.set(target-nums[i], i);
          if(obj.has(nums[j])) return [obj.get(nums[j]),j];
          else
          obj.set(target-nums[j], j);
      }
  }
  };

使用map,不影响效率的同时,降低了内存消耗

  //方法7
//执行用时: 64 ms
//内存消耗: 41.4 MB
    /**
     * @param {number[]} nums
     * @param {number} target
     * @return {number[]}
     */
    var twoSum = function(nums, target) {
    let obj = new Map();
    let i = 0;
    let j = nums.length-1; 
    while(i<=j) {
        let left = nums.shift();
        let right = nums.pop();
        if(obj.has(left)) {
            return [i, obj.get(left)];
        }
        obj.set(target-left, i);
        if(obj.has(right)) {
            return [obj.get(right), j];
        }
        obj.set(target-right, j);
        ++i;
        --j;
    }
    };

方法7与方法6基本一样,只是将for循环使用while循环来代替了,并且,旨在为了降低内存使用的shift, pop 操作,从测试结果而言,并没有效果。这应该和javascript的内存分配机制有关系,毕竟变量nums还在不断的使用中。

  //方法8
//执行用时: 64 ms
//内存消耗: 41.6 MB
    /**
     * @param {number[]} nums
     * @param {number} target
     * @return {number[]}
     */
    var twoSum = function(nums, target) {
    let obj = [];
    let i = 0;
    let j = nums.length-1; 
    while(i<=j) {
        let left = nums.shift();
        let right = nums.pop();
        if(obj[left] != undefined) {
            return [i, obj[left]];
        }
        obj[target-left] = i;
        if(obj[right] != undefined) {
            return [obj[right], j];
        }
        obj[target-right] = j;
        ++i;
        --j;
    }
    };

方法8与方法7一样,只是将map修改成了数组。执行效率一样,但内存占用多了些。

    //方法9:
//执行用时: 72 ms
//内存消耗: 41.5 MB
  /**
   * @param {number[]} nums
   * @param {number} target
   * @return {number[]}
   */
  var twoSum = function(nums, target) {
  let obj = new Map();
  let i = 0;
  let j = nums.length-1; 
  while(i<=j) {
      let left = nums[i];
      let right = nums[j];
      if(obj.has(left)) {
          return [i, obj.get(left)];
      }
      obj.set(target-left, i);
      if(obj.has(right)) {
          return [obj.get(right), j];
      }
      obj.set(target-right, j);
      ++i;
      --j;
  }
  };

方法9与方法7一样,只是将数据的提取使用数组的方式,而非shift,pop的方式, 即使这一简单改变,也导致 测试执行效率降低了些。

  //方法10
//执行用时: 52 ms
//内存消耗: 41.7 MB
    /**
     * @param {number[]} nums
     * @param {number} target
     * @return {number[]}
     */
    var twoSum = function(nums, target) {
    let obj = {};
    let i = 0;
    let j = nums.length-1; 
    while(i<=j) {
        let left = nums.shift();
        let right = nums.pop();
        if(obj[left] != undefined) {
            return [i, obj[left]];
        }
        obj[target-left]= i;
        if(obj[right] != undefined) {
            return [obj[right], j];
        }
        obj[target-right]= j;
        ++i;
        --j;
    }
    };

方式10和方法9一样,只是使用了object和数组的shift,pop的方法,

除了负优化的方法2之外,总体而言,减少循环可以优化效率。使用object和map的方式会比较节约些内存。 受到数据样本情况,上面的测试结果也不是非常准确的,比如,如果数据样本大都集中在前面几个就有 答案的话,方法1可能比较快的,反之,方法1则是比较慢的;如果数据样本的答案集中在两侧的话,循环减半 的方式则相对比较快,(对于前一种情况,循环减半效果也比较好)。即使是同一个算法,每次提交 执行用时也不相同,不如方法10,我这边在测试的时候,最快在52ms, 而排在前面的小于40ms的,我将 算法提交上去后,却在64ms,方法7有时候也会执行用时也会在56ms左右。

其实无论何种方式,何种优化,关键是从何种角度去思考问题,解决问题。

脚本获取证书过期时间

这几天想用node.js来检测网站的https证书有效期还剩余多少天,以便及时续期。npm上有个ssl-checker 库可用,用了后发现这个对于非标准端口不太适用,在我这边还有一个问题就是会直接报错,连接重置。于是 就使用shell写了个获取任意网站ssl证书过期时间的脚本:

#!/bin/sh
EXPIRE_DATE=`curl --cert-status -v $1 2>&1 | awk 'BEGIN { cert=0 } /^\* Server certificate:/ { cert=1 } /^\*/ { if (cert) print }' | grep "expire date:" | cut -d":" -f 2-`
date +%Y-%m-%d --date "$EXPIRE_DATE"

用法也很简单, 直接将域名作为参数传入即可,结果格式化为 年-月-日的格式,这样在node.js这边可以 进行后续的时间处理