Linux 进程基础介绍

1、进程介绍
程序是存放在磁盘上的一系列代码和数据的可执行映像,是一个静止的实体

1)进程概念:
进程是一个执行中的程序,是动态的实体,由两部分组成
    1> 是操作系统用来管理进程的内核对象
    2> 地址空间,包含所有可执行模块的或DLL模块的代码和数据,还包括动态内存分配的空间,如线程堆栈和堆栈分配空间

===========================================

进程实体=程序段 + 相应的数据段 + PCB

PCB表:系统把PCB组成在一起,并放在内存的固定区域。PCB表的个数决定了在系统中最多可同时存在的进程个数,称为系统的开发度。

PCB的组织方式: 链式方式和索引方式

上下文切换:由CPU切换到另一个进程时,系统必须保存旧进程状态并为新进程调入所保留的状态。切换实际按取决于硬件的支持。

        每个进程都有自己的内核栈,当进程从用户态进入内核态时,CPU就自动地设置该进程的内核栈,也就是CPU从任务状态栏TSS中装入内核栈指针ESP,内核根本不给tast_struct分配内存,而仅仅给内核站分配8KB的内存,并把其中一部分给task_struct使用。内核栈不能超过7KB。否则,内核站会覆盖tast_struct结构,导致内核奔溃。

===========================================

2)进程状态:
    运行态、等待态、就绪态
3)进程四要素
    1> 有一段程序供其执行
    2> 有进程专用的内核空间堆栈
    3> 有一个task_struct数据结构(PCB,进程控制块)
    4> 有独立的用户空间
4)进程分类概念
    孤儿进程一个父进程退出,而它的一个或多个进程还在运行,那么这些子进程称为孤儿进程。孤儿进程将被init进程(进程号为1)收养,并由init进程对他们完成状态收集。
    僵尸进程:一个进程使用fork创建子进程,如果子进程退出,父进程并没有使用wait或waitpid获取子进程的状态,那么子进程的状态描述符仍然保存在系统中。这种进程称为僵尸进程(进程状态为"Z")。僵尸进程在内核中还有一些信息,需要及时处理;但是每个进程或长或短都会经历僵尸进程的状态。
    
5)创建进程:
     pid_t fork(void)
    //fork 是完全copy了一份父进程的地址空间给子进程,让彼此可以独立的执行。
    pid_t vfork(void)
    //与父进程共享地址空间,并且父进程会等待子进程执行完毕后才继续执行。

    1> 调用一次fork返回2次,分别在子进程和父进程中返回,父进程中其返回值是是子进程的进程标识符,子进程中其返回值等于0。子进程复制父进程的数据段、堆栈,共享父进程的代码段。父、子进程执行次序不一定
    2> 保证子进程先运行,即在调用exec和exit之前父进程不运行,也就是依赖父进程的进一步动作,将陷入死锁。在调用exec和exit之前,子进程和父进程是共用数据段和代码段的,也就是子进程的数据更改会影响父进程
    
    当一个进程被创建时,内核仅仅把父进程的页框赋给了子进程的地址空间,但是把这些页框标记位只读,一旦父或子进程修改页内容时,产生异常--初始化页框
