learner66的探索空间

启航,探索未知之地


  • 首页

  • 标签

  • 分类

  • 归档

  • 搜索

git学习(二)

发表于 2018-05-22 | 更新于: 2018-06-01 | 分类于 git
字数统计: 906 | 阅读时长 ≈ 4

工作区和暂存区

git设计中有一些概念,了解这些概念对于深入学习git有很大的帮助。其中就有工作区
和暂存区。

工作区

工作区就是创建的工作目录,这个目录下的所有文件都需要在git的管理之下。文件的创
建,修改,删除都会被git记录。

暂存区

另外一个就是.git。这个称为版本库。版本库里存了很多东西,其中一个就是暂存区,所
有的修改只有在提交到暂存区,才能被commit。

1
2
3
4
5
6
7
8
9
10
位于分支 master
要提交的变更:
(使用 "git reset HEAD <文件>..." 以取消暂存)

修改: readme.txt

未跟踪的文件:
(使用 "git add <文件>..." 以包含要提交的内容)

LICENSE

git add 是将文件修改添加到暂存区
git commit 是将暂存区的所有内存提交到当前分支上

管理修改

上一节提到暂存区和工作区,git管理的是修改,而不是文件文件。为什么这样说呢,实验
一下。
原readme.txt中的内容:

1
2
git is a version control tool
git is so nice

第一次修改后的内容:

1
2
3
git is a version control tool
git is so nice
this is first test

使用命令:

1
2
3
4
5
6
7
8
9
10
11
12
xxx:~/workplace/demo$ git add readme.txt
xxx:~/workplace/demo$ sudo git commit -m "first"
[master e76954f] first
1 file changed, 3 insertions(+)
create mode 100644 readme.txt
xxx:~/workplace/demo$ git status
位于分支 master
无文件要提交,干净的工作区
xxx:~/workplace/demo$ cat readme.txt
git is a version control tool
git is so nice
this is first test

第二次修改的内容:

1
2
3
4
git is a version control tool
git is so nice
this is first test
this is second test

使用一下命令:

1
2
3
4
5
6
7
8
9
   xxx:~/workplace/demo$ sudo git commit -m "test"
位于分支 master
尚未暂存以备提交的变更:
(使用 "git add <文件>..." 更新要提交的内容)
(使用 "git checkout -- <文件>..." 丢弃工作区的改动)

修改: readme.txt

修改尚未加入提交(使用 "git add" 和/或 "git commit -a")

由上面执行结果可以,第二次的修改并未被提交。
这也说明,只有将修改添加到暂存区才能进行后一步的提交。也能看出来git的确
是对修改进行管理的,只有当修改添加到暂存区,才能把修改提交到分支上。文件已经
被修改了,但是并不会被提交。

撤销修改

上一次讲过git的时光魔法,利用git reset可以让文件回到某一状态。但是针对的
是提交后的。每次reset的标记也是commit id。
还有一种状况需要考虑,就是在修改没有commit之前,如何让文件恢复到某一时刻的
状态。
git checkout就是专门负责则这个功能的。

1
2
3
4
5
6
7
8
9
10
11
xxx:~/workplace/demo$ cat readme.txt
git is a version control tool
git is so nice
this is first test
this is second test
this is modify.
xxx:~/workplace/demo$ git checkout readme.txt
xxx:~/workplace/demo$ cat readme.txt
git is a version control tool
git is so nice
this is first test

可以从上面的命令清楚的看到,git checkout命令就相当于撤回键一样,可以让修
该回到之前的一个状态。
还有一个需要考虑的是,如果你的修改已经被提交,那么需要使用reset来回到上一个
状态。

删除修改

1
2
3
4
5
xxx:~/workplace/demo$ rm readme.txt
xxx:~/workplace/demo$ sudo git commit -m "delete"
[master 94de890] delete
1 file changed, 1 insertion(+)
xxx:~/workplace/demo$

git学习(一)

发表于 2018-05-22 | 更新于: 2018-06-01 | 分类于 git
字数统计: 1,337 | 阅读时长 ≈ 6

git是什么

git是一种版本控制工具,它的诞生是因为linux日益增长,代码合并依靠手工已经非常吃力。
一开始linux也是用的其他的版本控制工具,但是后来提供版本控制的公司不再向linux社区
提供支持,原因是因为这些大神太有创造性了,要修改他们的代码…balabala。最后linux
的核心人物linus带领自己的小伙伴自己编写了一个版本控制工具:git.大神是怎么炼成的,
心中充满了羡慕。

git能做什么

之前也没用到版本控制工具,只是在上一个项目中接触一点。但是也没系统的学习,刚好又
要用到版本控制工具,就详细的学习一下。这么牛的东西不学习一下,怎么装x呢。
其实以前自己一个人做项目没有什么感受,但是几个人共同完成一个项目,问题就会层出
不穷。比如jar包版本不同啊,这个可以通过项目管理工具maven来解决,什么是maven,
有时间另写一遍来介绍它。还有一个比较大的问题,就是几个人的文件如何保持一致,一开
始大家都是同一个文件没什么问题。但是任务一开始就是完全不同的场景了,A开始写他的
功能,B开始写他的测试,C开始写他的页面。最后如何让大家的文件汇总起来呢,总不能一
直复制粘帖吧,这样的效率是惨不忍睹的。git或者其他的版本控制工具可以帮助我们解决
这个问题,具体怎么解决,一步一步学习吧。

git怎么用

git的安装

在ubuntu环境:

1
sudo apt-get install git

查看是否安装成功:

1
2
git --version
git version 2.7.4

git初次尝试

先创建一个文件夹作为git的工作目录,

1
mkdir demo

什么意思呢?其实这个工作目录在git中有个专门的名称,叫做仓库,英文repository.
它的意思这个目录下的所有文件都可以被git管理,每个的创建,修改,删除,git都能知道。
git记录这些事情的发生,所以它也可以追踪到在整个文件的历史,并且它可以使某个文件回
到某个历史节点。

