getopt

1
2
#include<unistd.h>
int getopt(int argc,char * const argv[ ],const char * optstring);

它是用来分析命令行参数的函数。参数optstring 则代表欲处理的选项字符串。

  • 字母后没有符号: 不带值的参数,定义即参数本身
  • 字母后带一个冒号: 必须带值,值被全域变量optarg指向。
  • 字母后带两个冒号: 可选值的参数,参数可加可不加。

getopt()每次调用会逐次返回命令行传入的参数,当没有参数的最后一次调用会返回-1。

参数返回的是字符串的,有些需要转化为整形,可以用下面函数.

1
2
3
4
5
#include<stdlib.h>
定义函数
unsigned long strtoul(const char *nptr,char **endptr,int base);
将字符串转化为长整形。
base 代表进制。base是0的话会根据字符串自动选择类型。若参数endptr不为NULL,则会将遇到不合条件而终止的nptr中的字符指针由endptr返回

交换处理

交换处理: 内存容量不足以容纳整个程序的代码和数据。所以需要定期进行交换处理。
sched(); //用来定期寻找交换处理对象的进程,也被称为swapper
sched() 从proc[]中寻找满足以下条件的进程作为交换处理的对象。

  • 位于交换空间最长
  • 位于可执行状态

如果换入时内存不足,需要找满足一下条件的进程换出

  • 位于内存中
  • 处于SWAIT or SSTOP状态
    如果没有满足,则放宽条件
  • 滞留内存时间最长
  • 处于SRUN 或 SSLEEP状态
    (但是要满足以下条件。)
  • 换入对象位于交换空间的时间大于或等于3秒
  • 换入对象位于内存的时间大于或等于2秒

mess:
spl6() //将处理器优先级设置为6,防止发生中断。

代码段,用text结构体表示

1
2
3
4
5
6
7
8
struct text {
int x_daddr; //交换磁盘的地址
int x_caddr; //读入内存时物理内存地址
int x_size; //代码段的长度
int *x_iptr; //指向inode[]中对应程序执行文件的元素
char x_count; //所有进程为对象的参照计数器
char x_ccount; //以内存中的进程为对象的参照计数器
} text[NTEXT];

代码段在内存中只读,如果某个程序有多个进程,共享代码段。当内存中没有进程用代码段了,内存自动释放, 交换空间和内存没有进程使用代码段,代码段在交换空间中释放。

内存和交换空间

内核用map管理内存和交换空间

1
2
3
4
struct map {
char *m_size;
char *m_addr;
};

int coremap[CMAPSIZ]; //以64字节为单位管理
int swapmap[SWAPSIZ]; //以512字节为单位管理

获取未使用的区域采用了First Fit
数组尾部,有一个长度为0的元素,充当哨兵

1
2
3
4
5
6
7
8
//分配地址
malloc(mp, size);
mp: coremap[] or swapmap[]
size: 大小
//释放地址
mfree(mp, size, aa);
aa: addr, 释放的起始地址。

var_list

C语言中如果参数个数不定,可以用省略号传参

1
var func(parm_list, ...)

printf 也是用了这种传参方式。
函数传参的时候用到了栈的数据结构,参数从右往左一个一个入栈。所以理论上,知道一个参数的类型和其中一个的地址,就可以知道所有参数。

1
2
3
4
5
6
7
8
//var_list 是char*类型,定义一个var_list
var_list args;
//var_list 初始化
var_start(args, parm_list);
//调用var_arg ,返回type类型变量
var_arg(args, type);
//获取所有参数之后,以防发生危险,需要把args指针置为NULL
var_end(args);

可以看一个简单printf的例子

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
#include <stdio.h>
#include <stdarg.h>
void myprintf(const char *format, ...)
{
va_list ap;
char c;
va_start(ap, format);
while (c = *format++) {
switch(c) {
case 'c': {
/* char is promoted to int when passed through '...' */
char ch = va_arg(ap, int);
putchar(ch);
break;
}
case 's': {
char *p = va_arg(ap, char *);
fputs(p, stdout);
break;
}
default:
putchar(c);
}
}
va_end(ap);
}
int main(void)
{
myprintf("c\ts\n", '1', "hello");
return 0;
}