6)进程间为什么要通信以及如何进行通信?
    进程用户空间是相互独立的,一般而言是不能相互访问的。但是很多情况下进程间需要互相通信,来实现系统的某项功能。进程通过与内核及其他进程之间的互相通信来协调他们的行为。
    1> 管道(Pipe)及有名管道(named Pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能之外,它还允许无亲缘关系进程间的通信;
    无名管道实际上是内存中的一个临时存储区,它由系统安全控制,并且独立于创建它的进程的内存区。管道对数据采用先进先出方式管理,并严格按顺序操作,例如:不能对管道进行搜索,管道中的信息只能读一次。无名管道只能用于两个相互协作的进程之间的通信,并且访问无名管道的进程必须有共同的祖先。
    pipe()——打开一个可以读写的管道;
    close()——关闭相应的管道;
    read()——从管道中读取字符;
    write()——向管道中写入字符;

    不同的地方在于使用有名管道的进程不需要具有共同的祖先,其他进程,只要知道它的名字,就可以访问它。管道非常适合进程之间快速交换信息
    2> 信号(signal):信号是比较复杂的通信方式 ,用于通知接受进程有某种事件发生,除了用于进程通信外,进程还可以发送信号给进程本身
    信号机制是UNIX为进程中断处理而设置。它只是一组预定义的值,因此不能用于信息交换,仅用于进程中断控制。例如在发生浮点错、非法内存访问、执行无效指令,某些按键等都会产生一个信号,操作系统就会调用有关的系统调度或用户定义的处理过程来处理。
    信号处理的系统调用是signal,调用形式是:
    signal(signalno,action);//其中signalno是规定信号编号的值,action指明当特定的信息发生时所执行的动作。
    3> 报文(message)队列(消息队列):消息队列是消息的链接表,包括Posix消息和system V消息。有足够权限的进程可以向队列添加消息,被赋予读权限的进程可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无字节流以及缓冲区大小受限的缺点
    消息队列是内存中独立于生成它的进程的一段存储区,一旦创建消息队列,任何进程,只要具有正确的访问权限,都可以访问消息队列,消息队列非常适合于在进程间交换信息。
    消息队列的每条信息由类型编号来分类,这样接收进程可以选择读取特定的消息类型。消息队列在创建在一直存在,直到使用msgctl系统调用或iqcrm -q命令删除它为止。
    系统提供了许多有关创建、使用和管理消息队列的系统调用,如:
      int msgget(key,flag)——创建一个具有flag权限的MQ及其相应的结构,并返回一个唯一的正整数msqid(MQ的标识符);
      int msgsnd(msqid,msgp,msgsz,msgtyp,flag)——向队列中发送信息;
     int msgrcv(msqid,cmd,buf)——从队列中接收信息;
     int msgctl(msqid,cmd,buf)——对MQ的控制操作;

    4> 共享内存(SM):使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的,往往与其他通信机制,如信号量共同使用,来达到进程间的同步及互斥。
    共享内存段是主存的一部分,它由一个或多个独立的进程共享。各进程的数据段与共享存储段相关联,对每个进程来说,共享存储段有不同的虚拟地址。系统提供的有关SM的系统调用有:
    int shmget(key,size,flag)——创建大小为size的SM段,其相应的数据结构名为key,并返回共享内存区的标识符shmid;
    char shmat(shmid,address,flag)——将当前进程数据段的地址赋给shmget所返回的名为shmid的SM段;
    int shmdr(address)——从进程地址空间删除SM段;
    int shmctl (shmid,cmd,buf)——对SM的控制操作;

    SM的大小只受主存限制,SM段的访问以及进程间的信息交换可以通过同步读写来完成。同步通常由信号灯来实现,SM非常适合进程之间大量数据的共享
    5> 信号量(semaphore):主要作为进程间以及同一进程间不同线程的同步手段
    在UNIX中,信号量是一组进程共享的数据结构,当几个进程竞争同一资源时,它们的操作便由信号量来同步,以防止互相干扰。
    信号量保证了同一时刻只有一个进程访问某一临界资源,所有请求该资源的进程都将被挂起,一旦该资源得到释放,洗头膏才允许其他进程访问该资源。
    6> 套接字(socket):可用于不同机器的进程间通信。

7)线程与进程的区别?
线程是指进程内的一个执行单元,也是进程内的可调度实体。
线程与进程的区别:
    1> 调度:线程作为调度和分配的基本单元进程作为拥有资源的基本单元
    2> 并发性:不仅进程之间可以并发执行,同一进程的多个线程也可以并发执行
    3> 拥有资源:进程是拥有资源的一个独立单元,线程不拥有系统资源,但是可以访问隶属于进程的资源
    4> 系统开销:在创建或撤销进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤销线程时的开销

创建线程:
    pthread_t pid;
    void *fun(void *arg){ }
    pthread_create(pid,NULL,func,NULL);
结束:
    pthread_exit(0);
主线程阻塞等待子进程:
    pthread_join(pid);//防止主线程结束,子线程没机会运行。同时还能获取子线程的退出状态。
    