刚刚只是创建了一个目录,还没有和git取得关联,这时候git并不能管理整个文件夹。
使用以下命令:

1
2
cd demo
git init

初始化空的 Git 仓库于 /home/xxx/workplace/demo/.git/

这时候demo目录就变成了git可以管理的仓库了。
现在仓库下创建一个文件
touch readme.txt
在该文件里写入以下内容:

1
2
3
4
Git is a version control tool.
Git is so nice.
使用:
git add readme.txt

可以将修改后的文件提交到暂存区内。

1
2
3
4
5
位于分支 master
初始提交
要提交的变更:
(使用 "git rm --cached <文件>..." 以取消暂存)
新文件: readme.txt

使用:

1
git commit  -m "readme.txt"

可以将暂存区的内容都提交到本地仓库中。

(根提交) 435eb64] readme.txt
1
2
 1 file changed, 2 insertions(+)
create mode 100644 readme.txt

git的魔法–穿越时空

在表演git的魔法之前,我们先为文件做一个时间线,这样对一个文件才能完成穿越时空
的操作。
1.在readme.txt中写入

1
this is first add someting to readme.txt

然后输入命令

1
2
git add readme.txt
git commit -m 'first add'

2.在readme.txt中写入

1
this is second add someting to readme.txt

然后输入命令

1
2
git add readme.txt
git commit -m 'second add'

使用git log 来查看历史线。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  commit fac4c988c2e9dc06074355cd137b51319ed65a7f
Author: learner66 <2952184251@qq.com>
Date: Tue May 22 19:01:07 2018 +0800

second add

commit b1c5887736de9909de4643ddd06a84b70ab50394
Author: learner66 <2952184251@qq.com>
Date: Tue May 22 19:00:17 2018 +0800

first add

commit 435eb64ac7e2b5f0d2f8153d940fc1d2ff92b89b
Author: learner66 <2952184251@qq.com>
Date: Tue May 22 18:50:46 2018 +0800

readme.txt

readme.txt

1
2
3
4
Git is a version control tool.
Git is so nice.
this is first add something to readme.
this is second add something to readme.

穿越到过去

已经知道了这条历史线,git可以任意的穿越到过去的某个时间点。
回到过去有两种方法

  1. git reset –hard HEAD^
    HEAD代表当前时间,HEAD^代表上一个时间点,HEAD~100代表是现在到过去的第一
    百个时间点。
  2. git reset –hard blc588
    blc588代表commit id。

    使用第二条命令的效果:

    1
    2
    3
    4
    5
    6
    xxx:~/workplace/demo$ sudo git reset --hard b1c588
    HEAD 现在位于 b1c5887 first add
    xxx:~/workplace/demo$ cat readme.txt
    Git is a version control tool.
    Git is so nice.
    this is first add something to readme.

回到未来

这个只能使用commit id来进行操作,如果不知道commit id,可以试用git reflog
来查看。

1
2
3
4
5
6
7
8
9
10
11
12
xxx:~/workplace/demo$ git reflog
b1c5887 HEAD@{0}: reset: moving to b1c588
fac4c98 HEAD@{1}: commit: second add
b1c5887 HEAD@{2}: commit: first add
435eb64 HEAD@{3}: commit (initial): readme.txt
xxx:~/workplace/demo$ sudo git reset --hard fac4c98
HEAD 现在位于 fac4c98 second add
xxx:~/workplace/demo$ cat readme.txt
Git is a version control tool.
Git is so nice.
this is first add something to readme.
this is second add something to readme.

网络之TCP/UDP漫游

发表于 2018-07-11 | 更新于: 2018-07-12 | 分类于 计算机网络
字数统计: 3,608 | 阅读时长 ≈ 13

可靠传输的TCP

TCP的可靠传输靠什么保证的,TCP可靠传输面临什么问题,TCP的连接和断开。
TCP和UDP都是应用层之下的协议,都是用来提供端到端的传输服务。那么TCP
和UDP有什么不同。以上都是对TCP协议的一些问题,下面慢慢展开来说。

TCP是面向连接的

TCP是面向连接的,这个问题看到过很多次,每次都有过思考,连接代表什么,
是虚拟电路?这样理解的话,那UDP也在通信过程中建立了一个虚拟电路啊。
很明显,这样的解释并不合理。
那么该如何理解这个连接的意思呢。追本溯源,先看看一下TCP和UDP是怎么
定义的。
UDP:
(1)udp是无连接的,发送数据之前不需要建立连接
(2)udp使用尽最大努力交付,即不保证可靠交付,因此主机不需要维持
复杂的连接状态表
(3)udp是面向报文的,对应用层交下来的报文,添加首部就交付IP层。
TCP:
(1)tcp是面向连接的
(2)tcp连接是1对1的
(3)tcp提供可靠交付的服务,通过TCP连接传送的数据,无差错,不丢失
不重复,并且按序到达。
这里仔细思考一下,tcp可以让传输的数据有序,无差错,不丢失。如果直接
传输,可以保证达到这样效果吗?肯定不可以啊,这里双方都应该保存通信的
状态,哪些数据已经传输了,哪些数据丢失了,哪些数据还没有传输过来,这
都需要双方记录下来(为什么双方,因为tcp是全双工通信,可以作为接受端,
也可以作为发送端),如果数据已经传输了,发送方可以把数据从缓存中清除,
如果数据丢失了,数据丢失了,接收方要通知发送方重传。这些状态都是需要
保存的,对状态的保存就是TCP连接的含义。同样,UDP不需要保存这些状态
所以,UDP是无连接。(当下理解,有错再改)

TCP怎么建立连接

