2017-2018-1 20155336 《信息安全系统设计基础》第十三周学习总结
找出全书你认为最重要的一章,深入重新学习一下
- 完成这一章所有习题
- 详细总结本章要点
- 给你的结对学习搭档讲解你的总结并获取反馈
重温第八章
教材学习内容详细总结
8.1异常
- 异常就是控制流中的突变,用来响应处理器状态中的某些变化
8.1.1 异常处理
-
系统中可能的每种类型的异常都分配了一个唯一的非负整数的异常号。
-
处理器:被零除、缺页、存储器访问违例、断点以及算术溢出
-
操作系统:系统调用和来自外部I/O设备的信号
8.1.2异常的类别
-
中断 :来自I/O设备的信号,异步,总是返回到下一条指令
-
陷阱 :有意的异常,同步,总是返回到下一条指令
-
故障 :潜在可恢复的错误,同步,可能返回到当前指令
-
终止 :不可恢复的错误,同步,不会返回
8.1.3Linux系统中的异常
-
Linux故障和终止 :
-
除法错误:异常号:0,通常报告为“浮点异常”
-
一般保护故障:异常号:13,Linux不会尝试回复这类故障,通常报告为“段故障”
-
缺页:异常号:14,是会重新执行产生故障的指令的一个异常事例
-
机器检查:异常号:18,从不返回控制给应用程序
-
-
Linux系统调用:
-
每个系统调用都有一个唯一的整数号,对应于一个到内核中跳转表的偏移量
-
将系统调用和与他们相关的包装函数称为系统级函数
-
8.2进程
-
异常是允许操作系统提供进程的概念所需要的基本构造块。
- 进程:一个执行中的程序的实例。上下文是由程序正确运行所需要的状态组成的,这个状态包括存放在存储器中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
-
进程提供给应用程序的关键抽象:
- 一个独立的逻辑控制流,独占地使用处理器;
- 一个私有的地址空间,独占地使用存储器系统。
8.2.1逻辑控制流
- 程序计数器:唯一的对应于包含在程序的可执行目标文件中的指令,或者是包含在运行时动态链接到程序的共享对象中的指令。这个PC值的序列叫做逻辑控制流,简称逻辑流。
8.2.2并发流
- 并发流:一个逻辑流的执行在时间上与另一个流重叠。
并发:多个流并发地执行的一般现象。
多任务:一个进程和其他进程轮流运行的概念。
时间片:一个进程执行它的控制流的一部分的每一时间段。
多任务也叫时间分片。
- 并行流:如果两个流并发的运行在不同的处理器核或者计算机上。
8.2.3私有地址空间
- 这个空间中某个地址相关联的那个存储器字节是不能被其他进程读或者写的
8.2.4用户模式和内核模式
-
当设置了模式位时,进程就运行在内核模式中
-
没有设置模式位时,进程就运行在用户模式中。用户模式中的进程不允许执行特权指令。
-
进程从用户模式变位内核模式的唯一方法是通过诸如中断、故障或者陷入系统调用这样的异常。
-
/proc文件系统允许用户模式进程访问内核数据结构的内容
8.2.5上下文切换
-
上下文切换:是较高形式的异常控制流来实现多任务
-
调度:在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程。这种决定叫做调度
-
当内核代表用户执行系统调用时,可能会发生上下文切换。如果系统调用因为等待某个事件发生阻塞,那么内核可以让当前进程休眠,切换到另一个进程
-
中断也可能引发上下文切换
8.3系统调用错误处理
-
定义错误报告函数,简化代码
-
错误处理包装函数
8.4进程控制
8.4.1获取进程ID
-
每个进程都有一个唯一的正数的进程ID。
-
getpid函数返回调用进程的PID,getppid函数返回它的父进程的PID。上面两个函数返回一个同类型为pid_t的整数值,在linux系统中,它在types.h中被定义为int。
8.4.2创建和终止进程
- 进程总处于三种状态
(1)运行:进程要么在CPU上执行,要么在等待被执行且最终会被内核调度。
(2)停止:程序的执行被挂起,,且不会被调度。
(3)终止:进程用永远停止了。
- 终止原因:
(1)收到一个信号,默认行为是终止进程
(2)从主进程返回
(3)调用exit函数
-
父进程通过调用fork函数创建一个新的运行的子进程。
-
子进程和父进程的异同:
-
异:有不同的PID
-
同:用户级虚拟地址空间,包括:文本、数据和bss段、堆以及用户栈。任何打开文件描述符,子进程可以读写父进程中打开的任何文件。
-
-
fork函数: 因为父进程的PID总是非零的,返回值就提供一个明确的方法来分辨程序是在父进程还是在子进程中执行。
-
fork函数的特点:
(1)调用一次,返回两次
(2)并发执行
(3)相同的但是独立的地址空间
(4)共享文件
8.4.3回收子进程
-
当父进程回收已终止的子进程时,内核将子进程的退出状态传递给父进程,然后抛弃已终止的进程。
一个终止了但还未被回收的进程称为僵死进程。 -
一个进程可以通过调用waitpid函数来等待它的子进程终止或者停止。
#include <sys/types.h> #include <sys/wait.h> pid_t waitpid(pid_t pid,int *status,int options); //返回:若成功,返回子进程的PID;若WNOHANG,返回0;若其他错误,返回-1。
默认地,当option=0时,waitpid挂起调用进程的执行,直到它的等待集合中的一个子进程终止。
-
判定等待集合的成员
有参数pid来确定的:
(1)pid>0:等待集合是一个单独的子进程,进程ID等于pid。
(2)pid=-1:等待结合就是由父进程所有的子进程组成的。
-
修改默认行为
通过options设置:
(1)WNOHANG:默认行为是挂起调用进程。(2)WUNTRACED:默认行为是只返回已终止的子进程。
(3)WNOHANG|WUNTRACED:立即返回,如果等待集合中没有任何子进程被停止或者已终止,那么返回值为0,或者返回值等于那个被停止或者已经终止的子进程的PID。
-
检查已回收子进程的退出状态
wait.h头文件定义了解释status参数的几个宏:
(1)WIFEXITED:如果子进程通过调用exit或者一个返回正常终止,就返回真;
(2)WEXITSTATUS:返回一个正常终止的子进程的退出状态。只有在WIFEXITED返回真时,才会定义这个状态。
-
错误条件
(1)若调用进程没有子进程,那么waitpid返回-1,并且设置errno为ECHILD;
(2)若waitpid函数被一个信号中断,那么返回-1,并设置errno为EINTR
-
wait函数
#include <sys/types.h>
#include <sys/wait.h>pid_t wait(int *status); //返回:若成功,返回子进程的PID;若错误,返回-1。
调用wait(&status)等价于调用waitpid(-1.&status,0)
8.4.4让进程休眠
-
sleep函数:将进程挂起一段指定的时间
#include <unistd.h>unsigned int sleep(unsigned int secs); //返回:还要休眠的秒数
如果请求的时间量已经到了,返回0,否则返回还剩下的要休眠的秒数。
-
pause函数:让调用函数休眠,直到该进程收到一个信号。
#include <unistd.h>int pause(void); //返回:总是-1
8.4.5加载并运行程序
-
execve函数:在当前进程的上下文中加载并运行一个新程序。
#include <unistd.h>int execve(const char *filename,const char *argv[],const char *envp[]); //返回:若成功,则不返回,若错误,返回-1
filename:可执行目标文件
argv:带参数列表
envp:环境变量列表
特点:execve调用一次从不返回
-
getenv函数:在环境数组中搜素字符串“name =VALUE”,若找到了,就返回一个指向value的指针,否则它就返回NULL。
#include <stdlib.h>
char *getenv(const char *name);
//返回:存在,返回指向name的指针,若无匹配的,为NULL注意:
execve函数在当前进程的上下文中加载并运行一个新的进程。它会覆盖当前进程的地址空间,并没有创建一个新的进程,新的进程仍然有相同的PID,并且继承了调用execve函数时已打开的所有文件描述符。
8.4.6利用fork和execve运行程序
-
外壳是一个交互型的应用级程序,它代表用户运行其他程序。
-
外壳执行一系统的读/求值步骤,然后终止。读步骤读取来自用户的一个命令行,求值步骤解释命令行,并代表用户运行程序。
-
eval函数:对外壳命令行求值
-
parseline函数:解析外壳的一个输入
8.5信号
-
Unix信号:更高层的软件形式的异常,允许进程中断其他进程
-
P505:30种不同类型的信号表
8.5.1信号术语
-
发送信号:
1)内核检测到一个系统事件2)一个进程调用了kill函数
-
接收信号:当目的进程被内核强迫以某种方式对发送的信号作出反应时,目的进程就接收了信号
-
待处理信号:一个只发出而没有被接收的信号
8.5.2发送信号
-
进程组
每个进程都只属于一个进程组
一个进程可以通过使用setpigd函数来改变自己或者其他进程的进程组
-
用/bin/kill程序发送信号 •一个为负的PID会导致信号被发送到进程组PID中的每个进程中
-
从键盘发送信号
-
用kill函数发送信号
-
用alarm函数发送信号
8.5.3接收信号
-
如果集合是非空的,那么内核选择集合中的某个信号k,并且强制p接收信号k
-
signal函数可以改变和信号signum相关联的行为
8.5.4信号处理问题
-
捕获多个信号时的问题:
待处理信号被阻塞 待处理信号不会排队等待 系统调用可以被中断
8.5.5可移植的信号处理
-
不同系统之间,信号处理语义有差异
-
sigaction函数明确地指定它们想要的信号处理语义
8.5.6显式地阻塞和取消阻塞
-
应用程序可以使用sigprocmask函数显式地阻塞和取消阻塞选择的信号
-
sigprocmask函数改变当前已阻塞信号的集合
8.6非本地跳转
-
通过setjmp和longjmp函数来提供
-
setjmp函数只被调用一次,但返回多次:一次是当第一次调用setjmp,而调用环境保存在缓冲区env中时,一次是为每个相应的longjmp调用
-
longjmp只调用一次,但从不返回
-
非本地跳转的一个重要应用就是允许从一个深层嵌套的函数调用中立即返回,通常是由检测到某个错误情况引起的
-
非本地跳转的另一个重要应用是使一个信号处理程序分支到一个特殊的代码位置,而不是返回到达中断了的指令位置
8.7操作进程的工具
- Linux系统提供监控和操作进程的工具:打印一个正在运行的程序和它的子进程调用的每个系统调用的轨迹(STRACE)、列出当前系统中包括僵死进程的进程(PS)、打印出关于当前进程资源使用的信息(TOP)、显示进程的存储器映射(PMAP)、虚拟文件系统(/proc)+
getpid函数:
kill:
- kill()函数不仅可以终止进程(实际上是通过发出SIGKILL信号终止),也可以向进程发送其他信号。
alarm:
- 也称为闹钟函数,它可以在进程中设置一个定时器,当定时器指定的时间到时,它就向进程发送SIGALARM信号。要注意的是,一个进程只能有一个闹钟时间,如果在调用alarm()之前已设置过闹钟时间,则任何以前的闹钟时间都被新值所代替。
signal()、sigaction()
- linux主要有两个函数实现信号的安装:signal()、sigaction()。其中signal()只有两个参数,不支持信号传递信息,主要是用于前32种非实时信号的安装;而sigaction()是较新的函数(由两个系统调用实现:sys_signal以及sys_rt_sigaction),有三个参数,支持信号传递信息,主要用来与 sigqueue() 系统调用配合使用,当然,sigaction()同样支持非实时信号的安装。sigaction()优于signal()主要体现在支持信号带有参数。
eval函数:对外壳命令行求值
exit函数:
parseline函数:解析外壳的一个输入
getenv函数:在环境数组中搜素字符串“name =VALUE”,若找到了,就返回一个指向value的指针,否则它就返回NULL。
#include <stdlib.h>
char *getenv(const char *name);
//返回:存在,返回指向name的指针,若无匹配的,为NULL
**注意:**
execve函数在当前进程的上下文中加载并运行一个新的进程。它会覆盖当前进程的地址空间,并没有创建一个新的进程,新的进程仍然有相同的PID,并且继承了调用execve函数时已打开的所有文件描述符。
execve函数:在当前进程的上下文中加载并运行一个新程序。
#include <unistd.h>
int execve(const char *filename,const char *argv[],const char *envp[]);
//返回:若成功,则不返回,若错误,返回-1
filename:可执行目标文件
argv:带参数列表
envp:环境变量列表
特点:execve调用一次从不返回
setenv函数
pause函数:让调用函数休眠,直到该进程收到一个信号。
#include <unistd.h>
int pause(void);
//返回:总是-1
sleep函数:将进程挂起一段指定的时间
#include <unistd.h>
unsigned int sleep(unsigned int secs);
//返回:还要休眠的秒数
如果请求的时间量已经到了,返回0,否则返回还剩下的要休眠的秒数。
wait函数
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
//返回:若成功,返回子进程的PID;若错误,返回-1。
调用wait(&status)等价于调用waitpid(-1.&status,0)
waitpid函数
教材学习中的问题和解决过程
-
问题1:异步异常和同步异常是如何划分的?
-
问题1解决:教材第八章504页,我了解到异常可以分为以下四类:中断、陷阱、故障、终止。这里提到了“同步异常”和“异步异常”的概念,通过进一步了解,我知道了像“中断”这类由处理器外部的I/O设备中的事件产生的异常属于异步异常,但是对于同步异常的解释“执行一条指令的直接产物”我还是不太理解。通过对陷阱、故障、终止这几类异常的进一步了解,我总结出异步异常是由用户发起系统调用或出现错误引发的,是由执行当前指令的结果。abort例程用于处理终止类异常,可以终止发生异常的应用程序。
-
问题2:进程和并发的概念区分?
-
问题2解决:关于进程,书中提到进程有两个假象:一个独立的逻辑控制流和一个私有的地址空间。通过PC的值我们可以了解到目前是哪个进程的哪个语句正在CPU中执行,关于进程之间的调度方法,我们已经在操作系统这门课中学习过了,这里毋庸赘言。总之,进程是指一个时间段内多个进程一起执行,但其实一个时间点对应的是一个事件。关于并发,与进程最明显的不同是,它的逻辑流不是交替执行的,而是在时间上重叠的,多个流并发执行,即一个时间点多个事件。这里要区分“并发流”和“并行流”两个概念。并发流和并行流都是指两个流在时间上是重叠的。但是并行流是并发流的子集,不管两个流是运行在同一个CPU上还是不同的CPU上,都可以叫做并发,但是只有运行在不同的CPU上,才能叫做并行。
-
问题3:进程创建和控制的系统调用及函数使用fork,exec,wait,waitpid,exit,getpid,getppid,sleep,pause,setenv,unsetenv各自的功能是什么?如何使用?
-
fork:create a child process
即创建一个子进程。man 2 fork
命令查看其帮助文档,如下图所示:
-
通过观察
SYNOPSIS
我们了解到它需要包含的头文件是#include
-
通过观察
RETURN VALUE
我们了解到在父亲进程中其返回值时子进程的PID
,在子进程中其PID
是0。
-
通过观察
SEE ALSO
我们了解到它还与下列函数有关:clone(2)
,execve(2)
,exit(2)
,setrlimit(2)
,unshare(2)
,
vfork(2)
,wait(2)
,daemon(3)
,capabilities(7)
,creden‐tials(7)
-
exec:execute a file
这是C语言中的函数 ,用于执行一个文件。用man 3 exec
来查看其帮助文档:
-
通过观察
SYNOPSIS
我们了解到它需要包含的头文件是:#include
-
通过观察
RETURN VALUE
我们了解到只有在出现错误时该函数才会返回-1
-
通过观察
SEE ALSO
我们了解到除了fork
,它还与下列函数有关:sh(1)
,execve(2)
,execveat(2)
,ptrace(2)
,fexecve(3)
,environ(7)
(3&4)wait&waitpid
:wait for process to change state
等待一个进程去改变当前状态。
-
通过观察
SYNOPSIS
我们了解到它需要包含的头文件是:#include <sys/types.h>;#include <sys/wait.h>
。wait()
和waitpid()
的用法有所不同。wait()
挂起调用进程直到有其子运行终止;waitpid()
挂起调用进程执行直到由PID
参数指定的子状态已更改。
-
通过观察
RETURN VALUE
我们了解到wait()
返回终止子进程的PID
,出错时返回-1
;waitpid()
返回状态改变的子进程的PID
,出错时同样返回-1
。 -
通过观察
SEE ALSO
我们了解到它们还和下列函数有关:exit(2)
,clone(2)
,kill(2)
,ptrace(2)
,sigaction(2)
,signal(2)
,wait4(2)
,pthread_create(3)
,credentials(7)
,signal(7)
(5)exit
:terminate the calling process
终止当前唤起的进程。man 2 exit
查看帮助文档后,如下图所示:
-
通过观察
SYNOPSIS
我们了解到它需要包含的头文件是:#include
-
这个系统调用没有返回值。
.
-
通过观察
SEE, ALSO
我们了解到除了上述函数它还和下列函数有关:exit_group(2)
,kill(2)
,wait4(2)
,atexit(3)
,exit(3)
,on_exit(3)
,termios(3)
-
问题4:linux中的kill 可以向一个进程发送信号,我想知道他是怎么做到的?kill在内核到底是怎么实现的?通过什么方式实现的?
-
问题4解决:
- 系统调用是内核对用户层提供的统一的界面接口
VFS
是在操作系统内部对不同文件系统提供的统一的接口(数据结构),处在系统调用和不同文件系统之间的一个中间层。- 用户调用
read
, 经过VFS
,在统一的数据结构file_operations
中找到不同文件系统真正的read
操作。
-
问题5:
wait
和waitpid
有什么区别? -
问题5解决:
wait()
函数用于使父进程(也就是调用wait()
的进程)阻塞,直到一个子进程结束或者该进程接收到了一个指定的信号为止。如果该父进程没有子进程或者它的子进程已经结束,则wait()
函数就会立即返回。waitpid()
的作用和wait()
一样,但它并不一定要等待第一个终止的子进程(它可以指定需要等待终止的子进程),它还有若干选项,如可提供一个非阻塞版本的wait()
功能,也能支持作业控制。实际上,wait()
函数只是waitpid()
函数的一个特例,在Linux
内部实现wait()
函数时直接调用的就是waitpid()
函数。
上周考试错题总结
-
1、实验5:使用openssl进行混合密码系统加密时,会话秘钥的分发最可能用到(B D)
A AES_encrypt B RSA_public_encrypt C AES_decrypt D RSA_private_decrypt E AES_cbc_encrypt F RSA_private_encrypt G RSA_public_decrypt
-
解析:
非对称算法的应用: 秘钥分发:用对方公钥加密,对方用自己的私钥解密 签名验签:签名主要是用自己私钥加密,对方用自己公钥验签
-
2、实验5:OpenSSL整个软件包三大主要的功能部分有(A B C)
-
解析:
OpenSSL整个软件包大概可以分成三个主要的功能部分: 密码算法库 SSL协议库 应用程序
-
3、实验4中我们实现的测试程序是( B )驱动。
-
解析:
在LINUX里面,设备类型分为:字符设备、块设备以及网络设备, PCI是一种和ISA为一类的总线结构,归属于网络驱动设备~~~ 字符设备、块设备主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般就紧接着发生了,而块设备则不然,它利用一块系统内存作为缓冲区,当用户进程对设备请求能满足用户的要求时,就返回请求的数据,如果不能就调用请求函数来进行实际的I/O操作
-
4、实验2中,SM1_Crypto(miqian, 16, 0, 0, 0,mihou);语句的功能是(B)
-
解析:SM1_Crypto(miqian, 16, 0, 0, 0,mihou);加密。SM1_Crypto(miqian, 16, 1, 0, 0,mihou);解密
-
5、有关套接字接口函数open_clientfd()、open_listenfd(),下面说法正确的是(A C E F )
-
解析:P660
教材课后习题
- 8.1
-
进程
A
和B
是相互并发的,就想B和C,因为他们各自的执行是重叠的,也就是一个进程在另一个进程结束前开始。进程A和C不是并发的,因为它们的执行没有重叠;A
在C
之前就结束了。 -
8.2
-
分析:父子进程执行无关的指令集合。然而,这个程序中,父子进程执行的指令集合是相关的,这是有可能的,因为父子进程有相同的代码段。
-
A:这里的关键点事子进程执行了两个
printf
语句。在fork
返回之后,它执行了第8
行的pringtf
。然后它从if
语句中出来,执行了第9
行的printf
语句。printf1:x=2
;printf2:x=1
; -
B.父进程只执行了第
九
行的printf
:printf2:x=0
; -
8.3列出下面程序所以可能的输出序列:
- 父进程打印
b
,然后是c
。子进程打印a
,然后是c
。意识到你不能对父进程和子进程是如何交错执行的做任何假设是很重要的。因此,任何满足b——>c
和a——c
的拓扑排序都是可能的输出序列。有三个这样的序列:acbc
、abcc
、bacc
、
- 8.4考虑下面的程序
A 这个程序会产生多少输出行?
B 这些输出行的一种可能的顺序是什么?
-
答:
A
. 每次都会运行6个输出行!B
. 输出行的顺序根据系统不同而不同,取决于内核如何交替执行父子进程的指令。unix>./waitprob1 Hello 0 1 Bye 2 Bye
-
8.5
unsigned int snooze(unsigned int secs){
unsigned unt rc = sleep(secs);
printf("Slept for %u of %u secs.\n",secs - rc, secs);
return rc;
}
- 8.6
#include "csapp.h"
int main(int argc, char *argv[], char *envp[])
{
int i;
printf("Command line arguments:\n");
for(i=0;argv[i] != NULL;i++)
printf("argv[%2d]: %s\n",i,argv[i]);
printf("\n");
printf("Environment variables:\n");
for(i=0;envp[i] != NULL;i++)
prrintf("envp[%2d]: %s\n",i,envp[i]);
exit(0);
}
- 8.7
-
分析:只要休眠进程收到一个未被忽视的信号,
sleep
函数就会提前返回。但是,因为收到一个SIGINT
信号的默认行为就是终止进程,我们必须设置一个SIGINT
处理程序来允许sleep
函数返回。处理程序简单地捕获SIGNAL
,并将控制返回给sleep
函数,该函数会立即返回。#include"csapp.h" void handler(int sig) { return; } unsigned int snooze(unsigned int secs){ unsigned int rc = sleep(secs); printf("Slept for %u of %u secs.\n",secs - rc,secs); return rc; } int main(int argc,char **argv){ if(argc != 2){ fprintf(stderr,"usage: %s <secs>\n",argv[0]); exit(0); } if(signal(SIGINT,handler) == SIG_ERR) unix_error("signal error\n"); (void)snooze(atoi(argv[1])); exit(0); }
本周代码托管链接
其他(感悟、思考等,可选)
在我开始写这篇博客的时候,忽然发现时间真的过得好快。转眼间整本书已经学完了,可是总感觉心里空荡荡的,但是相比于之前所学的C语言以及JAVA来说,对于这门课的学习,我感觉还是有所收获的,如计算机中硬件和操作系统是具体如何实现应用的,存储器、处理器、还有机器语言都是如何使用的。在比如虚拟机的使用,用常用的Linux命令去完成一些打开文件,编译运行文件,查看帮助手册,用git托管代码,查看托管日志等简单操作都可以被我灵活掌握。回顾整本书,之所以觉得这一章会如此重要,是因为进程在现代操作系统来说是至关重要的。理解进程的原理对理解计算机系统如何工作有着很大的帮助。通过这次的学习,我对于本章又有了更深刻的了解,并且学到了之前没有学到的知识,感觉受益匪浅。很希望自己再最后这段时间里,能够再接再厉,通过一学期的学习,能够给自己给这门课有一个好的结尾。
本周结对学习情况
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 200/200 | 2/2 | 20/20 | |
第二周 | 300/500 | 2/4 | 18/38 | |
第三周 | 500/1000 | 3/7 | 22/60 | |
第四周 | 600/1300 | 4/9 | 30/90 | |
第五周 | 650/1300 | 5/9 | 40/90 | |
第六周 | 700/1300 | 6/9 | 50/90 | |
第七周 | 800/1300 | 7/9 | 60/90 | |
第八周 | 1200/1700 | 8/10 | 80/110 | |
第九周 | 1800/2000 | 9/11 | 100/120 | |
第十一周 | 2400/3000 | 10/15 | 120/140 | |
第十三周 | 3000/3500 | 11/15 | 140/160 |
尝试一下记录「计划学习时间」和「实际学习时间」,到期末看看能不能改进自己的计划能力。这个工作学习中很重要,也很有用。
耗时估计的公式
:Y=X+X/N ,Y=X-X/N,训练次数多了,X、Y就接近了。
-
计划学习时间:20小时
-
实际学习时间:20小时
(有空多看看现代软件工程 课件
软件工程师能力自我评价表)