分离:
    pthread_detach();//使线程分离,这样运行结束后,存储器资源会被立刻回收。
    子线程调用:
        pthread_detach(pthread_self());//
    父进程调用:
        pthread_detach(tid);//此函数为非阻塞函数,立刻返回。
子线程分为分离状态和join状态join状态子线程运行结束后,一些资源不会释放。调用pthread_join后,资源才释放。分离状态子线程运行结束后,资源被自动回收。
    1> 线程优点:
    所有线程可以直接共享内存和变量等(线程的栈是单独分配的,但是也是在同一地址空间);(与多进程相比,缺点是线程同步需要加锁,线程出问题可能会影响整个程序)。

        1.1> 拥有就绪、阻塞和执行三种基本状态。

        1.2> 线程只拥有必不可少的资源,如:线程状态、程序计数器,寄存器上下文和栈

        1.3> 一个线程和对等线程共享:代码段、数据段、操作系统资源

        1.4> 每个线程独立拥有的资源: 寄存器和栈

        1.5> 内核线程和用户线程

                1.5.1> 内核线程依赖于OS核心,由内核进行创建、撤销和切换

                1.5.2> 时间片分配给线程,所以多线程的进程获取更多CPU的时间

                1.5.3> 用户线程【速度快】:由用户级线程库进行管理的线程。线程库提供对线程创                                 建、调度和管理的支持,无需内核支持
    2> 多进程优点:
    不需要线程同步的锁;
    各进程相互独立,互不影响。
    3> 线程同步
    多个线程使用相同内存时,需要确保每个线程看到一致的数据。

2、守护进程
linux 守护进程编写步骤:
    1) 脱离控制终端tty,让父进程为init进程
        fork()一个新进程,然后父进程退出
        setsid(); //创建一个会话期
    2) 禁止进程重新打开控制终端
    3) 关闭打开的文件描述符
    4) 改变工作当前目录
        进程活动时,其工作目录所在的文件系统不能卸下,一般需要将工作目录改变到根目录 -- chdir("/")
    5)  重设文件创建掩码
        进程从创建它的父进程哪里继承了文件创建掩码。为了防止可能修改守护进程所创建的文件的存取位,将文件创建掩码清除: umask(0)

系统级守护进程以及用户级守护进程:权限不一样,

多进程之间彼此独立,内存隔离
3、进程调度

=====================================

进程调度队列:

        作业队列:在系统中的所有进程的集合

        就绪队列:在主内存中的,就绪并等待执行的所有进程的集合

        设备队列:等到某一I/O设备的进程队列

作业调度(长程调度):可以进入就绪队列的进程---外存到内存

短程调度(CPU调度):选择可被下一个执行并分配到的进程 --- 切换频率高

中程调度:将内存中处于阻塞状态的进程换到外村上,满足执行时,再放到内存中

=====================================


1)调度策略(在Linux中,进程的优先级是动态的,调度程序跟踪进程正在做什么,并周期性调整它们的优先级):
    1> 进程响应时间尽可能快
    2> 后台作业的吞吐量极可能高
    3> 尽可能避免进程的饥饿现象
    4> 低优先级和高优先级进程的需要尽可能调和

调度程序总能成功的找到要执行的进程(总是至少有一个可运行的进程,swapper进程,PID=0,只有CPU不能执行其他进程时才执行)    
    
CHS调度类:
    SCHED_NORMAL(SCHED_OTHER):普通的分时进程,新进程会继承父进程的静态优先级,静态优先级由高到低
    SCHED_FIFO:先入先出的实时进程,保存进程描述符在当前运行队列,更高优先级可抢占
    SCHED_IDLE:只在系统空闲时才能够被调度执行的进程
实时调度类:
    SCHED_RR:时间片轮转的实时进程,插入到运行队列链表的末尾
    SCHED_BATCH:批处理进程 
      
2)内核优先级区间(100-139):
基本时间片:
    1> (140-静态优先级)*20,(静态优先级<120)
    2> (140-静态优先级)*5, (静态优先级 >= 120) 

