编程


转载自 http://www.tsingfun.com/html/2015/dev_0804/hello_os_word_my_first_os.html

首先阐述下程序运行的基本原理:计算机CPU只执行二进制指令,我们使用的开发语言开发出的程序最终由相应的编译器编译为二进制指令,二进制中包含程序相关的数据、代码指令(用我们最常见的公式描述就是:程序=数据+算法)。CPU读取相应的指令、数据后开始执行,执行后的结果输出到外部设备,如屏幕、磁盘等。整个过程中,CPU发挥最为核心的作用,与其他设备一起完成程序的执行、输出。

OS本身也是程序,它的运行也是如此,开机后从指定地址处(0x7c00),开始执行指令。先看看本节例子最终运行效果:

编译运行环境:
nasm:Inter x86汇编编译工具,用户将我们的汇编代码编译为二进制。
(下载地址 http://www.tsingfun.com/html/2015/soft_0804/nasm_asm.html)
Bochs:运行os的虚拟机工具,模拟加载我们生成的软盘映像,并运行os。
(下载地址 http://www.tsingfun.com/html/2015/soft_0804/Bochs_Lightweight_VirtualMachine.html)

代码如下:

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
;--------------------------------------------------------------
; 平凡OS(Pf OS) 一个最简单的OS
; Author : tsingfun.com
;--------------------------------------------------------------

; FAT12引导扇区
trueORG 0x7c00 ;引导开始地址,固定的,主板BIOS决定,本条指令只是申明,不占字节(有兴趣的可以单独编译这条指令,然后查看二进制,文件0k)
trueJMP _START ;CPU执行的第一条指令,就是跳转到_START地址处(这里是标签,实际编译后_START是有一个相对地址的)
trueTIMES 3-($-$$) NOP ;NOP:一个机器周期。$:当前地址,$$:首地址。因为以上信息必须占3个字节,所以不足的部分用nop指令填充,
truetruetruetruetruetruetruetrue;具体nop占用几个字节请读者使用二进制查看工具自行验证。

trueDB "PFOSBEST" ; 标识(公司、品牌等)8个字节
trueDW 512 ; 每扇区字节数
trueDB 1 ; 每簇扇区数
trueDW 1 ; Boot内容占几个扇区
trueDB 2 ; 共有多少FAT表
trueDW 224 ; 根目录文件数最大值
trueDW 2880 ; 扇区总数
trueDB 0xf0 ; 介质描述符
trueDW 9 ; 每FAT扇区数
trueDW 18 ; 每磁道扇区数
trueDW 2 ; 磁头数(面数)
trueDD 0 ; 隐藏扇区数
trueDD 2880 ; 若上面“扇区总数”为0,则这个值记录扇区总数
trueDB 0,0,0x29 ; 中断13的驱动器号;未使用;拓展引导标记
trueDD 0xffffffff ; 卷序列号
trueDB "PFOS v1.0.0" ; 卷标(11个字节)
trueDB "FAT12 " ; 文件系统类型(8个字节)
true;---------------------------------------------------------------------
true; 448个字节,引导代码、数据及其他填充字符
trueTIMES 18 DB 0

_START:
trueMOV AX, 0 ;AX:累加寄存器,CPU内置的16位寄存器,最为常用,可以用于存储运行的中间结果,此处清零
trueMOV SI, MSG ;SI:(source index)源变址寄存器,常用来存储待操作的数据的首地址,此处指向数据区的字符串
_LOOP: ;循环指令开始
trueMOV AL, [SI] ;[]取地址中的内容,AL是AX的低8位,所以取8bit,即1字节,字符串的ASCII。
trueADD SI, 1 ;字符串往后移动一个字节
trueCMP AL, 0 ;判断当前取出的ASCII是否是0,
trueJE _END ;JE:Equal则Jmp,等于零表示字符串显示完了,结束。
trueMOV AH, 0x0e ;调用系统10h中断显示ASCII字母,AH,BX指定显示模式及参数(详见:http://www.tsingfun.com/html/2015/dev_0804/570.html)
trueMOV BX, 15
trueINT 0x10
trueJMP _LOOP ;继续下一个字符的显示
_END:
trueJMP $ ;跳到当前的地址,当然就陷入无限循环啦,此处为了让程序停在此处。

;数据区,就是待输出的字符串信息
MSG DB 0x0a, "----------------------------------------------", 0x0d, 0x0a, \
truetruetruetruetruetrue "| Hello, OS World! |", 0x0d, 0x0a, \
truetruetruetruetruetruetruetrue "----------------------------------------------", 0x0d, 0x0a, 0x0

trueTIMES 510-($-$$) DB 0
trueDW 0xaa55 ; 结束标志
true;----------------------------------------------------------------------

; FAT数据区
trueDB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
trueTIMES 4600 DB 0
trueDB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
trueTIMES 1469432 DB 0

其中,主要的步骤代码中都有详尽的注释,如有任何问题,请移步至论坛《深入OS》板块发帖讨论。

编译执行过程:
打开dos窗口,进入源码所在目录,执行命令nasm boot.asm -o pfos.img:

同目录下生成一个”pfos.img”软盘映像文件,接下来只需要把它装载到虚拟机运行即可,当然有条件的话可以实际写入老式的软盘用真机运行,结果是一样的。

同目录下新建一个 pfos.bxrc Bochs配置文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#how much memory the emulated machine will have
megs:4

#filename of ROM images
romimage:file=$BXSHARE\BIOS-bochs-latest,address=Oxf0000
vgaromimage: file=$BXSHARE\VGABIOS-elpin-2.40

#what disk images will be used
floppya:1_44="pfos.IMG",status=inserted

#Choose the boot disk
boot:a

#where do we send log messages?
#log:bochsout.txt

双击“pfos.bxrc”启动Bochs运行即可启动我们自己写的os了。
源码下载:http://www.tsingfun.com/uploadfile/2016/0628/hello os world.zip

接下来解释一下运行原理:

首先,软盘大小是1.44M(这个是固定的),所以我们在程序中指定它为1,474,560 字节,除了程序本身的指令、数据外,不足的部分全部补零。

TIMES 1469432 DB 0 就是此处开始写1469432个字节的0。

软盘采用的是FAT12文件格式,我们现在的常见的文件格式有FAT32、NTFS、EXT3等,FAT12是早期的一种文件格式。文件格式是文件格式化存储的一种算法,比如我们要将一个文件存储到软盘(磁盘)上,有些人可能会想我直接从地址0开始存储,直到结束,那么文件名、文件大小、创建时间等其他信息怎么存?紧接着后面继续存储么?那该给各部分分配多少字节空间?先不说后续查找文件的效率,这种存储方法无章可循会完全失控,是不行的方案。

文件格式化算法就解决了此类问题,而且兼顾文件的高效率查找。基本原理就是给软盘(磁盘)分区:FAT区、目录区、数据区,存储文件时先存储文件基本信息到目录区,然后文件的数据按照一定格式存储到数据区,目录区中有数据区中文件数据的地址。

这里只简单介绍一下FAT12格式,后续篇章会深入解析每个字节代表的含义。

我们来看看我们生成的映像里面到底有什么东西?这时我们需要用到二进制查看工具WinHex,点此下载 http://www.tsingfun.com/html/2015/soft_0804/WinHex.html

以上看到的是二进制静态代码,实际运行中各指令的地址都是动态变化的,下来一起借助Bochs的debug功能来一探究竟。
我们双击“pfos.bxrc”默认是以运行模式启动Bochs,实际上我们应该启动bochsdbg.exe,因此写个简单的批处理脚本启动它吧,如下:

1
2
3
4
5
6
7
8
9
@echo off

SET BXSHARE=C:\Program Files (x86)\Bochs-2.5

if %PROCESSOR_ARCHITECTURE% == x86 (
trueSET BXSHARE=C:\Program Files\Bochs-2.4.6
)

"%BXSHARE%"\bochsdbg -q -f "pfos.bxrc"

双击脚本,启动debug模式,如下:

Bochs常用的debug命令如下:

1
2
3
4
5
6
7
8
9
10
11
12
b 0x...   断点命令,指定地址处调试
info break 显示当前断点信息
c 继续执行
s 步入执行
n 单步执行
info cpu 查看cpu寄存器(可分别执行 r/fp/sreg/creg)
print-stack 打印堆栈
xp /长度 地址 显示地址处内容(xp:物理地址,x:线性地址)
u 起始地址 结束地址 反汇编一段代码
trace on 反汇编执行的每条指令
trace-reg on 每执行一条指令都打印一下cpu信息
exit 退出调试

大家有兴趣的话可以调试下,然后看看每步骤寄存器值的变化。

总结:本篇主要是让大家对操作系统有个整体概念上的认识,揭开os神秘的面纱,从底层调试到运行,每个过程都真真切切展现在大家面前。至于汇编指令、地址寻址暂时不懂的话,也不要紧,后续章节会继续作详细阐述,力求使大家在不断的运行、调试过程中逐渐熟悉并掌握汇编及计算机底层技术。



默认安装的MySQL数据库,无法远程连接。
登录MySQL之后,运行

1
SELECT user,host from mysql.user;

如果只有一条记录,说明是这个原因。
将下面的脚本保存成user.sql,登录MySQL,运行:

1
2
3
use mysql;
source user.sql;
flush privileges;

Notice: 会重置MySQL user表,并且将root用户密码设置为空。

脚本内容: 点这里直接下载

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
SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for `user`
-- ----------------------------
USE mysql;
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`Host` char(60) COLLATE utf8_bin NOT NULL DEFAULT '',
`User` char(16) COLLATE utf8_bin NOT NULL DEFAULT '',
`Password` char(41) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '',
`Select_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`Insert_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`Update_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`Delete_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`Create_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`Drop_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`Reload_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`Shutdown_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`Process_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`File_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`Grant_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`References_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`Index_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`Alter_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`Show_db_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`Super_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`Create_tmp_table_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`Lock_tables_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`Execute_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`Repl_slave_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`Repl_client_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`Create_view_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`Show_view_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`Create_routine_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`Alter_routine_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`Create_user_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`Event_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`Trigger_priv` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N',
`ssl_type` enum('','ANY','X509','SPECIFIED') CHARACTER SET utf8 NOT NULL DEFAULT '',
`ssl_cipher` blob NOT NULL,
`x509_issuer` blob NOT NULL,
`x509_subject` blob NOT NULL,
`max_questions` int(11) unsigned NOT NULL DEFAULT '0',
`max_updates` int(11) unsigned NOT NULL DEFAULT '0',
`max_connections` int(11) unsigned NOT NULL DEFAULT '0',
`max_user_connections` int(11) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`Host`,`User`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='Users and global privileges';

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('localhost', 'root', '', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', '', '', '', '', '0', '0', '0', '0');
INSERT INTO `user` VALUES ('127.0.0.1', 'root', '', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', '', '', '', '', '0', '0', '0', '0');
INSERT INTO `user` VALUES ('%', 'root', '', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', '', '', '', '', '0', '0', '0', '0');
flush privileges;


1.数组遍历方法总结

1
array = (1..10).to_a
1
2
3
4
5
length = array.length 
length.times do t
print "#{array[t]} "
end
puts "n"
1
2
3
4
5
length = array.length-1 
for i in 0..length do
print "#{array[i]} "
end
puts "n"
1
2
3
4
for i in array do 
print "#{i} "
end
puts "n"
1
2
array.each{x print x," "} 
puts "n"
1
2
3
4
5
6
7
length = array.length 
i = 0
while i< length do
print "#{array[i]} "
i = i+1
end
puts "n"
1
2
3
4
5
6
7
length = array.length 
i = 0
until i==length do
print "#{array[i]} "
i += 1
end
puts "n"
1
2
array.each_index do i 
print "#{array[i]} "

2.Ruby连接数据库

  • mysql

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    require 'mysql'  
    begin
    db = Mysql.init
    db.options(Mysql::SET_CHARSET_NAME, 'utf8')
    db = Mysql.real_connect("127.0.0.1", "root", "123456", "test", 3306)
    db.query("SET NAMES utf8")
    db.query("drop table if exists tb_test")
    db.query("create table tb_test (id int,
    text LONGTEXT) ENGINE=MyISAM DEFAULT CHARSET=utf8")
    db.query("insert into tb_test (id, text) values (
    1,'first line'),(2,'second line')")
    printf "%d rows were inserted\n",db.affected_rows
    rslt = db.query("select text from tb_test")
    while row = rslt.fetch_row do
    puts row[0]
    end
    rescue Mysql::Error => e
    puts "Error code: #{e.errno}"
    puts "Error message: #{e.error}"
    puts "Error SQLSTATE: #{e.sqlstate}" if e.respond_to?("sqlstate")
    ensure
    db.close if db
    end
  • redis

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #!/bin/ruby

    require 'redis'

    def writeToFile(file,content)
    fp = File.new(file,"a+")
    if fp
    fp.syswrite(content)
    else
    puts "..."
    end
    end

    def connect(host)
    redis = Redis.new(:host => host,:port => 6379)
    redis.info.keys.each do |key|
    puts "#{key}:\t"+redis.info["#{key}"]
    end
    end

    connect("1.1.1.1")
  • sqlite3

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    require 'sqlite3'

    db = SQLite3::Database.new('test.db')

    db.execute("create table test(
    ID integet not null,
    Username varchar(20) null,
    Password varchar(64) null)")
    db.execute("insert into test(ID.Username,Password)
    values('0','admin','admin')")
    db.execute("select * from test")
    db.execute("update test set password='12345' where id=0")

3.Ruby socket

  • 服务端:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    require 'socket'

    server = TCPServer.open('0.0.0.0', 8080)
    loop do
    Thread.start(server.accept) do |client|
    begin
    while true
    puts "#{client.to_i} online"
    data = client.read()
    throw "empty" if data.empty?
    #puts data.length
    puts data
    end
    rescue Exception => e
    puts "#{client.to_i} offline"
    end
    end
    end
  • 客户端:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    require 'socket'

    hostname = '127.0.0.1'
    port = 8080
    buf = 'test'
    s = TCPSocket.open(hostname, port)
    s.write buf
    sleep(1)
    s.close

4.ruby gem 文档服务

rubygems.org上的gem文档访问起来太慢了,其实gem本身就自带doc的功能
安装gem的时候会默认安装相应gem的doc,如果不想占用空间安装doc,则gem install XXX –no-doc 即可。
使用下列命令可以启动gem自带的文档:

1
gem server --port 1234

然后访问

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

### 5.ruby改变控制台输出内容的颜色
```Ruby
puts "\033[1m前景色\033[0m\n"
puts " \033[30mBlack (30)\033[0m\n"
puts " \033[31mRed (31)\033[0m\n"
puts " \033[32mGreen (32)\033[0m\n"
puts " \033[33mYellow (33)\033[0m\n"
puts " \033[34mBlue (34)\033[0m\n"
puts " \033[35mMagenta (35)\033[0m\n"
puts " \033[36mCyan (36)\033[0m\n"
puts " \033[37mWhite (37)\033[0m\n"
puts ''
puts "\033[1m背景色\033[0m\n"
puts " \033[40m\033[37mBlack (40), White Text\033[0m\n"
puts " \033[41mRed (41)\033[0m\n"
puts " \033[42mGreen (42)\033[0m\n"
puts " \033[43mYellow (43)\033[0m\n"
puts " \033[44mBlue (44)\033[0m\n"
puts " \033[45mMagenta (45)\033[0m\n"
puts " \033[46mCyan (46)\033[0m\n"
puts " \033[47mWhite (47)\033[0m\n"
puts ''
puts "\033[1m其他\033[0m\n"
puts " Reset (0)"
puts " \033[1mBold (1)\033[0m\n"
puts " \033[4mUnderlined (4)\033[0m\n"

colors

6.一些比较特别的包

Ruby json gem
https://rubygems.global.ssl.fastly.net/gems/json-1.8.3.gem
树莓派wiringpi gpio包
http://pi.gadgetoid.com/article/wiringpi-as-a-ruby-gem

7.安装rvm的正确姿势

参考自:http://rvm.io/rvm/install
首先添加gpg公钥:

1
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3

安装稳定版本的rvm

1
curl -sSL https://get.rvm.io | bash -s stable --ruby

8.解决kali2.0中RVM不能编译ruby-2.3.3

  • 错误详情:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    ruby-2.3.3 - #compiling.......................................................................-
    Error running '__rvm_make -j4',
    showing last 15 lines of /usr/local/rvm/log/1488041042_ruby-2.3.3/make.log
    exts.mk:210: recipe for target 'ext/openssl/all' failed
    make[1]: *** [ext/openssl/all] Error 2
    make[1]: *** Waiting for unfinished jobs....
    installing default nkf libraries
    compiling objspace_dump.c
    linking shared-object json/ext/generator.so
    make[2]: Leaving directory '/usr/local/rvm/src/ruby-2.3.3/ext/json/generator'
    linking shared-object objspace.so
    make[2]: Leaving directory '/usr/local/rvm/src/ruby-2.3.3/ext/objspace'
    linking shared-object nkf.so
    make[2]: Leaving directory '/usr/local/rvm/src/ruby-2.3.3/ext/nkf'
    make[1]: Leaving directory '/usr/local/rvm/src/ruby-2.3.3'
    uncommon.mk:203: recipe for target 'build-ext' failed
    make: *** [build-ext] Error 2
    ++ return 2
    There has been an error while running make. Halting the installation.

查看/usr/local/rvm/log/1488041042_ruby-2.3.3/make.log发现是openssl版本过老导致的。

  • 解决:
    第一步:先安装用于rvm的openssl:
    1
    rvm pkg install openssl

第二步:编译安装ruby,指定openssl目录(我的是/usr/local/rvm/usr/)

1
rvm install ruby-2.3.3 --with-openssl-dir=/usr/local/rvm/usr/

9.设置Gems默认源为ruby-china

现在没有淘宝源了,只有ruby-china源

1
gem sources --add https://gems.ruby-china.org/ --remove https://rubygems.org/

设置Bundler默认源为ruby-china:

1
bundle config mirror.https://rubygems.org https://gems.ruby-china.org

这样修改以后,即使Gemfile中指定了Source,也会用国内的源。



Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×