那么根据上面的说法,其实建立连接就是对状态的初始化。生出序列号,同样
也有了确认号,并且数据包的大小,发送窗口的大小都在这是做一个初始化。
那么TCP怎么建立连接呢?
TCP建立连接一般经过三次握手,SYN标志位表示申请建立连接,FIN标志位
表示结束连接。
(1)客户端首先发送带有SYN的数据包给服务器端,并且给出自己的序列号。
(2)服务端收到客户端的数据包后,应该给出一个回应,包含确认号。表示
自己已经收到了客户端的连接请求,同时连接应该是双方都需要确认的,服务
端也发送一个带有SYN的数据包,给出自己的序列号,并且和回应放到一个数
据包中,如果能收到客户端的回应,说明双方的通信没有问题。
(3)客户端收到了服务端的回应,就知道了,服务端能够接收自己的数据包,
并且服务端能够正常发送数据包。这时,客户端给出回应。服务端收到后,表
示自己和客户端的通信可以正常进行,连接建立。之后可以正常通信。
network-1

为什么是三次握手

因为三次握手已经可以确定双方能够正常通信,这也是能够确认双方能够正常
通信的最小次数。
比如,两个人电话交流。
A:喂,听得到吗?
B:我能听到,你能听到吗?
A:我也能听到。
三次交流才能说明双方的语音的发送和接收都没问题,如果是四次就多了,没
有必要,如果是两次呢?B不确定自己的通话对方能够听到。

为什么不是两次握手

网络环境是复杂的,如果发送端第一次发送的连接请求遇到网络拥塞,超过了
重传时间还没收到回应。发送端会重新发送一个连接请求来建立连接,这个连
接走了一个比较畅通的道路,很快就建立建立,并且完成来通信。但是第一个
连接并没有丢失,而是延时到达了,当它到达的时候,又会建立一次连接,但
是这次连接其实已经过时了,只会平白浪费资源。
如果是三次握手,即使第一个连接延时到达,当它请求连接的时候,因为客户
端已经知道连接已经建立了,会拒绝这次连接。

TCP怎么传输数据

经过上面的一系列步骤,连接终于建立完成了。
在阐述TCP怎么传输数据之前,先来熟悉几个标志位,这几个标志位对于理解
TCP是如何传输数据至关重要。
(1)Seq:序号,用于表示本次发送报文段的第一个字节的序号。
(2)Ack:确认号,用于表示期望下一次接受报文段的第一个字节的序号。
(3)len:本次报文段的长度,seq+len=Ack
为了确保每个数据都能被接受到,就需要对每次的传输进行确认。每发送
一个数据报就进行一次确认。
network-2

出现丢包怎么办|停止等待协议

上面的设想是好的,但是网络环境并不是这样的,数据会出现延时或者丢包
如果出现丢包问题该怎么办?
最简单的方法就是发送方确定上一次数据包被接收方收到后,才进行下一次
发送。首先,发送方在发送完数据后给自己一个定时器,如果超过定时时间
还没有收到确认,就重新发送一次。
没有收到确认的情况有两种,一种是自己的数据丢失或者延迟,二是因为确
任丢失或者延迟。
(1)对于发送发放方数据丢失,接收方重新接受发送方重传的数据
(2)对于数据延迟,接收方应该丢弃重复的数据,但是还是要发送确认
(3)对于确认丢失,发送方重传,接收方丢失重复数据,但是要发送确认
(4)对于确认延迟,发送方丢失重复确认就可以了
停止等待
network-3
确认丢失
network-4
确认延迟
network-5

每次只确认一个,效率太低怎么办|滑动窗口协议(ARQ)

每次只发送一个数据包,然后傻傻地等着确认,百无聊赖。珍贵的时间就这样
被消磨了,如果能在等待的时候接着发送数据,想必是极好的。
其实滑动窗口协议就是这样的一个设计初衷,它开始规定一个了窗口大小,可以
连续发送数据包,只要不超过窗口大小就可以。另外,再收到接收方的确认后。
窗口还可以进行滑动。
network-6

滑动窗口协议下的确认机制

现在,有了滑动窗口协议,一次可以发送多个数据包,但是接收方的确认机制
是怎么样的呢?是每发一次进行一次确认,还是会累计到一次的次数后进行确
认?
滑动窗口协议规定,发送方每收到一个确认,就把发送窗口向前滑动一个分组
的位置,但是接收方一般都是采用累计确认的方式,也就是说,不用对每一个
分组都发送一个确认,可以在几个分组接受后,对按序到达的最后一个分组进
性确认即可。累计确认优缺点都很明显,不用每一次都发送一个确认,这很
明显节省了带宽。但是如果出现丢包,那么在这个包后面发送的数据要重新
发送一次。

选择确认SACK

上面所说的确认机制在丢包的时候可能要重传已经发送过的数据,那么能不
不能只重传丢包的数据呢?TCP给出了一个解决方案,就是选择确认,接受
方将接收到的数据边界发给发送方,发送方能够推算出丢的包。但是由于TCP
首部的option只有40个字节,每个边界都需要4个字节(32bit),所有
总共可以报告4个已经接受到分组的边界。另外8个字节还需要对SACK做说明。
但是SACK文档并没有说明接收方如果设置SACK,所以一般的选择还是重传
丢包之后所有的数据包。

超时重传的时间确定

超时重传的概念很简单,但是如何确定超时重传的时间呢?计算机网络是分组
交换的,有可能会选择较拥挤的路线,也有可能选择较通畅的路线。如果把重
传时间设置过小,可能会造成不必要的重传。如果超时重传的时间设置的过大,
又可能使网络空闲的时间过大,降低了传输效率。
TCP采用的是一种自适应的算法,它记录一个报文段发出的时间,以及收到相应
确认的时间。这两个时间差就是报文段的往返时间RTT。TCP保留了RTT的一个
加权平均往返时间RTTs.
new RTTs = (1-a)×(old RTTs) + a×(new RTT)
first RTTs = first RTT
a的推荐值为0.125
RTTd是RTT的加权平均值
new RTTd = (1-b)× (old RTTd)+ b × abs(RTTs - new RTT)
first RTTd = half of fisrt RTT
b的推荐值是0.25
RTO = RTTs + 4 × RTTd