3)调度时机---发生在schedule()什么时候被调用
    调度标志:TIF_NEED_RESCHED,表示是否需要重新执行一次调用
    
    主动式:在内核中直接调用schedule()
    被动式:
        用户抢占(从内核空间返回用户空间):
            从系统调用返回用户空间
            从中断处理程序返回用户空间
        内核抢占:
            高优先级的进程/线程可以抢占正在内核空间运行的低优先级进程/线程
            中断处理程序完成,返回内核空间之前、
            当内核代码再一次具有可抢占行的时候,如解锁及使能软中断
            
            不允许内核抢占:
                内核正进行中断处理
                正在运行中断上下文
                进程正持有自旋锁,读写锁等,容易造成思索
                处于抢占过程
            
4)调度步骤
    1> 清理当前运行中的进程
    2> 选择下一个要运行的进程(pick_next_task分析)
    3> 设置新进程的运行环境
    4> 进程上下文切换
5)调度程序所使用的函数:
    schedule_tick() 、//维持当前最新的time_slice计数器 --更新进程的时间片
    try_to_wake_up() //唤醒睡眠进程 ---设置进程状态为TASK_RUNNING,并插入CPU的运行队列
    recale_task_prio() //更新进程的动态优先级
    schedule()    //选择要执行的新进程 --实现调度程序,从运行队列中找到一个进程,随后将CPU分配
    load_balance() //维护多处理器系统中运行队列的平衡 ,检查调度域处于严重的不平衡状态,检查是否可以通过把最繁忙的组中的一些进程迁移到本地CPU的运行队列来减轻不平衡的状态 --调度域(CPU集合)-sched_domain描述符表示,保存在CPU变量phys_domains中  
 
6)调度相关的系统调用:
    nice()--允许进程改变它们的基本优先级(仅限于调用它的进程)
    getprority()--返回20减去给定组中所有进程之中的最低nice字段的值
    setproprity()--把给定组中所有进程的基本优先级都设置为1个给定的值

7)调度相关进程分类    
    活动进程:时间片未用完
    过期进程:时间片用完
    交互式进程:等待键盘和鼠标操作,接受输入后,进程必须很快被唤醒(shell文本编辑等)
    批处理进程:在后台运行,不必很快相应(编译程序)
    实时进程:短的相应进程,不会被低优先级的进程阻塞(音视频应用程序,传感器等的程序)

    实时进程的调度:
        实时进程与实时优先级相关(1最高,99最低),总是被当成活动进程
        sched_setparam()和sched_setscheduler()改变优先级

    实时进程被另外一个进程取代,需要满足以下条件之一:
    1> 更高优先级实时进程抢占
    2> 执行了阻塞操作并进入睡眠
    3> 进程停止或被杀死
    4> 进程通过系统调用自愿放弃CPU

为了避免进程饥饿,当一个进程用完它的时间片时,就应该被还没有用完时间片的低优先级进程取代
    
4、系统调用
    系统调用由操作系统内核实现,运行于内核态,
    普通函数调用由函数库或用户自己提供,运行于用户态

===========================================

系统调用在用户层和操作系统直接传递参数:

1> 寄存器中的参数传递

2> 参数存在内存中的一张表,表地址作为寄存器的参数传递

3> 程序把参数压入栈,由操作系统弹出

===========================================


1)工作原理
    应用程序首先用适当的值填充寄存器,然后调用一个特殊的指令跳转到内核某一个固定的位置,内核根据应用程序所填充的固定值来找到相应的函数执行
    1> 适当的值 -- 系统调用号
        在文件include/asm/unistd.h中为每个系统调用规定了唯一的编号,称为系统调用号
    2> 特殊的指令 
        在Inter CPU中,指令由中断0x80实现
        在ARM中,指令是SWI(已重命名为SVC指令)
    3> 固定的位置
        在ARM体系中,应用程序跳转到固定内核位置是ENTRY(vectoe_swi) <entey-common.S>
    4> 相应的函数
        内核根据应用程序传递来的系统调用号,从系统调用表sys_call_table找到相应的内核函数
2)添加系统调用
    添加新的内核函数
    更新头文件unistd.h
    针对这个新函数更新系统调用表call.S    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值