C++迭代器的一些记录

一段时间没写C++,把C++迭代器和java的引用搞混了。

1
2
3
4
5
6
vector<int> vec;
for(int i = 1; i < 3; ++i) vec.push_back(i);
vector<int>::iterator it1,it2;
it1 = vec.begin();
it2 = it1;
it1 ++;

上述的代码最后it1 加之后it2还是vec.begin()
it2和it1是独立的,这里不和java的引用一样。

git usage

使用的时候要把秘钥放到github网站上

1
ssh-keygen -t rsa -C "xxx@gmail.com"

克隆一个仓库

1
git clone git://xxx

添加文件

1
2
3
git add <filename>
#把所有新文件添加进去
git add .

本地提交改动

1
git commit -m "some information about change and commit"

推送到master

1
git push origin master

把仓库连接到远程服务器

1
git remote add origin <server>

查看git状态

1
git status

版本回退

1
2
3
4
#查看状态
git log
#回退
git reset --hard 3628164

branch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#创建一个分支"feature"并切换过去
git checkout -b feature
#切换到主分支
git checkout master
#查看分支
git branch
#删除新建分支
git branch -d feature
#强制删除分支[分支有改动]
git branch -D feature
#提交
git push origin <branch>
#丢弃文件修改
git checkout -- <filename>

update and merge

1
2
3
4
5
6
7
8
#更新仓库
git pull
#合并
git merge <branch>
#conflicts改掉之后合并成功,可以加入仓库
git add <filename>
#预览差异
git diff <source_branch> <target_branch>

tag

1
2
3
4
5
6
7
8
#[1b2e1d63ff]是提交的ID的前10位
git tag 1.0.0 1b2e1d63ff
#获取提交id
git log
#删除变迁
git tag -d [name of tag]
#提交标签
git push origin --tags

autotools

autotools是一系列工具,可以用来制作makefile

其中包括了aclocal,autoscan,autoconf,autoheader,automake

流程

  • 1.运行autoscan.
    它会搜索源文件以寻找一般移植性的问题,并创建一个文件”configure.scan”
    configure.scan为configure.in的原形文件。

  • 2.修改configure.scan为configure.in,按照描述填上配置
    下面是单hello文件的配置。与原始文件相比加上了AM_INIT_AUTOMAKE()和AC_CONFIG_FILES()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.69])
AC_INIT(hello, 1.0)
AM_INIT_AUTOMAKE(hello,1.0)
AC_CONFIG_SRCDIR([hello.c])
AC_CONFIG_HEADERS([config.h])
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
AC_CONFIG_FILES([makefile])
AC_OUTPUT
  • 3.执行aclocal,生成”aclocal.m4”,主要功能是处理本地宏定义

  • 4.执行autoconf,生成configure可执行文件

  • 5.执行autoheader,负责生成config.h.in,复制用户附加的符号定义

  • 6.编写makefile.am

    1
    2
    3
    4
    AUTOMAKE_OPTIONS=foreign
    bin_PROGRAMS=hello
    hello_SOURCES=hello.c
    #如果有头文件,则hello_SOURCES=hello.c hello.h
  • 7.执行automake,把makefile.am转换成makefile.in.
    automake -a(或automake –adding-missing),可以让automake自动添加一些必须的脚本文件。

  • 8.执行./configure,生成makefile

  • 9.之后就直接可以make,make install,make clean之类的了。如果想要打包为压缩文档发布,直接运行make dist生成tar.gz压缩文件。

