标签:
Linux学习总结
结束了一学期的linux学习后我收获非常的丰富,在这次学习中,我们尝试了一种全新的学习方式,linux翻转课堂,每周都上到mooc网站上跟着老师讲解的视频学习并进行实验,一点一点的慢慢对linux这个操作系统有了了解,同时也掌握了一些基本的命令。每一个单元的内容我都有了自己的理解与收获。在Linux内核这几周的学习中,我受益匪浅,了解到了很多之前不曾了解的知识。
关于计算机是如何工作的:
计算机系统由硬件系统和软件系统两大部分组成。冯·诺依曼(John von Neumann)奠定了现代计算机的基本结构,这一结构又称冯·诺依曼结构,其特点是:
1)使用单一的处理部件来完成计算、存储以及通信的工作。
2)存储单元是定长的线性组织。
3)存储空间的单元是直接寻址的。
4)使用低级机器语言,指令通过操作码来完成简单的操作。
5)对计算进行集中的顺序控制。
6)计算机硬件系统由运算器、存储器、控制器、输入设备、输出设备五大部件组成并规定了它们的基本功能。
7)采用二进制形式表示数据和指令。
我认为计算机在运行时,先从内存中取出第一条指令,然后按照指令的要求按,从存储器中取出数据进行指定的运算和逻辑操作等加工,然后再按地址把结果送到内存中去。接下来,再取出第二条指令,在控制器指挥下完成规定操作,就相当于C语言当中的for循环,依此进行下去。直至遇到停止指令。
关于操作系统是如何工作的:
当一个进程在执行一个程序的时候,进来一个中断,CPU首先将当前EIP,ESP压入内核 栈,然后把ESP指向内核栈,EIP指向中断处理的入口,其中最关键的就是TSS,因为TSS中保存了用户态下各个寄存器的信息,所以将用户态下的EIP 和ESP入栈,就相当于对进程之前的一个状态进行保存,以便于从中断返回后继续执行之前任务,然后kernel调用SAVE_ALL来将其他寄存器信息保 存在栈中,接着根据CS EIP指向的中断程序,对中断进行处理。当执行完一个中断后,会通过schedule进行调度,其中最重要的要数switch to,其会调用 switch to函数来进行调度。当中断执行完后,若要返回到用户态,就需要回到最初中断开始的地方继续执行先前任务,这就需要 restore all将之前保存在内核栈中的值出栈,然后通过iret恢复其计算器的状态,这样我们就又回到了用户态,继续执行之前因中断而暂停的任务。
我认为操作系统工作的核心是进程,而许多操作系统都有自己的核心进程,进程是程序的动态执行。系统最重要的操作就是完成核心进程,而其他的程序都是其核心进程的衍生物,其可以控制衍生进程的进程启动及切换甚至可以终止进程,当核心系统结束,将影响整个操作系统的运行。
关于Linux系统的启动过程:
首先找到main.c中start kernel,不管分析内核的哪一个部分都要调用start kernel.
Trap init中arch/x86,设置了很多中断,而系统调用也是一种中断。Init process是Linux系统中的一号进程,当系统中没有进程需要执行的时候就会调度到idle进程,start kernel从内核一开始启动就会一直存在,这便是0号进程,随后0号进程创建了1号进程即init,于是系统便启动起来了。这是内核的启动过程。init_task是进程0使用的进程描述符,也是Linux系统中第一个进程描述符,该进程的描述符在arch/powerpc/kernel/init_task.c中定义,代码片段如下:struct task_struct init_task = INIT_TASK(init_task);init_task描述符使用宏INIT_TASK对init_task的进程描述符进行初始化,宏INIT_TASK在include/linux/init_task.h文件中,init_task是Linux内核中的第一个线程,它贯穿于整个Linux系统的初始化过程中,该进程也是Linux系统中唯一一个没有用kernel_thread() 函数创建的进程,在init_task进程执行后期,它会调用kernel_thread()函数创建第一个核心进程kernel_init,同时init_task进程继续对Linux系统初始化。在完成初始化后,init_task会退化为cpu_idle进程,当Core 0的就绪队列中没有其它进程时,该进程将会获得CPU运行。新创建的1号进程kernel_init将会逐个启动次CPU,并最终创建用户进程。
关于系统调用的方法:
用户程序------>C库(即API):INT 0x80 ----->system_call------->系统调用服务例程-------->内核程序
先说明一下,我们常说的用户API其实就是系统提供的C库。
系统调用是通过软中断指令 INT 0x80 实现的,而这条INT 0x80指令就被封装在C库的函数中。
(软中断和我们常说的硬中断不同之处在于,软中断是由指令触发的,而不是由硬件外设引起的。)
INT 0x80 这条指令的执行会让系统跳转到一个预设的内核空间地址,它指向系统调用处理程序,即system_call函数。
(注意:!!!系统调用处理程序system_call 并不是系统调用服务例程,系统调用服务例程是对一个具体的系统调用的内核实现函数,而系统调用处理程序是在执行系统调用服务例程之前的一个引导过程,是针对INT 0x80这条指令,面向所有的系统调用的。简单来讲,执行任何系统调用,都是先通过调用C库中的函数,这个函数里面就会有软中断 INT 0x80 语句,然后转到执行系统调用处理程序 system_call ,
system_call 再根据具体的系统调用号转到执行具体的系统调用服务例程。)
system_call函数通过系统调用号查找系统调用表sys_call_table!软中断指令INT 0x80执行时,系统调用号会被放入 eax 寄存器中,system_call函数可以读取eax寄存器获取,然后将其乘以4,生成偏移地址,然后以sys_call_table为基址,基址加上偏移地址,就可以得到具体的系统调用服务例程的地址了!
然后就到了系统调用服务例程了。需要说明的是,系统调用服务例程只会从堆栈里获取参数,所以在system_call执行前,会先将参数存放在寄存器中,system_call执行时会首先将这些寄存器压入堆栈。system_call退出后,用户可以从寄存器中获得(被修改过的)参数。
另外:系统调用通过软中断INT 0x80陷入内核,跳转到系统调用处理程序system_call函数,然后执行相应的服务例程。但是由于是代表用户进程,所以这个执行过程并不属于中断上下文,而是进程上下文。因此,系统调用执行过程中,可以访问用户进程的许多信息,可以被其他进程抢占,可以休眠。
当系统调用完成后,把控制权交回到发起调用的用户进程前,内核会有一次调度。如果发现有优先级更高的进程或当前进程的时间片用完,那么会选择优先级更高的进程或重新选择进程执行。
关于system-call中断处理过程:
系统调用是通过软中断指令 INT 0x80 实现的,而这条INT 0x80指令就被封装在C库的函数中。INT 0x80 这条指令的执行会让系统跳转到一个预设的内核空间地址,它指向系统调用处理程序,即system_call函数。系统调用处理程序system_call 并不是系统调用服务例程,系统调用服务例程是对一个具体的系统调用的内核实现函数,而系统调用处理程序是在执行系统调用服务例程之前的一个引导过程,是针对INT 0x80这条指令,面向所有的系统调用的。简单来讲,执行任何系统调用,都是先通过调用C库中的函数,这个函数里面就会有软中断 INT 0x80 语句,然后转到执行系统调用处理程序 system_call ,system_call 再根据具体的系统调用号转到执行具体的系统调用服务例程。
system_call函数通过系统调用号查找系统调用表sys_call_table!软中断指令INT 0x80执行时,系统调用号会被放入 eax 寄存器中,system_call函数可以读取eax寄存器获取,然后将其乘以4,生成偏移地址,然后以sys_call_table为基址,基址加上偏移地址,就可以得到具体的系统调用服务例程的地址了!
然后就到了系统调用服务例程了。需要说明的是,系统调用服务例程只会堆栈里获取参数,所以在system_call执行前,会先将参数存放在寄存器中,system_call执行时会首先将这些寄存器压入堆栈。system_call退出后,用户可以从寄存器中获得(被修改过的)参数。
另外:系统调用通过软中断INT 0x80陷入内核,跳转到系统调用处理程序system_call函数,然后执行相应的服务例程。但是由于是代表用户进程,所以这个执行过程并不属于中断上下文,而是进程上下文。因此,系统调用执行过程中,可以访问用户进程的许多信息,可以被其他进程抢占,可以休眠。
当系统调用完成后,把控制权交回到发起调用的用户进程前,内核会有一次调度。如果发现有优先级更高的进程或当前进程的时间片用完,那么会选择优先级更高的进程或重新选择进程执行
关于Linux内核创建一个新进程的过程
在Linux中创建一个新进程的唯一方法是使用fork函数,fork()执行一次但有两个返回值。
在父进程中,返回值是子进程的进程号;在子进程中,返回值为0。因此可通过返回值来判断当前进程是父进程还是子进程。
在fork的最后是将任务设置成了就绪状态,由于fork()是一个系统调用,在系统调用部分system_call.s,可以看到在系统函数返回后,会调用调度函数schedule(),在schedule()中,就会检测到新进程的就绪状态,并用switch_to()切换到新进程进行执行。
使用fork函数得到的子进程是父进程的一个复制品,它从父进程处复制了整个进程的地址空间,包括进程上下文,进程堆栈,内存信息,打开的文件描述符,信号控制设定,进程优先级,进程组号,当前工作目录,根目录,资源限制,控制终端等。而子进程所独有的只是它的进程号,资源使用和计时器等。可以看出,使用fork函数的代价是很大的,它复制了父进程中的代码段,数据段和堆栈段里的大部分内容,使得fork函数的执行速度并不快。由 fork() 系统调用创建的新进程被称为子进程。该函数被调用一次,但返回两次。如果 fork()进程调用成功,两次返回的区别是子进程的返回值是0,而父进程的返回值则是新子进程的进程号
关于Linux如何装载和启动一个可执行程序
在linux首先创建父进程,然后通过调用fork()系统调用创建一个新的进程,然后新的进程调用execve()系统调用执行指定的ELF文件。主进程继续返回等待新进程执行结束,然后重新等待用户输入命令。execve()系统调用被定义在unistd.h,它的原型如下:
int execve(const char *filenarne, char *const argv[], char *const envp[]);
它的三个参数分别是被执行的程序文件名、执行参数和环境变最。Glibc对execvp()系统调用进行了包装,提供了execl(), execlp(), execle(), execv()和execvp()等5个不同形式的exec系列API,它们只是在调用的参数形式上有所区别,但最终都会调用到execve()这个系统中。
调用execve()系统调用之后,再调用内核的入口sys_execve()。 sys_execve()进行一些参数的检查复制之后,调用do_execve()。因为可执行文件不止ELF一种,还有java程序和以“#!”开始的脚本程序等,所以do_execve()会首先检查被执行文件,读取前128个字节,特别是开头4个字节的魔数,用以判断可执行文件的格式。如果是解释型语言的脚本,前两个字节“#!"就构成了魔数,系统一旦判断到这两个字节,就对后面的字符串进行解析,以确定程序解释器的路径。环境下,可执行文件是以ELF格式存在的,文件头部标明了文件在加载到内存中需要的相关信息,随后的部分是以段的形式存在的代码和数据,段的划分主要依据加载到内存中的读写属性。系统调用execve负责可执行文件的调度工作,先进行相关参数的传递和调用前环境的处理,然后加载可执行文件的信息,查找相应的可执行文件解析模块,对于ELF格式的可执行文件,按照格式要求加载到内存中相应的地址空间,如果是静态链接的就将文件头部标明的入口地址作为开始;如果是依赖动态链接库的可执行文件则需要将动态链接器ld的入口地址作为开始。execve是一个特殊的系统调用,在子进程总fork返回到一个特定的点,当前程序执行到execve时陷入到内核态,当execve返回时返回的是一个新的可执行程序的执行起点,shell环境执行execve,当系统调用陷入内核中时,调用evecve,do execve,根据可执行文件加载头部,在链表当中寻找能够解析的内核模块
我觉得在学习linux的过程中,明白了一个道理,那就是linux的用户权限很大,做任何事情都很自由,所以你必须知道自己做的每一步在干什么,因为黑客会利用linux的漏洞来获取信息。比如缓冲区溢出就这个实验,缓冲区溢出是指当计算机向缓冲区内填充数据位数时超过了缓冲区本身的容量溢出的数据覆盖在合法数据上,理想的情况是程序检查数据长度并不允许输入超过缓冲区长度的字符,但是绝大多数程序都会假设数据长度总是与所分配的储存空间想匹配,这就为缓冲区溢出埋下隐患。操作系统所使用的缓冲区 又被称为"堆栈".,在各个操作进程之间,指令会被临时储存在"堆栈"当中,"堆栈"也会出现缓冲区溢出 。缓冲区溢出通过往程序的缓冲区写超出其长度的内容,造成缓冲区的溢出,从而破坏程序的堆栈,使程序转而执行其它指令,以达到攻击的目的。缓冲区溢出是指程序试图向缓冲区写入超出预分配固定长度数据的情况。这一漏洞可以被恶意用户利用来改变程序的流控制,甚至执行代码的任意片段。这一漏洞的出现是由于数据缓冲器和返回地址的暂时关闭,溢出会引起返回地址被重写。
Linux这个操作系统是一个非常聪明的系统,但同时它又存在很多系统漏洞,比如Set-UID ,它是一个重要的安全机制。当一个Set-UID程序运行的时候,它被假设为具有拥有者的权限,任何人运行这个程序时都会获得程序拥有者的权限,同时就给黑客们创造了入侵盗取信息的条件。这就需要我们在以后的学习当中不断学习不断突破,而我对于linux的了解远远不足,对细节方面了解还不够透彻,希望在以后的学习生活中,对linux内核有更深入的了解,同时要多涉猎信息安全的相关知识,感觉只有学习了信息安全,才能升入的把握程序内在的各种机制。从攻击的角度能更加细致的看待编程。因为只有懂得了程序正常工作的逻辑,才能从供给的角度对其进行破坏。因此更多的夯实基础,才是真正掌握信息安全的正确途径。不可能一簇而就,没有好的基础就能学习好信息安全的知识。总之这学期的linux内核这门课程的学习给了我全新的体验与收获,感谢老师!
标签:
原文地址:http://www.cnblogs.com/chenyueyi/p/4569571.html