TCP流量控制和拥塞控制

TCP如何在复杂的网络环境中进行资源的最大利用,并且又不会增加网络的负担,
如果根据网络环境来选择自己的发送窗口的大小。

发送窗口大小的制约因素

发送窗口肯定不是无限大的,有两个因素在制约着它,一个是接收方的接受
窗口,如果接收方的数据还在处理,没有多余的空间来存放发送的分组,那么
发送的数据就会被丢弃。所以发送窗口的大小要有接收方的接收窗口制约,不
能大于接收方的窗口大小。另外一个就是网络因素,如果网络出现拥塞,应该
适当的减小发送方的窗口,不然只会使得网络环境更加拥塞。
size(发送窗口) < min(size(拥塞窗口,size(接收窗口))

慢启动和拥塞避免

在TCP刚连接开始,本机是不知道网络环境是怎么样的,不知道当前网络环境
是拥塞还是畅通。所以本机只能谨慎向网络中发送数据,先将拥塞窗口设置为
2(RFC推荐MUST be less than or equal to 2*SMSS bytes and
MUST NOT be more than 2 segments.),然后发送2个数据包后会收到
2个确认,把拥塞窗口设置为2+2;然后下一次可以连续发送4个数据包,以此
类推,当拥塞窗口到达慢启动阈值的时候或者侦测到拥塞时,慢启动就结束了。

慢启动阈值

初始的慢启动阈值可以是任意值,启动慢启动算法,当侦测到网络拥塞时,就
可以将慢启动阈值设置为发生拥塞时发送窗口的1/2.之后,只要发生拥塞,就
用上面的方式设置慢启动阈值。

拥塞侦测

在TCP连接中,只要发送的数据超时没有收到确认,就认为网络发生了拥塞。

如果拥塞窗口到达了慢启动阈值,那么接下来就使用拥塞避免算法,在每个
RTT收到确认后会让拥塞窗口增加一个MSS,直到发生拥塞,拥塞窗口重新设置
为一个比较小的值,继续慢启动算法。
network-7

快重传与快恢复

快重传是在接收方意识到对方丢包,自己收到了一个乱序的数据包,自己主动
发送多次相同的确认,让发送方尽早知道自己丢包了,然后把丢失的数据包发送
过来,而不是等到重传计时器时间到了再重传。一般是接收方收到乱序的数据
后连续发送三个确认给发送方,发送方连续收到三个重复确认就需要重传丢失
的包。
但是既然能够连续收到三个重复确认,说明网络环境并没有那么糟糕,不然应
该会出现丢包问题,不可能收到三个重复确认。这时候使用慢启动就显得不是
那么合理,所有利用快恢复来解决这个问题。
当方法方连续收到三个重复确认,就把慢启动阈值设置原来阈值的1/2,并且这
拥塞窗口并不是直接设置为1或者2这么小的值,而是也设置为原阈值的1/2,
并且之后使用拥塞避免算法。
在采用快恢复算法的时候,只有在TCP连接建立时和网络出现超时重传时才使
用慢启动算法。
network-8

TCP怎么终止连接

TCP终止连接称为四次挥手。
(1)想要结束连接的一方发送带有FIN的数据包
(2)另一方接收到FIN数据包后就发送一个确认。
(3)另一个此时也发送一个FIN数据包
(4)该机接收到FIN数据包后,给出一个确认,这时候连接完全断开。
关于TCP连接和终止连接的问题还有更深入的细节需要讨论,会在其他文章中讨论。

磁盘IO的工作机制

发表于 2018-07-10 | 更新于: 2018-07-10 | 分类于 操作系统
字数统计: 2,360 | 阅读时长 ≈ 8

磁盘IO的工作机制

需要搞清的问题,磁盘IO怎么工作的,IO的控制方式有哪几种。
为什么需要对磁盘IO进行探究,在web应用中,涉及到最多的就是数据的流通问题,
无论是在本地机器上的数据流动还是在网络上的数据流通,都会涉及到IO操作,IO
问题也成为应用系统的瓶颈问题,了解磁盘IO的工作机制有助于进一步理解复杂的
web应用问题。

字节和字符

这个问题的引入是来自java中的字符接口(reader,write)和字节接口(Inputstream)
在java中,Inputstream以及和它相似的接口都是用于处理字节流,一个字节就是8bit。
而Reader以及和它相似的接口是用于处理字符流,字符流会涉及到字符集的问题,不同的
字符集可能占用不同的字节,所以将输入内容翻译成不同的字符。例如使用ASCII和unicode
来翻译相同的字节流,肯定会得到不同的字符流。这里是对java中字节和字符做一个简单的
讨论。

访问文件的方式

应用程序想要读入和写入文件IO操作都需要调用操作系统的接口,进行系统调用就意味着要
进行内核空间和用户空间的切换。数据要先从磁盘传输到内核空间,然后再从内核空间传输
到用户空间。对于一个耗时的操作,这样的过程需要非常久。

标准IO

标准IO又称缓冲区IO,它的方式是在内核空间中使用缓冲区机制,数据会先被复制到内核
缓冲区,然后才从内核缓冲区复制到应用程序的地址空间。
在读取IO文件时,如果要读取的数据已经在内核缓冲区中,那么直接从缓冲区复制到用户空
间,而不需要进行实际的读磁盘操作,毕竟磁盘是一个低速设备,对它的访问会需要比较久
的时间。如果内核缓冲区没有所需要的数据,才会进行读磁盘操作。
同样,写操作也是先把数据从用户空间复制到内核空间,其实,这样一个写操作已经完成。
至于内核缓冲区中的数据何时写入到磁盘中,则由操作系统决定。
当然,如果向立即写入,可以试用同步写机制,sync()函数可以帮助完成直接写操作。
标准IO有什么缺点吗?
标准IO的工作方式可以很明显的看出来,它需要多次的复制,从用户空间到缓冲区,从缓冲
区到内核空间,多次的复制会占用更多的CPU时间。
disk-1

直接IO

有些应用程序并不太需要操作系统的缓存机制,因为它们对自己的数据可能有更好的缓存
方式,数据库管理系统就是这样的一个例子,系统明确知道应该缓存哪些数据,应该失效
哪些数据,所以在这种情况下,并不太需要系统缓存的存在,使用直接IO的方式更适合这
些应用系统。
disk-2
直接IO有缺点吗?
直接IO的缺点很明显,虽然它能减少CPU的使用时间,但是如果应用缓存中没有想要的数
据,那么就需要花费漫长的时间从磁盘中把数据读入到缓存中。

内存映射

内存映射是指操作系统将内存中的某一块区域与磁盘中的文件关键起来,当要访问内存
的一段数据时,转换为访问文件的某一段数据。
disk-3

硬盘上的文件的位置和进程逻辑地址空间中一块大小相同的区域之间是一一对应的,逻辑
上的映射。在内存映射过程中,并没有实际的数据拷贝,文件没有被载入内存,只是逻辑
上被放入内存,具体到代码,就是建立了相应的数据结构。系统调用mmap()可以实现,
所以建立内存映射的效率很高。
mmap()会返回一个指针ptr,它指向进程逻辑地址空间中的一个地址,这样以后,进程无
需再调用read活write对文件进行读写,而只需要通过ptr就能够操作文件。
但是ptr所指向的是一个逻辑地址,要操作其中的数据,必须通过MMU将逻辑地址转换成
物理地址。
建立内存映射并没有实际复制数据,这时,MMU在地址映射表中是无法找到ptr对应的物
理页面,会产缺页中断,通过mmap()建立的映射关系,从硬盘上将文件读取到物理内存
中。

从硬盘上将文件读入内存,都要经过文件系统进行数据复制,为什么试用内存映射会比
read/write效率高呢?内存映射只是将内存和硬盘文件进行映射,并没有实际的数据
拷贝。真正的数据拷贝是由缺页中断产生的,由于mmap()将文件直接映射到用户空间,
所以根据这个映射,直接将文件从硬盘拷贝到用户空间,整个过程只进行了一次数据拷贝。
而read/write需要进行两次的数据拷贝,所以内存映射在处理大文件的时候效率比较高。
内存映射文件原理探究

IO控制方式

上面所有内容都是在说明如何让磁盘文件复制到用户空间中,何种工作机制能够更加节省
CPU占用时间,或者何种工作机制能够更符合自缓存的应用程序。
但是文件中的内容是如何从磁盘复制到内核空间,又是如何从内核空间复制到用户空间的
呢?是一个一个字符的复制吗?还是一块一块数据的复制?这里就需要讨论的是IO的控
制方式。

程序IO方式(PIO)

程序IO的方式是轮循,CPU首先向IO控制器发送一个读命令,IO控制器会设置自己的状态
字busy。之后CPU会一直轮询busy,当busy=0时,表示控制器已经把需要的字符读入到了
自己的寄存器中,CPU可以取走这个数据。这个过程看似没什么问题,但是这个过程CPU一
直在询问IO控制器的状态,导致CPU在IO控制器读数据的时间也不能去做其他的事情,很
明显会降低CPU的工作效率。
disk-4

中断方式

中断应该是比较熟悉的一个名词,现代计算机中都有中断系统。进程的切换,方法的调用
和退出都需要中断来协助完成,此时敲击键盘就是一个keyboard interrupt.
使用中断方式就不需要CPU一直处于轮询状态,因为IO控制器在读完数据后会给CPU一个
中段,让CPU来取数据,这样的话,CPU在其他时间就可以去做其他的任务。这样CPU得
到解放,效率会大大提升。
disk-5

直接存储器访问DMA方式

虽然中断方式已经解放了一部分CPU,但是中断方式每次只能读一个字节(具体可能看IO
控制器数据寄存器的大小,一般都是8bit,一个字节)。所以如果要读入一块数据,例如
要读取1K的数据,那么CPU仍然要进行1K次的干预。有没有什么办法让CPU读1K数据的时
侯只进行一次干预呢?
DMA就是一次读取一个数据块的。
先来看一个DMA控制器
disk-6
当CPU要从磁盘读入一个数据块时,便向磁盘控制器发送一条读命令,该命令会被送到其中
的命令寄存器(CR)中。同时,还必须发送本次要将数据读入的内存起始目标地址,该地
址被送入内存地址寄存器(MAR)中,本次要读的数据的字(节)数则送入数据计数器(DC)
中,还需要将磁盘中的源地址直接送至DMA控制器的IO控制逻辑上。之后CPU就可以去处理
其他任务,数据的传送由DMA自己完成。
当DMA控制器已经从磁盘中读入一个字节的数据并送入数据寄存器(DR)中,再将该数据送
到内存中区,将DC内容减1,对MAR内容加1.继续传送,直到DC=0为止。

通道控制方式

DMA虽然能够读取一个数据块,问题又出来了,读取多个数据块怎么办?通道方式其实是
DMA的加强版,它自己拥有通道程序,能够执行一系列通道指令。
它把对一个数据块的读写为单位进行中断变成对一组数据块的读写为单位进行中断,
进一步解放CPU。

java对象的生存周期

发表于 2018-07-09 | 更新于: 2018-07-09 | 分类于 java
字数统计: 953 | 阅读时长 ≈ 3

java对象的生存

对于java对象的生存问题,需要考虑的是,java对象在哪生存,java对象生存时间,
java对象回收问题。

java对象在哪生存

java虚拟机对java内存区域有着很明确的划分。这个会有另外的文章来叙述,程序员
最关心的两个内存区域就是栈和堆。这也是经常提及的两个内存区域。
栈一般是用来存放局部变量的,在java中,局部变量是在方法中出现的。
stack-1
如图所示,调用的方法会被存储在栈中,当然,处在方法中的局部变量也会存储在栈
中。并且先调用的方法会被压到栈底,图中,fun1是先被调用的。
栈的用途很明确,用来存储调用的方法及方法中的局部变量。
那么在java中,堆被设计用来专门存储对象的。
当一个类被加载到内存,并且解析后。它的对象所占用的空间其实已经可以确定了,
当使用new创建对象的时候,会在堆上为为其分配相应的空间。实例变量是类中所
声明的属性,会随着对象一起存储到堆上。类变量会有另外的存储空间。
heap-1
栈中的局部变量和堆中的实例变量有什么关系?
如果是原始类型的局部变量(除了String),它在栈上保存的是自己的真实值。
但是如果是非原始类型的局部变量,那么它在栈上保存的是指向对象的引用,而
不是对象本身。
stack-heap

java对象的生存时间

上面提到,java中有两种变量,实例变量和局部变量。
先来讨论一下这两种变量,局部变量的生存周期很明显,当方法被调入栈中,
局部变量开始存在,当方法被调出栈,局部变量结束了它的一生。它是随方法调用
而存在,随方法消失而消失。
实例变量是随着对象创建而创建,随着对象的销毁而消失。
这里并没有说明java对象的生存时间,当有引用指向java对象时,java对象就不会
被垃圾回收器回收,但是如果没有引用指向java对象时,它很可能就被GC回收。
如何判断java对象是否能被回收呢?
一般的思路是可以为该对象的引用设置一个计数器,如果该计数器为0,说明没有引用
指向该对象,那么该对象是可以回收的,但是这种方法不能很好的解决循环引用的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ReferenceCountingGC{

public Object instance = null;
private static final int _1MB = 1024 * 1024;
private byte[] bigSize = new byte[2 * _1MB];
public static void testGC(){
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
system.gc();
}
}

这里深入理解JVM中的一个例子,例子中objA和objB其实是相互引用着,但是,
并不妨碍垃圾回收期对objA和objB的回收,所以引用计数法是存在一定的问题。
java虚拟机采用的是可达性分析算法来判断一个对象是否可以被回收。
首先GC Roots的对象作为起点,如果一个对象不能到达这些GC Roots,那么就
可以判断这些对象是可以回收的。
GC Roots:
(1)虚拟机栈中引用的对象
(2)方法区中静态属性引用的对象
(3)方法区中常量引用的对象
(4)本地方法栈中引用的对象
[深入理解java虚拟机]

java对象的回收

操作系统-进程通信

发表于 2018-06-01 | 更新于: 2018-06-01 | 分类于 操作系统
字数统计: 41 | 阅读时长 ≈ 1

进程通信

(1)一个进程如何把信息传递给另一个
(2)两个或多个进程的活动不会交叉
(3)正确的顺序

操作系统的主要功能

发表于 2018-06-01 | 更新于: 2018-06-01 | 分类于 操作系统
字数统计: 283 | 阅读时长 ≈ 1

处理机管理功能

在传统的多道程序系统中,处理机的分配和运行都是以进程为基本单位,因而对处理机的
管理可归结为对进程的管理;在引入线程的OS中,也包含对线程的管理。处理机管理的主要
功能是创建和撤销进程(线程),对诸进程(线程)的运行进行协调,实现进程(线程)之间
的信息交换,以及按照一定的算法把处理及分配给进程。

1.进程控制

为作业创建进程,撤销已经结束的进程,以及控制进程在运行过程中的状态转换,同样,
它也应该具有对线程进行类似操作的能力。

2.进程同步

多个进程想要使结果具有可再现性,应该对进程进行同步控制。
通常采用进程互斥和进程同步来实现该功能

3.进程通信

4.进程调度

作业调度和进程调度

存储器管理功能

设备管理功能

文件管理功能

操作系统-进程

发表于 2018-05-30 | 更新于: 2018-06-01 | 分类于 操作系统
字数统计: 4,168 | 阅读时长 ≈ 15

进程

进程是正在执行程序的实例,包括程序计算机,寄存器和变量。

从概念上说,每个进程都有自己的虚拟CPU,当然,实际的CPU是在进程之间来回切换。

实际上,只有一个物理程序计数器,所以在每个程序运行时,它的逻辑程序计数器被
装入到实际的程序计数器中。当该程序执行结束(暂停)时,物理程序计数器被装入到
逻辑程序计数器中。

守护进程

unix中,可以通过一个系统调用来创建新进程:fork。这个系统调用会创建一个与调
用进程相同的副本。在调用fork后,这两个进程(父进程和子进程)拥有相同的内存映像
,同样的环境字符串和同样的打开文件

写时复制:

进程的状态:

运行:cpu和条件都满足
阻塞:cpu满足,必要条件不满足
就绪:cpu不满足,必要条件满足

进程控制块(PCB)–进程存在的唯一标志

PCB记录了操作系统所需的、用于描述进程的当前情况以及控制进程运行的全部信息。
操作系统是根据PCB对进程进行控制和管理的。
(1)当OS要调度某进程执行时,要从该进程的PCB中查出其现行状态及优先级。
(2)调度到某进程后,根据其PCB中所保存的处理机状态信息,设置该进程恢复运行
的现场,并根据PCB中的程序和数据的内存始址,找到其程序和数据。
(3)进程载执行过程中,与其他进程的通信,同步都需要用到PCB
(4)当进程切换时,处理机环境也保存在PCB中

进程的实现:

有了进程模型,进程应该如何实现
为了实现进程模型,操作系统维护者一张(一个数据结构),即进程表。每个进程占用
一个进程表项。该表项包含了进程状态的重要信息,包括程序计数器、堆栈指针,内存
分配状况、所打开文件的状态、帐号和调度信息,以及其他在进程进行状态转换时所必
须保存的信息,从而保证该进程随后能再次启动,就像从未被中断过一样。

中断向量在进程切换中的重要作用

单个CPU如何维护多个顺序进程
假设当一个磁盘中断发生时,用户进程正在运行,则中断硬件将程序计数器,程序状态字、
有时还有一个或多个寄存器压入堆栈,计算机随即跳转到中断向量所指示的地址。这些是
硬件完成的所有操作,然后软件,特别是中断服务例程就接管了一切剩余的工作。

线程

为什么需要多线程
1.有了多线程,并行实体拥有共享同一个地址空间和所有可有数据的能力。
2.线程比进程更轻量级。
3.IO密集的处理,使用多线程效率更高。

线程概念试图实现的是,共享一组资源的多个进程的执行能力,以便这些线程可以为完成某
一个任务而共同工作。

进程:
用某种方法将相关的资源集中在一起。进程有存放程序正文和数据以及其他的资源的地址空间。
这些资源包括打开的文件,子进程,即将发生的定时器、信号处理程序、帐号信息等。
线程:CPU调度的单位

线程堆栈

线程的问题

线程的过程
子进程和父进程可以拥有相同的多线程吗,如果解决两者中线程的同步
多线程中共享数据的同步

POSIX线程

为了实现可移植的线程程序,IEEE定义了线程的标准。它定义的线程包叫作pthread.大部分
UNIX系统都支持该标准。
pthread_create 创建一个新的线程
pthread_exit 结束调用的线程
pthread_join 等待一个特定的线程退出
pthread_yield 释放CPU来运行另外一个线程
pthread_attr_init 创建并初始化一个线程的属性结构
pthread_attr_destroy 删除一个线程的属性结构

代码演示:

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
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

#define NUMBER_OF_THREADS 10

void *print_hello_world(void *tid)
{
printf("hello world,Greeting form thread %d\n", tid);
pthread_exit(NULL);
}

int main(int args,char *argv[])
{
pthread_t threads[NUMBER_OF_THREADS];
int status,i;
for(i=0;i<NUMBER_OF_THREADS; i++){
printf("main here,creating thread %d\n",i);
status = pthread_create(&threads[i],NULL,print_hello_world,(void*)i);
if(status!=0){
printf("oops,pthread_create returned error code %d\n", status);
}
}
exit(NULL);
}

output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
main here,creating thread 0
main here,creating thread 1
hello world,Greeting form thread 0
hello world,Greeting form thread 1
main here,creating thread 2
main here,creating thread 3
hello world,Greeting form thread 2
main here,creating thread 4
hello world,Greeting form thread 3
main here,creating thread 5
hello world,Greeting form thread 4
main here,creating thread 6
hello world,Greeting form thread 5
main here,creating thread 7
hello world,Greeting form thread 6
main here,creating thread 8
hello world,Greeting form thread 7
main here,creating thread 9
hello world,Greeting form thread 8
hello world,Greeting form thread 9

线程的实现方式

线程已经在许多系统中实现了,但各系统的实现方式并不完全相同。在有的系统中所实现
的是用户级线程(ULT),而另一些系统所实现的是内核支持线程(KST),还有的系统是
混合实现。

什么是用户级线程

用户级线程仅存在于用户空间中,对于这种线程的创建、撤销、线程之间的同步与通信
等功能,同无需利用系统调用来实现。对于用户级线程的切换,也无须内核的支持。对于
设置用户级线程的系统,其调度仍是以进程为单位进行的,内核完全不知道用户级线程的
存在。
在用户空间管理线程时,每个进程都需要有其专用的线程表,用来跟踪该进程中的线程。
线程表和进程表相似,不过它记录的仅仅是各个线程的属性,比如每个线程的程序计数器、
堆栈指针、寄存器和状态等。该线程表由运行时系统管理。

什么是内核支持线程

对于通常的进程,无论是系统进程还是用户进程,进程的创建、撤销、以及要求由系统
设备完成的I/O操作,都是利用系统调用而进入内核,再由内核中的相应处理程序予以完成。
进程的切换同样是在内核的支持下实现的。不论什么进程,它们都是在操作系统内核的支持
下运行的,是与内核紧密相关的。
这里所谓的内核支持线程KST(kernel supported threads),也都同样是在内核的
支持下运行的,即无论是用户进程中的线程,还是系统进程中的线程,他们的创建、撤销和
切换等也是依靠内核,在内核空间实现的。此外,在内核空间还为每一个内核支持线程设置
了一个线程控制块,内核是根据该控制块而感知某线程的存在,并对其加以控制。
内核支持线程有以下的优点:
(1)内核中拥有每个线程的线程控制块,那么内核就可以使用调度算法来调度进程中多个
线程并发执行。
(2)如果进程中的一个线程被阻塞了,内核可以调度该进程中的其他线程占用处理器运行,
也可以运行其他进程中的线程。
(3)内核支持线程具有很小的数据结构和堆栈,线程的切换比较快,切换开销小。
(4)内核本身也可以采用多线程技术,可以提高系统的执行速度和效率。
内核线程的缺点也很明显,对于用户线程的切换,要从用户态进行到内核态。

线程切换

线程切换

用户级线程为什么要和内核级线程进行映射,映射的模型

线程的实现

不论是进程还是线程,都必须直接或间接地取得内核的支持。由于内核支持线程可以
直接利用系统调用为它服务,故线程的控制相当简单;而用户级线程必须借助于某种形式
的中间系统的帮助方能取得内核的服务,故在对线程的控制上要稍复杂些。

内核支持线程的实现

在仅设置了内核支持线程的OS中,一个可能的线程控制方法是,系统在创建一个新进程
时,便为它分配一个任务数据区PTDA(per task data area),其中包括若干线程控
制块TCB空间。在每一个TCB中可保存线程标识符,优先级,线程运行的CPU状态等信息。
虽然这些信息与用户线程TCB中的信息相同,但现在却是被保存在内核空间中。
每当进程要创建一个线程时,便为新线程分配一个TCB,将有关信息填入该TCB中,并
为之分配必要的资源,如为线程分配数百至数千的栈空间和局部变量存储区。

用户级线程的实现

用户级线程是在用户空间实现的。所有的用户级线程都具有相同的结构,它们都运行在
一个中间系统的上面。当前有两种方式实现的中间系统,即运行时系统和内核控制线程。
(1)所谓“运行时系统”,实质上是用于管理和控制线程的函数(过程)的集合,其中包括
用于创建和撤销线程的函数、线程同步和通信的函数以及实现线程调度的函数等。正因为
有这些函数,才能使得用户线程和内核无关。运行时系统中的所有函数都驻留载用户空间,
并作为用户级线程和内核之间的接口.
在传统的OS中,进程在切换时必须先由用户态转为核心态,再由核心来执行任务;而用
户级线程在切换时则不需要转入核心态,而是由运行时系统的线程切换过程来执行切换任
务。该过程将线程的CPU状态保存在该线程的堆栈中,然后按照一
定的算法选择一个处于就绪状态的新线程进行运行,将新线程堆栈中的CPU状态装入到CPU相应的寄存器中;
一旦将栈指针和程序计数器切换后,便开始了新线程的运行。
由于用于级线程的切换无需进入内核,且切换操作简单,因为使用用户级线程的切换速度
非常快。
不管在传统的OS中,还是在多线程OS中,系统资源都是由内核管理的。在传统的OS中,
进程是利用OS提供的系统调用来请求系统资源的,系统调用通过软中断机制进入OS内核,
由内核来完成相应资源的分配。用户级线程是不能利用系统调用的。当线程需要系统资源
时,是将该要求传送给运行时系统,由后者通过相应的系统调用来获得系统资源的。

(2)内核控制线程
这种线程又称为轻型进程LWP(light-weight process)。每一个进程都可拥有多个LWP,
同用户级线程一样,每个LWP也有自己的TCB。LWP可通过系统调用来获得内核提供的
服务,这样,当一个用户级线程运行时,只要将它链接到一个LWP上,此时它便具有了内
核线程的所有属性。
LWP线程是有限的,所有用户线程可以复用这些LWP。如果需要与内核通信,则需要和LWP
进行连接。这样,通过LWP可把用户级线程和内核线程链接起来,用户级线程可通过LWP来
访问内核。从内核的角度来看,它只能意识到LWP的存在,而意识不到用户级线程的存在。
这种方式也是实现了
用户级线程和内核无关。
lwp
当用户级线程不需要与内核通信时,并不需要LWP;而当要通信时,便需要借助于LWP,
而且每个要通信的用户级线程都需要一个LWP。如果要通信的用户级线程大于LWP的数量,
那么就需要有用户级线程等待。
在内核线程执行操作时,如果发生阻塞,那么与之相连的LWP也会阻塞,进而连接到LWP
上的用户级线程也会被阻塞。如果进程只有一个LWP,那么和传统的OS一样,当进程执行系
统调用时,该进程实际上是阻塞的。但如果一个进程中含有多个LWP,一个LWP阻塞,进程
中的其他LWP可以执行;即使进程中的所有LWP全部不阻塞,进程中的线程也仍然能继续执
行,只是不能再去访问内核。

用户级线程和内核控制线程(LWP)的连接

(1)一对一模型
该模型为每一个用户线程都设置一个内核控制线程与之相连,当一个线程阻塞时,允许调度
另一个线程运行。在多处理及系统中,则有多个线程并行执行。
该模型并行能力强,但每创建一个用户线程相应地就需要创建一个内核线程,开销较大,
需限制整个系统的线程数。win2000,winNT,OS/2实现该模型。
(2)多对一模型
该模型将多个用户线程映射到一个内核控制线程,为了管理方便,这些用户线程一般属于一
个进程。运行在该进程的用户空间,对这些线程的调度和管理也是在该进程的用户空间中完
成。当用户需要访问内核时,才将其映射到一个内核控制线程上,但每次只允许一个线程进
程映射。
该模型的主要优点是开销小,效率高,但当一个线程在访问内核时发生阻塞,则整个进程都
会被阻塞,而且多处理机系统中,一个进程的多个线程无法实现并行。
(3)多对多模型
该模型结合以上两种模型的优点,将多个用户线程映射到多个内核控制线程,内核控制线程
的数目可以根据应用和系统的不同而变化。

思考

在一对一和多对一模型下,如果内核线程阻塞,那么用户线程肯定也会被阻塞,特别是多对一,
会导致一组线程都会被阻塞,所以才会有第三种模型的产生。但是内核线程阻塞后,用户线程
是如何能够转到另一个线程运行的?
这个问题的解决就是利用调度程序激活机制,字面理解,就是内核线程阻塞时,内核会主动通知
运行时系统有阻塞发生,运行时系统可以重新调度其他线程进行执行。
实际的工作思路也是差不多的,当内核了解到一个线程被阻塞之后(例如,由于执行了一个阻塞
系统调用或者产生了一个页面故障),内核通知该进程的运行时系统,并且在堆栈中以参数形式
传递有问题的线程编号和所发生事件的一个描述。内核通过在一个已知的起始地址启动运行时系
统,从而发出了通知,这是对UNIX中信号的一个粗略模拟,这个机制称为上行调用(upcall)。

flask-web的思维导图

发表于 2018-05-20 | 更新于: 2018-06-01 | 分类于 flask-web
字数统计: 26 | 阅读时长 ≈ 1

简单展示flask-web的框架,后面会用详细的文章来阐述这幅图。

Quick Start

flask

learner66

learner66

有时候,觉得思考是最美好的事情

9 日志
5 分类
14 标签
RSS
0%
© 2018 learner66