System Protection

  《30天自制操作系统》差不多都过了一遍。感觉操作系统和一般程序的最大的区别是可以随意操作和控制内存。而作为操作系统,最重要的是兼容性和稳定性。稳定的最基本的条件就是安全。所以操作系统作为最灵活的“程序”必须在安全方面做好工作。《30天自制操作系统》这本书上也讲了很多关于保护操作系统的知识,让我记忆深刻。下面就来总结一番。
  保护操作系统是一项工程量十分巨大的活,有时候一个死角就能够然操作系统运行错误。所以要想保护操作系统,必须对操作系统的运行的每个流程都十分清楚,这样才能够找到操作系统的弱点,并做好防护措施。下面就来回顾一下操作系统的运行流程以及涉及的相关代码。

  • 1.BIOS找到启动区,执行启动区的代码。这段代码所在文件是ipl10.nas,是用汇编写的,引导区只有512字节,不足以存放操作系统,这段代码的主要作用就是设定分区格式,把操作系统读入内存,然后跳入到操作系统核心代码。说简单一点就是引导操作系统。

  • 2.启动区之后直接跳入HariMari函数开始执行。相关代码在bootpack.c。这部分代码主要是初始化键盘、鼠标等硬件设备设备,初始化GDT、IDT、PIC,开放中断,初始化内存、初始化任务并运行,然后进入到了一个循环,在这个循环里面接收键盘和鼠标的数据,并控制数据的发送。

  • 3.操作系统会启动一个终端。相关代码在console.c中。终端是当前的操作系统最重要的交互工具。主要功能是执行相关命令和外部程序。如果当前选中终端,输入字符在HariMari中被接收之后,会发送到终端做相关的显示,如果发送回车键之后,终端会判断是命令还是程序,并执行。

  • 4.如果执行的是外部程序,则操作系统则会跳到程序的主函数执行其代码。为了程序的方便,程序可以调用操作系统所提供的公开API。

  分析上面的内容。前三个部分是操作系统所提供的服务。这些部分是不能够被外部程序所更改的,只需要给操作系统关键部分文件加上一定的修改权限就可以了。比如在win7操作系统上,不能够删除系统关键文件,这应该是一个道理。这部分代码是设计和编写操作系统的人编写的,只要这部分代码稳定,能够保证运行正常功能不会出错就可以了。
  第四个部分的代码是可以由其它程序员编写的,所以这部分代码可能会存在bug,或者是恶意代码,如果没有做好一定的防护,操作系统可能会崩溃。

  下面再来看一下外部文件被操作系统调用的流程。
  用户通过键盘向电脑输入相关外部文件名字,然后回车。HariMari函数接收到键盘数据,发送给终端,终端判断输入是否是命令,如果不是命令,则查找是否存在该文件,如果存在文件则通过file_loadfile载入内存,注册gdt,然后跳转执行。
  现在假设操作系统没有任何防备,看看应用程序能够做些什么来破坏操作系统。

  • 1.语言直接修改内存数值。
      这是C语言的特性,在操作系统没限制的条件下,可以通过指针修改任意内存的值。
    这个本来应该是操作系统的权力。在实现操作系统的时候可以通过在一些内存中写入来保存数据,把数据当全局变量来用。这个在之前的内容中也用到过。但是如果是外部的软件,当然不可能给予它这么大的权限,所以需要把所有软件中的非法内存操作给禁止。
      解决方法:专门给应用程序分配数据段,限制应用程序修改的数据范围。如果应用程序需要调用系统函数,则在执行系统函数之前先把段地址切换到操作系统所在段再执行。执行好之后回到应用程序段,切换段以及执行api的操作由操作系统提供,只需要再对应用程序对api给的参数进行合法性校检就可以了。

  • 2.用汇编写第三方程序,忽略操作系统指定的数据段。
      这个是上面的解决方法的漏洞。c语言编译之后会用系统提供的段,但是汇编更强大,可以自己选定段进行数据修改。
      解决方法:让应用程序无法访问操作系统的段。在软件的数据段和代码段上的访问权限加上0x60,这样可以将段设置为应用程序用。该段中的代码如果要访问操作系统所在段代码是非法的。这个0x60是x86架构中提供的,在现在的操作系统中只需要设定好久行了,不需要编写额外的代码。处于好奇,我去查了一下arm,里面有一个MMU,也可以设定访问权限,功能与x86类似。看来这部分内容在操作系统中至关重要,在处理器架构上面就已经把这个问题考虑清楚了。
      申明段为应用程序段之后还有其它的一系列好处,比如应用程序不能够直接执行IN和OUT指令,这样可以防止应用程序更改定时器的周期,让定时器运行缓慢。除了这个,在应用程序中,也不能够执行CLI、STI和HLT之类的指令,这些指令在操作系统看来都是危险的,所以0x60的设置极大地提高了系统的安全性。

    1
    2
    3
    4
    //代码段
    set_segmdesc(gdt + 1003, finfo->size - 1, (int) p, AR_CODE32_ER + 0x60);
    //数据段
    set_segmdesc(gdt + 1004, segsiz - 1, (int) q, AR_DATA32_RW + 0x60);
  • 3.破坏应用程序所在段中的数据。
      应用程序不能够对操作系统的段进行修改,所以篡改操作系统是没用的了。但是可以篡改应用程序所在段的内容,让其它应用程序无法正常运行。对于CPU来说,应用程序访问应用程序所在段应该是理所当然的。但是这的确西药改进,如果操作系统中的应用程序很容易受到其它应用程序的干扰,而操作系统对此也不能够做些什么,那这个操作系统也是失败的操作系统。
      解决方法:由gdt可以得到一些启示,可以动态修改gdt设置,把正在运行的程序gdt设置为应用程序段,把不在运行的程序设置为操作系统段。这样的话如果运行的应用程序想要破坏其它应用程序,那也只能够破坏和它同时运行的应用程序,大大降低了危害。但是这样有很多缺点,比如说一个程序就要用好几个gdt,而且每次更改起来也比较麻烦。
    其实对于这个问题,CPU已经准备好了解决的方案。就是LDT。我们知道,GDT是全局段描述符表,而LDT则是局部段描述符表。每个LDT都是互相独立的,不能够访问各自的资源。所以只需要把应用程序设置在LDT中,这样其它应用程序就不能够访问该应用程序的段,保证了数据的安全。设定LDT也很简单,只需要在任务的tss中给成员变量ldtr赋相应值就可以了。

    1
    2
    //设定ldtr
    taskctl->tasks0[i].tss.ldtr = (TASK_GDT0 + MAX_TASKS + i) * 8;

  最后来谈一下对一些破坏性程序的处理。
  作为一个可用的操作系统,如果应用程序具有破坏性,那么运行之后就应该做到适当的提示,以及能够强制关闭应用程序,不要让它继续占用内存和CPU。这样操作系统才能够更加稳定。
  对于一些恶意代码,比如在应用程序中修改操作系统段的数据,或者在应用程序中执行STI等操作,由于我之前设定过0x60的段权限,应用程序会自动产生0x0d中断(异常中断),在这个中断中,我可以写一些代码来关闭应用程序做好善后工作。

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
//0x0d中断之后调用asm_inhandler0d函数
set_gatedesc(idt + 0x0d, (int) asm_inthandler0d, 2 * 8, AR_INTGATE32);
;中断调用汇编函数
_asm_inthandler0d:
STI
PUSH ES
PUSH DS
PUSHAD
MOV EAX,ESP
PUSH EAX
MOV AX,SS
MOV DS,AX
MOV ES,AX
;调用inthandler0d显示异常
CALL _inthandler0d
CMP EAX,0
JNE _asm_end_app
POP EAX
POPAD
POP DS
POP ES
; INT 0x0d 中需要这句
ADD ESP,4
;返回操作系统
IRETD
//_asm_inthandler0d调用
int *inthandler0d(int *esp)
{
struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);
struct TASK *task = task_now();
char s[30];
//在终端中显示异常
cons_putstr0(cons, "\nINT 0D :\n General Protected Exception.\n");
sprintf(s, "EIP = %08X\n", esp[11]);
cons_putstr0(cons, s);
//强制结束程序
return &(task->tss.esp0);
}

  现在再来回顾一下操作系统,只要其内部稳定,基本上就很难被外部程序给干扰。因为操作系统和程序已经隔离。应用程序不能够破坏操作系统数据,如果要高权限操作,应用程序可以调用操作系统公开的api来完成。同时应用程序和应用程序间也独立,互不干扰。这样的操作系统才能经受住考验。

