系统调用

系统调用

丶灬走出姿态 提交于 2019-12-25 02:14:05
Linux内核中设置了一组用于实现各种系统功能的子程序,称为系统调用。系统调用实际上是内核中的一些C函数,它们都以sys开头的,如sys_mkdir()。它们通过一个指令int 0x80(软中断)把控制权交给内核,即进入特权级执行。int 0x80指令会使“执行”跳转到系统调用在内核中定义的入口地址。这个位置是唯一确定的,且只可被用户进程读,不可写,这正是利用了“陷阱们”跳转的优点。 进程可以跳转到的内核在的位置叫做system_call。它通过查找系统调用表sys_call_table,找到希望调用的内核函数的地址,调用此函数后返回。Linux里面的每个系统调用是由一些宏,一张系统调用表,一个系统调用入口来完成的。 设定0x80号中断 系统启动后,初始化工作中较重要的一部分在start_kernel()函数中进行,函数start_kernel调用了函数trap_init并设置了各种中断服务程序入口。与系统调用相关的是宏set_system_gate(0x80,&system_call),set_system_gate函数调用层次图如下: 宏_set_gate()的作用是把addr地址值放入gate_addr所指向的内存单元中,使中断向量表中的0x80项保存中断服务程序system_call的入口地址。 系统调用现场保护 系统调用表sys_call_table,部分列出如下:

系统调用

帅比萌擦擦* 提交于 2019-12-25 02:12:54
glibc 对系统调用的封装   在用户态进程里调用open函数 【 int open(const char *pathname, int flags, mode_t mode) 】 在glibc 源代码中有个文件 syscalls.list , 里面咧着所有glibc 的函数对应的系统调用。 另外还有一个脚本 make-syscall.sh ,可根据配置文件,对于每个封装好的系统调用,生成一个文件。 另外还有一个文件 syscall-template.S 使用上面这个宏,定义系统调用的调用方式。 对于任何一个系统调用,会调用 DO_CALL 。 这个宏32位和64位定义不同。 在源代码注释中, int 就是interrupt 。中断的意思。 int $0x80 就是触发一个软中断,通过它就可以陷入(trap) 内核。   在内核启动时,其中会有一个软中断的陷入门,当接收到一个系统调用的时候,相应的文件就会被调用,然后通过 push 和 SAVE_ALL 将当前用户态的寄存器,保存在 pt_regs 结构中。 进入内核前,会保持所有的寄存器,然后调用 do_syscall_32_irqs_on 。在这里,将系统调用号从eax里面取出来,然后根据系统调用号,在系统调用表中找到相应的函数进行调用。并将找出来的参数取出来,作为函数参数。且参数对于的寄存器与linux 的注释是一样的。

Linux内核分析— —创建新进程的过程

只谈情不闲聊 提交于 2019-12-22 17:56:49
分析Linux内核创建一个新进程的过程 实验过程 要求:使用gdb跟踪分析一个fork系统调用内核处理函数sys_clone ,验证对Linux系统创建一个新进程的理解,推荐在实验楼Linux虚拟机环境下完成实验。 cd LinuxKernel qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S 课程内容 阅读理解task_struct数据结构: 进程控制块PCB——task_struct 1、操作系统的三大管理功能包括:   (1)进程管理   (2)内存管理   (3)文件系统 2、PCB task_struct中包含:   (1)进程状态   (2)进程打开的文件   (3)进程优先级信息 3、通过唯一的进程标识PID来区别每个进程。 4、进程状态转化 (1)创建新进程后实际的状态是TASK_RUNNING,就绪但是没有运行,调度器选择一个task之后进入运行态,也叫TASK_RUNNING。 (2)当进程是TASK_RUNNING时,代表这个进程是可运行的,至于它有没有真的在运行,取决于它有没有获得cpu的控制权,即有没有在cpu上实际的运行。 (3)一个正在进行的进程调用do_exit(),进入TASK_ZOMBIE,进程被终止,“僵尸进程”。 (4)等待特定时间或者资源的时候

零拷贝

