全部博文(2759)
分类:
2012-05-21 10:33:17
原文地址:自己整理的linux系统相关知识(1) 作者:yulianliu1218
线程可以通过全局变量来通信,但是进程无法通过全局变量来通信。
线程安全是中的术语,指某个、在环境中被调用时,能够正确地处理各个的,使程序功能正确完成。
一般来说,线程安全的函数应该为每个调用它的线程分配专门的空间,来储存需要单独保存的状态(如果需要的话),不依赖于“”,把多个线程共享的变量正确对待(如,通知编译器该为“易失(volatile)”型,阻止其进行一些不恰当的优化),而且,线程安全的一般不应该修改全局对象。
很多C库代码(比如某些strtok的实现,它将“多次调用中需要保持不变的状态”储存在静态变量中,导致不恰当的共享)不是线程安全的,在环境中调用这些函数时,要进行特别的预防措施,或者寻找别的替代方案。般而言“线程安全”由多线程对共享资源的访问引起。如果调用某个接口时需要我们自己采取同步措施来保护该接口访问的共享资源,则这样的接口不是线程安全的.MFC和STL都不是线程安全的. 怎样才能设计出线程安全的类或者接口呢?如果接口中访问的数据都属于私有数据,那么这样的接口是线程安全的.或者几个接口对共享数据都是只读操作,那么这样的接口也是线程安全的.如果多个接口之间有共享数据,而且有读有写的话,如果设计者自己采取了同步措施,调用者不需要考虑数据同步问题,则这样的接口是线程安全的,否则不是线程安全的。
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。
n
n 一个进程至少需要一个线程作为它的指令执行体。进程需要进行上下文切换,而线程则无需上下文切换。用户态线程减少了多线程的系统开销,实现方式灵活,进程在创建多个用户态线程时不需要linux内核支持,也不直接对cpu标志寄存器进行操作,用户态线程进行调度切换时,不需要进行系统调用。同一进程可创建的线程数没有限制。若某进程的其中一个线程被阻塞,进程会进入睡眠状态,其他线程同时也被阻塞。用户态线程无法发挥多路处理器和多核处理器的优势。而内核态线程是根据cpu硬件的特点以硬件底层模式实现的线程机制,内核态将所有线程按照同一调度算法调度,更有利于发挥多路处理器和多核处理器所支持的并发处理特性。内核态线程可自由访问内存空间,且在某一线程阻塞时其他线程还能正常运行。但是相对于用户态线程,内核态线程的系统开销较大,并且必须通过系统调用实现,对硬件和linux内核版本的依赖性较高,不利于程序移植。
n 1. 系统为每个进程分配一个独立的虚拟地址空间。进程的虚拟地址空间被分做两个部分:
n 用户空间。用户进程本身的程序和数据(可执行映象)映射到用户空间中。
n 系统空间。内核被映射到所有进程的系统空间中。它们只允许在具有较高特权的内核态下访问。进程运行在特权较低的用户态下时,不允许它直接访问系统空间。进程只能通过系统调用(system call)转换为内核态后,才能访问系统空间。一个进程在运行过程中,总是在两种执行状态之间不断地转换
n task_struct容纳了一个进程的所有信息,我们主要对如下几个方面的信息进行介绍。
n (1) 进程的状态和标志信息
n (2) 进程的调度信息
n (3) 进程的标识信息
n (4) 进程间通信信息
n (5) 进程的家族关系
n (6) 时间和定时信息
n (7) 文件系统信息
n (8) 存储管理信息
n (9) CPU现场保留信息
n 每个进程分为内核态(特权级0)和用户态(特权级3)两种级别。
n 2.4.0版本中,每个task_struct 结构占1680字节。
n 系统中的最大进程数由系统的物理内存大小决定。
n 进程间通信的目的主要有以下几个:
n 传输信息
n 共享数据
n 通知事件
n 同步互斥
n 控制进程
无名管道:ls|wc 只存在于内存中
关于inline,小弟总结一下,请大家指教:
1、内联函数的声明和实现必须在一起,否则编译器只当它是普通函数。
2、任何在类内部声明和实现的函数自动成为内联函数。这是内联函数的重要作用之一。
3、内联可以提高效率,且没有宏存在的缺点和隐患。所以应尽量使用内联而不用宏。
4、inline仅是给编译器的一个建议,不同的编译器会作出不同的优化。、
2.Heap与stack的差别。
Heap是堆,stack是栈。
Stack的空间由操作系统自动分配/释放,Heap上的空间手 动分配/释放。
Stack空间有限,Heap是很大的自由存储区
C中的malloc函数分配的内存空间即在堆上,C++中对应的是new 操作符。
3.信号编号从1号开始,所以没有0号信号。
4.Linux的引导过程大体经历以下几个阶段:
n 1. 加载BIOS的硬件信息;
n 2. 读取MBR的Boot loader(亦即是lilo,grub,spfdisk等等)开机信息;
n 3. 加载Kernel的操作系统内核信息;
n 4. Kernel执行init程序并取得run-level信息;
n 5. init执行/etc/rc.d/rc.sysinit文件;
n 6. 启动内核的外挂式模块(/etc/modules.conf);
n 7. init执行run-level的各个脚本(Scripts);
n 8. init执行/etc/rc.d/rc.local文件;
n 9. 执行/bin/login程序;
n 10.登入之后开始以Shell控管主机。
5. Linux的引导程序有很多种,最为常见是LILO和Grub。
6. 内核态又称系统态,它具有较高的特权,能执行所有的机器指令,能访问所有的寄存器和存储区域,能直接控制所有的系统资源。Linux在执行内核程序时是处于内核态下。
用户态是进程的普通执行状态,在用户态下进程具有较低的特权,只能执行规定的机器指令,不能执行特权指令。进程在用户态下只能访问进程的存储空间。在用户态下进程不能与系统硬件相互作用,不能访问系统资源。
在I386结构中,内核态的特权级为0,用户态的特权级为3。
7. 存放在磁盘上的可执行文件的代码和数据的集合称为可执行映象(Executable Image)。当它被装入系统中运行时,它就形成了一个进程。Linux进程是由三部分组成:
(1) 正文段(text):存放程序代码的数据,假如机器中有数个进程运行相同的一个程序,那么它们就可以使用相同的正文段,正文段具有只读的属性。
(2) 用户数据段(user segment):是进程在运行过程中处理数据的集合,它们是进程直接进行操作的所有数据(包括全部变量在内),以及进程使用的进程堆栈。
(3) 系统数据段(system segment):存放着进程的控制信息,即进程控制块(PCB),它存放了程序的运行环境。Linux中进程控制块PCB是名字为task_struct的数据结构,它称为任务结构体。任务结构体是进程存在的唯一标志。
8. Linux操作系统包括三种不同类型的进程,每种进程都有自己的特点和属性:
(1) 交互进程——由一个Shell启动的进程。交互进程既可以在前台运行,也可以在后台运行
(2) 批处理进程——这种进程和终端没有联系,是一个进程序列
(3) 守护进程——Linux系统启动时启动的进程,并在后台运行
9. Linux的进程控制块用任务结构体task_struct描述。Linux在内核空间专门开辟一个指针数组task,该数组的每一个元素是一个指向任务结构体的指针,所以task数组又称为task向量。将所有进程控制块task_struct的指针存储在task数组中,以便有效地管理。task数组大小限制了系统并发执行的进程总数,而物理内存的大小决定了系统中的最大进程数。在2.4.0版本中,每个task_struct结构占1680字节。
10. task_struct中的state项表示进程当前的状态。
n Linux系统的2.2.X版本进程共有六种状态,包括运行状态、可中断等待状态、不可中断等待状态、僵死状态、暂停状态和交换状态,而在2.4.0版本中取消了交换状态,加入独占状态。
n 可运行状态(TASK_RUNNING):可运行状态进程组成队列RUN_QUEUE。
n 等待状态:Linux进程有两种等待状态:
n 可中断的等待状态(TASK_INTERRUPTIBLE)。可中断等待进程可以被信号中断;当进程处于可中断等待状态时,系统不会调度该进程执行。当系统产生一个中断或者释放了进程正在等待的资源,或者进程收到一个信号,都可以唤醒进程转换到可运行状态。
n 不可中断等待状态(TASK_UNINTERRUPTIBLE)。不可中断等待进程直接在硬件条件等待,并且任何情况下都不可中断。处于不可中断状态的进程只有被使用wake_up()函数明确唤醒时才能转换到可运行的就绪状态。
n 暂停状态(TASK_STOPPED):通常是通过接收一个信号,如SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU而暂停运行。正在被调试的进程可能处于暂停状态。可向其发送SIGCONT信号让进程转换到可运行状态。
n 僵死状态(TASK_ZOMBIE):当进程已停止运行,但其父进程还没有询问其状态时,则称该进程处于僵死状态,是表示进程结束但尚未消亡的一种状态。
n 交换状态(SWAPPING):处于交换状态的进程正在执行内存、外存的交换工作。这个状态在2.2.X版的内核中基本已经不使用,在2.4.X版中没有这种状态。
n 独占状态(EXCLUSIVE):严格地说,这不是一种独立的进程状态。它应该是等待状态的一种,处于独占状态的进程位于等待队列中,当等待的事件发生时,只有处于这种状态的进程被唤醒,其他处于可中断和不可中断等待状态的进程则继续等待。引入独占状态后,如果事件发生,只唤醒处于独占状态的那个进程,这就可以大大提高Apache这类Web应用的效率,使Linux更适合网络服务器的角色。
Linux系统(2.2.x-2.4.x版本)进程状态表
进程状态 |
值 |
说明 |
TASK_RUNNING |
0 |
运行态 |
TASK_INTERRUPTIBLE |
1 |
等待态,可中断 |
TASK_UNINTERRUPTIBLE |
2 |
等待态,不可中断 |
TASK_ZOMBIE |
4 |
僵死态 |
TASK_STOPPED |
8 |
暂停态 |
TASK_SWAPPING |
16 |
交换态(2.4.x版本无) |
TASK_EXCLUSIVE |
32 |
独占态 |
Linux系统(2.6版本)进程状态表
进程状态 |
值 |
说明 |
TASK_RUNNING |
0 |
运行态 |
TASK_INTERRUPTIBLE |
1 |
等待态,可中断 |
TASK_UNINTERRUPTIBLE |
2 |
等待态,不可中断 |
TASK_ZOMBIE |
4 |
僵死态 |
TASK_STOPPED |
8 |
暂停态 |
TASK_DEAD |
16 |
已经退出且不需要父进程来回收的进程 |
11. Linux的所有进程还组成一个双向链表。
*next_task项指向下一进程任务结构体的指针;
*prev_task项是指向上一进程任务结构体的指针。链表的头和尾都是init_task(即0号进程)。
Linux还把所有处于可运行状态的进程通过两个指针*next_run和*prev_run连接形成双向循环队列RUN_QUEUE。
12. 进程是和内存联系在一起的,task_struct结构中有如下几个与内存相关的数据项:
n *mm进程的虚存信息;
n *ldt进程的局部描述符表指针;
n saved_kernel_stack内核态下堆栈的指针;
n kernel_stack_page内核态下堆栈的页表指针;
13. 在Linux系统中,用NR_TASKS定义task数组的大小,NR_TASKS的缺省值一般为512。创建新进程时,Linux将从系统内存中分配一个task_struct结构并将其加入task数组。当前运行进程的结构用current指针来指示。
14. 系统启动时总是处于内核模式,此时只有一个进程:初始化进程。在系统初始化的最后,初始化进程启动一个称为init内核线程(或进程),然后保留在idle状态。如果没有任何事要做,调度管理器将运行idle进程。idle进程是唯一不是动态分配task_struct的进程,它的task_struct在内核构造时静态定义,叫init_task,其标识号为0。
init内核线程是系统中第一个真正有用的进程,其标识号为1。它负责完成系统的一些初始化设置任务,以及执行系统初始化程序。init程序使用/etc/inittab作为脚本文件来创建系统中的新进程。这些新进程又创建各自的新进程。
15. 内核为fork()完成以下操作:
n (1)为新进程分配一进程表项和进程标识号
n (2)检查同时运行的进程数目
n (3)拷贝进程表项中的数据
n (4)子进程继承父进程的所有文件,对父进程当前目录和所有已打开的文件表项中的引用计数加1。
n (5)为子进程创建进程上、下文
n (6)子进程执行
16. 子进程为了和父进程完成不同的任务,利用exec()系统调用装载新的进程映像,放弃从父进程那里拷贝过来的内容。exec是一个函数族,有6个函数,分别是:
n int execl(const char *path, const char *arg, ...);
n int execlp(const char *file, const char *arg, ...);
n int execle(const char *path, const char *arg, ..., char *const envp[]);
n int execv(const char *path, char *const argv[]);
n int execvp(const char *file, char *const argv[]);
n int execve(const char *path, char *const argv[], char *const envp[]);
n 其中只有execve是真正意义上的系统调用,其它都是在此基础上经过包装的库函数。系统调用execve()对当前进程进行替换,替换者为一个指定的程序,其参数包括文件名(filename)、参数列表(argv)以及环境变量(envp)。
17. Linux系统进行调度时,把进程分成两类:
普通进程。对普通进程,一律采用基于动态优先级的轮转法(SCHED-OTHER)。
实时进程。实时进程的优先级要高于其它进程。实时进程又有两种策略:
时间片轮转(SCHED-RR)。
先进先出(SCHED-FIFO)。
18. schedule()函数在系统中被频繁调用,该函数被调用的时机有:
n (1)进程状态转换的时刻;
n (2)可运行进程队列中新增加一个进程时;
n (3)当前进程的时间片用完时;
n (4)进程从系统调用返回到用户态时;
n (5)内核处理完中断后,进程返回到用户态时;
19. Linux内核需要管理所有的虚拟内存地址,每个进程虚拟内存中的内容在其task_struct结构中有
n vm_area_struct结构
n mm_struct结构
为了加快存取,Linux另外把vm_area_struct数据结构排列成一个AVL(Adelson-Velskii和Landis)树(也称平衡树)。这棵树上,每个vm_area_struct(或节点)有一左一右两个指针指到它的邻近的vm_area_struct结构。左指针指向的节点虚拟地址小于右指针指向的节点。寻找正确的节点时,Linux从树根开始,根据每个节点的左右指针指向的地址的大小关系决定向何处去找,直到找到为止。
20. 设备驱动程序的主要功能是:
n 对设备进行初始化;
n 启动或停止设备的运行;
n 把设备上的数据传送到内存或把数据从内存传送到设备;
n 检测设备状态。
一个完整的设备驱动程序包括:
n (1)设备驱动程序的注册与注销:完成设备加载的初始化工作,或卸载时的工作。
n (2)设备的打开与释放。
n (3)设备的读写操作。
n (4)设备控制操作。
n (5)设备中断和查询。
21. 设备驱动程序都具有一些共性:
n 内核代码
n 内核接口
n 内核机制与服务
n 动态可加载
n 可配置
n 动态性
22. 对设备的识别使用设备类型、主设备号、次设备号。
n 设备类型:字符设备还是块设备。
n 按照设备使用的驱动程序不同而赋予设备不同的主设备号。主设备号是与驱动程序一一对应的,同时还使用次设备号来区分一种设备中的各个具体设备。次设备号用来区分使用同一个驱动程序的多个设备。
23.Linux的I/O控制方式有三种:
n 查询等待方式
n 中断方式
n DMA(内存直接存取)方式
n 中断方式:底半(Bottom half)处理
为了保证每一个中断处理程序都很快执行完,Linux采用了一种特殊的方式来实现中断处理的快速完成,称为底半处理的技术。将中断处理程序分为两部分:顶半部分(Top half)和底半部分。顶半部分响应中断请求并传输相关参数,这样可以快速处理完毕,以便接收其他中断。底半部分处理与中断设备相关的数据结构,解决设备状态信息和操作模式的转换等工作,费时较多,一般放到一个特定队列中,有内核提供的特殊机制负责在适当的时机统一执行。
n Linux把中断处理分成两部分的原因有三:
n 一是要把中断的总延迟时间最小化。
n 二是,当内核执行顶半部分时,正在服务的这个特殊IRQ将会被可编程中断控制器禁止,于是,连接在同一个IRQ上的其它设备就只有等到该该中断处理被处理完毕后才能发出IRQ请求。采用底半机制后,不需要立即处理的部分就可以放在底半部分处理,从而,加快了处理机对外部设备的中断请求的响应速度。
n 三是,处理程序的下半部分还可以包含一些并非每次中断都必须处理的操作;对这些操作,内核可以在一系列设备中断之后集中处理一次就可以了。即在这种情况下,每次都执行并非必要的操作完全是一种浪费,而采用底半机制后,可以稍稍延迟并在后来只执行一次就行了。
n 一个DMA控制器允许在设备没有处理器的干预下和系统内存直接交换数据。一个PC机的ISA DMA控制器共有8个DMA通道,其中7个可以用于设备驱动程序。每一个DMA通道都包括一个16位的地址寄存器和一个16位的记数寄存器。在开始传输数据之前,设备驱动程序需要设置DMA通道的地址和记数寄存器,以及数据传输的方向—是读数据还是写数据。在这之后,设备就可以随时开始DMA传输了。当传输结束后,设备将会中断系统的CPU,但在传输过程中,CPU可以做其他工作。
n 设备驱动程序在使用DMA时应该小心。首先,DMA控制器不知道虚拟内存的存在,它只能存取系统的物理内存。第二,DMA控制器无法存取整个的物理内存。DMA通道的地址寄存器存储的是DMA地址的后16位,而前8位则来自页面寄存器。这表明DMA请求只能使用系统最开始的16M地址。
Linux系统使用dma_chan数据结构跟踪DMA通道的使用。dma_chan数据结构只有两个字段,一个是指向描述DMA通道使用者的字符串的指针,另一个用来标识DMA通道是否已被分配。在系统中使用cat/proc/dma命令时,可以显示dma_chan向量表。
struct dma_chan {
int lock;
const char *device_id;
};
其中,如果成员lock!=0则表示DMA通道正被某个设备所使用;否则该DMA通道就处于free状态。而成员device_id就指向使用该DMA通道的设备名字字符串。