redis notes1 sds

redis使用一个sds来表示一个字符串

1
2
3
4
5
struct sdshdr {
int len;
int free;
char buf[];
}

###不用C字符串的原因

  • C字符串不能满足要求,是因为C字符串不能够记录自身的长度,获取其长度的复杂度是O(n)
    sdshdr用空间换时间,直接用len标记了字符串的长度
  • C字符串不记录自身长度,容易造成缓冲区溢出,strcat会假定第一个参数有足够多的内存,sds则会自动检查并扩展字符串大小
  • C字符串因为没有记录长度,所以不能保存’\0’,而sds是二进制安全的

不停地重分配内存性能会损失很多,redis的sds实现了空间预分配和惰性空间释放两种优化策略

  • 预分配

    • sds如果修改之后小于1mb,则分配和len属性同样大小的未使用空间
    • 如果修改之后,大于等于1mb,程序会分配1mb未使用空间
  • 惰性空间释放

    • sdstrim之后不释放空间
1
2
sds - sizeof(struct sdshdr) 就是sdshdr地址
realloc(sh, sizeof(struct sdshdr)+sh->len+1);//释放free的内存

最近的一些记录15-5-17

最近学了一下Flask,感觉这类东西使用起来还是差不多的。
以前感觉python应用部署起来比较麻烦,其实部署过之后还是蛮简单的。