大城市里の小女人 提交于 2019-12-22 16:40:58
1.1 Linux的普通I/O过程 这是一个从磁盘文件中读取并且通过Socket写出的过程,对应的系统调用如下。 read(file, tmp_buf, len); write(socket, tmp_buf, len); 1. 程序使用read()系统调用,系统由用户态转换为内核态,磁盘中的数据由DMA(Direct memory access)的方式读取到内核读缓冲区(kernel buffer)。DMA过程中CPU不需要参与数据的读写,而是DMA处理器直接将硬盘数据通过总线传输到内存中。 2. 系统由内核态转为用户态,当程序要读的数据已经完全存入内核读缓冲区以后,程序会将数据由内核读缓冲区,写入到用户缓冲区,这个过程需要CPU参与数据的读写。 3. 程序使用write()系统调用,系统由用户态切换到内核态,数据从用户缓冲区写入到网络缓冲区(Socket Buffer),这个过程需要CPU参与数据的读写。 4. 系统由内核态切换到用户态,网络缓冲区的数据通过DMA的方式传输到网卡的驱动(存储缓冲区)中(protocol engine) 可以看到,普通的拷贝过程经历了 四次内核态和用户态的切换 (上下文切换), 两次CPU从内存中进行数据的读写过程 ,这种拷贝过程相对来说比较消耗系统资源。 1.2 内存映射方式I/O tmp_buf = mmap(file, len);

操作系统

你。 提交于 2019-12-22 05:39:30
为何要有操作系统 程序员无法把所有的硬件操作细节都了解到,因为管理这些硬件并加以优化使用是非常繁琐的工作,这个繁琐的工作就是操作系统来干的,有了他,程序员就从这些繁琐的工作中解脱出来,只需要考虑自己的应用软件的编写就可以了,应用软件直接使用操作系统提供的功能来间接使用硬件 操作系统的位置 操作系统位于计算机硬件与应用软件之间,本质也是一个软件。操作系统由操作系统的内核(运行于内核态,管理硬件资源)以及系统调用(运行于用户态,为应用程序员写的应用程序提供系统调用接口)两部分组成 操作系统的功能 隐藏了丑陋的硬件调用接口,为应用程序员提供调用硬件资源更好,更简单,更清晰的模型(系统调用接口)应用程序员有了这些接口后,就不用再考虑操作硬件的细节,专心开发自己的应用程序即可 将应用程序对硬件资源的竞态请求变的有序化(多路复用) 操作系统的发展 第一代(1940~1955)真空管和穿孔卡片 特点:没有操作系统的概念,所有的程序设计都由纯粹的机器语言编写 工作过程:程序员在墙上的机时表预约一段时间,然后程序员拿着他的插件板到机房里,将自己的插件版接到计算机里,这几个小时内他独享整个计算机的资源,后面的一批人都得等着(2万多个真空管经常有被烧坏的情况出现),后来出现了穿孔卡片,可以将程序写在卡片上,然后读入机而不用插件板 优点:程序员在申请的时间段里独享整个资源,即时调整自己的程序

nachos操作系统(七)

别来无恙 提交于 2019-12-21 01:16:55
本节实现重新写内存分配管理。主要有两个知识点,一个是内存管理,一个是系统调用,现在先实现内存管理,然后实现系统调用,预计至多4个课时。 预计内存管理就是写比较简单的,系统调用都是这样预见的。 要实现内存管理,就要实现多道程序并行运行,不然内存管理没法测试,因此要先实现Exec。 userprog/syscall.h里面写的系统调用号为: /* system call codes -- used by the stubs to tell the kernel which system call * is being asked for */ #define SC_Halt 0 #define SC_Exit 1 #define SC_Exec 2 #define SC_Join 3 #define SC_Create 4 #define SC_Open 5 #define SC_Read 6 #define SC_Write 7 #define SC_Close 8 #define SC_Fork 9 #define SC_Yield 10 而且在这个文件里,包括exec,join,fork,read等等耳熟能详的函数,系统调用接口,不过这是头文件,并没有它的详细实现,并且没有对应的源文件,大概率是让我们实现这个源文件。 文档中说,进程开启入口写在userprog

僵尸进程的产生和避免,如何kill杀掉linux系统中的僵尸defunct进程