virtualenv真是神器,可以创建独立的python环境。
其实在没接触到这个之前一直是直接装在原始环境中的,pip还老是遇到permission问题,要sudo.

最近把windows上的virtualbox更新了一下,原来用的是绿色版,但是那个网络的服务老是报异常,需要卸载再安装一遍。比较麻烦所以索性换掉了。virtualbox下mount的地方使用virtualenv会遇到permission error的问题,应该是virtualbox的bug.

virtualbox里面的debian感觉x-windows不怎么需要,所以直接取消启动了,为了粘贴方便,迫不得已装了一个openbox.

1
2
3
4
5
//配置linux启动项,把gdm给删掉
sudo apt-get install rcconf
sudo rcconf
//配置startx默认启动openbox
update-alternatives --config x-session-manager

pip在装一些软件的时候会少一些东西,一般装上python-dev和mysql-dev(好像不叫这个名字)就可以了

还有些pip下载慢,需要代理,可以用proxychains4,不能够直接apt-get装,装了好像有问题,可以直接clone https://github.com/rofl0r/proxychains-ng 编译安装。

赞一下aptitude很好用啊。可以给你几个选项来解决依赖问题。十分方便。

要在windows上用virtualbox实在是有点麻烦,只是现在有个项目还要用到myeclipse和vc++,不然就可以完全转到linux下工作了。

linux查看端口占用信息

1
netstat -ntpl //查看端口占用

mosh这个软件很好用,没有代理的情况下访问vps速度飞快。
但是windows上就只有Cygwin和chrome上有。
Cygwin不太想装。chrome上面有时候会出现载入一个module时卡住的问题,google了一下好像是bug,暂时无解。希望能改善。
使用mosh一定要两边的字符集一样,不然会报错误。

1
2
3
4
5
//设置debian字符集为utf-8
debian:~# apt-get install locales
debian:~# dpkg-reconfigure locales
debian:~# locale
export LC_ALL=en_US.utf8