百般思念 提交于 2019-12-21 00:30:07
在 Unix系统管理中,当用ps命令观察进程的执行状态时,经常看到某些进程的状态栏为defunct,这就是所谓的“僵尸”进程。“僵尸”进程是一个早已 死亡的进程,但在进程表(processs table)中仍占了一个位置(slot)。由于进程表的容量是有限的,所以,defunct进程不仅占用系统的内存资源,影响系统的性能,而且如果其数 目太多,还会导致系统瘫痪。 我们知道,每个Unix进程在进程表里都有一个进入点(entry),核心程序执行该进程时使用到的一切信息都存储在进入点。当用ps命令察看系统中的进程信息时,看到的就是进程表中的相关数据。当以fork()系统调用建立一个新的进程后,核心进程就会在进程表中给这个新进程分配一个进入点,然后将相关信息存储在该进入点所对应的进程表内。这些信息中有一项是其父进程的识别码。当这个进程走完了自己的生命周期后,它会执行exit()系统调用,此时原来进 程表中的数据会被该进程的退出码(exit code)、执行时所用的CPU时间等数据所取代,这些数据会一直保留到系统将它传递给它的父进程为止。由此可见,defunct进程的出现时间是在子进 程终止后,但是父进程尚未读取这些数据之前。 defunct进程是不能直接kill -9杀掉的,否则就不叫僵尸进程了。 知道了defunct进程产生的原因,就可以轻易的kill掉defunct进程。 方法有二:

系统调用wait()

与世无争的帅哥 提交于 2019-12-21 00:21:15
  进程一旦调用了 wait,就 立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出 ,如果让它找到了这样一个已经变成僵尸的子进程,wait 就会收集这个子进程的信息, 并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。 头文件   #include<sys/types.h>   #include<sys/wait.h> 定义函数   pid_t wait (int * status); 函数说明   wait()会暂时停止目前进程的执行,直到有信号来到或子进程结束。如果在调用wait()时子进程已经结束,则wait()会立即返回子进程结束状态值。子进程的结束状态值会由参数status 返回,而子进程的进程识别码也会一快返回。如果不在意结束状态值,则参数status 可以设成NULL。子进程的结束状态值请参考waitpid()。 返回值   如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因存于errno 中。 范例一 #include<stdlib.h> #include<unistd.h> #include<sys/types.h> #include<sys/wait.h> int main() { pid_t pid; int status,i; if(fork()= =0){

Linux 创建子进程执行任务

这一生的挚爱 提交于 2019-12-21 00:17:58
Linux 操作系统紧紧依赖进程创建来满足用户的需求。例如,只要用户输入一条命令,shell 进程就创建一个新进程,新进程运行 shell 的另一个拷贝并执行用户输入的命令。Linux 系统中通过 fork/vfork 系统调用来创建新进程。本文将介绍如何使用 fork/vfork 系统调用来创建新进程并使用 exec 族函数在新进程中执行任务。 fork 系统调用 要创建一个进程,最基本的系统调用是 fork: # include <unistd.h> pid_t fork(void); pid_t vfork(void); 调用 fork 时,系统将创建一个与当前进程相同的新进程。通常将原有的进程称为父进程,把新创建的进程称为子进程。子进程是父进程的一个拷贝,子进程获得同父进程相同的数据,但是同父进程使用不同的数据段和堆栈段。子进程从父进程继承大多数的属性,但是也修改一些属性,下表对比了父子进程间的属性差异: 继承属性 差异 uid,gid,euid,egid 进程 ID 进程组 ID 父进程 ID SESSION ID 子进程运行时间记录 所打开文件及文件的偏移量 父进程对文件的锁定 控制终端 设置用户 ID 和 设置组 ID 标记位 根目录与当前目录 文件默认创建的权限掩码 可访问的内存区段 环境变量及其它资源分配 下面是一个常见的演示 fork 工作原理的 demo

进程的创建与描述

ぐ巨炮叔叔 提交于 2019-12-21 00:16:30
陈民禾——原创作品转载请注明出处—— 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.复习上周内容 上周主要学习了Linux中的系统调用的过程,如图所示就是系统调用的大致过程: 一.关于进程调度的一些基本概念 fork(): 进程是处于执行期的程序以及相关资源的总称,进程在创建它的时候开始存活,在Linux系统中。这通常是调用fork()系统的结果,该系统调用通过复制一个现有进程来创建一个全新的进程,调用fork()的进程成为父进程,新产生的进程称之为子进程,在调用结束时,在返回点这个相同位置上,父进程恢复执行,子进程开始执行,fork()系统从内核返回两次:一次返回到父进程,另一次回到新的子进程。其中fork()实际上是由clone()系统调用实现的。 exec(): 创建新的进城之后会立即执行新的进程,接着调用exec()这组函数就可以创建新的地址空间,并把新的地址空间载入其中。 进程描述符: 内核把进程的列表存放在叫做任务队列的双向循环列表中,链表中的类型都是task_struct、成为进程描述符的结构,进程描述符包含一个具体进程的所有信息,能完整的描述一个正在执行的程序:它打开的文件,进程的地址空间,挂起的信号,进程的状态,还有其它的更多信息。 thread_info