Linux基础复习(1)

复习点:(1)进程和线程区别       (2)进程/线程间的通信方式       (3)进程/线程的同步和互斥    (4)死锁      (5)多进程/多线程程序的调试    

一、进程和线程的区别

进程:进程是对运行是程序的封装,是系统进行资源调度和分配的基本单位,实现了操作系统的并发

线程:线程是进程的子任务,是cpu调度和分派的基本单位,用于保证程序的实时性,实现进程内部的并发;线程是操作系统可识别的最小执行和调度单位。每个线程都独自占用一个虚拟处理器;独自的寄存器组,指令计数器和处理器状态。每个线程完成不同的任务,但是共享同一地址空间,打开的文件队列和其他内核资源。

进程和线程的区别:

1、一个线程只能属于一个进程;而一个进程至少有一个线程,线程是依赖于进程而存在的。

2、进程在执行的过程中拥有独立的内存单元;而线程则是共享同一个线程的内存单元,每个线程都有自己独立的栈和一些寄存器组,用来存放局部变量和临时变量。

3、进程是资源调度和分度的基本单位;线程是cpu调度和分派的基本单位。

4、在系统开销方面,每个进程都有自己独立的内存空间,所以在开辟和回收的系统开销就比较大;而线程是共享进程的内存空间的,自己只拥有一小部分独立的空间,所以在创建和回收上的开销比进程小的多,操作系统创造线程的原因有很大一部分是因为这个。

5、在通信方面,由于每个进程是对立的,所在在通信上就比较麻烦;而由于线程共享了同一进程的内存空间所以通信很方便。

6、在稳定性方面,进程由于是独立存在的所有程序中一个进程的异常结束并不会导致整个程序崩溃,而且在调试时比较容易;而在多线程程序中一个线程异常结束,就会导致整个进程结束,调式也相对比较困难。

二、进程/线程间的通信方式

1、进程间的通信方式

进程间的通信方式又被称为IPC(Interprocess communication ),主要的方法包括:管道、系统IPC、套接字。

1.1、管道

管道主要分为无名管道(pipe)和有名管道(fifo):其中无名管道只允许有亲源关系的进程间通信;而有名管道允许所有进程间通信,无论通信的进程是否有亲缘关系。

无名管道(pipe)

(1)半双工,只允许数据在一个方向上流动,具有固定的读端和写段。

(2)只能作用在具有亲缘关系的进程间的通信。

(3)在使用时类似于文件,但只能存在于内存中,不能存在于文件系统中。

有名管道(fifo):

(1)和pipe一样也是半双工的。

(2)可以作用于任意两个进程之间,无论进程是否有亲缘关系。

(3)是一种可以存在于文件系统的文件,但不能用于存储数据。

1.2、系统IPC

信号:信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某个事件已经发生。

信号量:是一个计数器,可以用来控制多个进程对共享资源的访问。信号量用于实现进程间的互斥与同步,而不是用于存储进程间的通信数据。

(1)信号量用于进程间同步,若要在进程将传递数据需要结合共享内存。

(2)信号量是基于系统的PV操作的,程序中队信号量的操作都是原子操作。

(3)每次队信号量的pv操作不仅限于对信号量的加1减1,而且可以加减任意正整数。

(4)支持信号量组。

消息队列:消息队列,是消息的连接表,存放在内核中。一个小队列由一个标识符来标记。它克服了信号传递信息少,管道只能承载无格式字节流且有缓冲区的限制。具有权限的进程的进程可以按照一定的规则向消息队列中添加新信息;对消息队列有读权限的进程从消息队列中读取信息。

(1)消息队列时面向记录的,其中的消息具有特定的格式以及特定的优先级。

(2)消息队列独立于发送与接收。进程终止时,消息队列以其内容并不会被删除。

(3)消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

共享内存:它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中的数据更新。这种方式依靠某些同步操作,如互斥锁和信号量等。

(1)共享内存时最快的一种IPC,因为时进程直接对内存进行存储。

(2)多个进程可以同时操作,所有需要进行同步。(类似于线程共享进程内存的方式)

(3)信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。

1.3套接字

利用socket可以跨主机通信。

2、线程间的通信方式

由于线程是在同一进程中,会共享进程上除占之外的全部内存空间,所以它的通信非常简单可以利用向内存映射、全部变量和静态变量这样的方式。这里所介绍的几种方式更像是一种同步的方式,虽然同步也可以看着是一种通信。

1、临界区:每个线程访问临界资源(进程之间应采用互斥的方式,实现对资源的共享。)的那段代码称为临界区。

2、互斥量:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源子啊不被多个线程同时访问。

3、信号量:为控制具有有限数量的用户资源而设计的,它允许多个线程在同一时刻区访问同一个资源,但一般需要限制同一时刻访问此资源的最大线程数目。(p、v操作)

4、信号:通过通知操作的方式来保持多线程的同步,还可以方便的实现多个线程优先的比较操作。

三、进程/线程的同步和互斥

进程同步:即当有一个进程在对临界资源进行操作时,其他进程都不可以对这个临界资源进行操作,直到该进程完成操作, 其他进程才能对该内存地址进行操作

进程互斥:指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

在多道程序环境下,对于同处于一个系统中的多个进程,由于它们共享系统的资源或为了完成某一目标任务而合作,它们之间就会存在两种制约关系:(1)间接相互制约关系(竞争关系)  (2)直接相互制约关系(协作关系)。而进程的互斥、同步和通信都是居于这个基本的关系而存在的。

(1)互斥关系:为了解决进程的竞争关系而引入进程的互斥。

(2)同步关系:为了解决进程间的松散的协作关系而引入进程同步。

(3)进程通信:为了解决进程间的紧密协作关系而引入进程间的通信。

系统中多个进程是独立的彼此无关,它们不知道其他进程的存在,并且也不受其他进程执行的影响。比如三个用户去种树,它们的总任务说三个人都把树种上。种树有三个工具树苗、铁锹和水桶。用铁锹挖好坑后将数码栽好,然后用水桶浇上水这样才算是种好一棵树。现在由于三个用户是竞争关系,他们分别拿了树苗、铁锹和水桶,谁也不愿意放手。这时就会导致死锁的问题,即一组进程如果都获得了部分资源,还想得到其他进程的资源,但每个进程都不去主动释放已经占有的资源,最终所有的进程都会无限制的等待,这就是死锁。

为了避免死锁的问题,操作系统就引入了进程的互斥的概念。进程互斥指若干进程要使用同一共享资源时,任何时刻最多允许一个进程去使用,其他要使用的该资源的必须等待,直到占用资源的进程释放该资源。

那上面的例子说明:在三个用户各自拿着自己占用的工具都不肯放手时,有人提了一个提议,将三个资源(树苗、铁锹和水桶)放在一个房子中,然后给没人分发一个锁和一把钥匙,然后一起抢用(线程的争用)那个房子,谁先抢到房子的使用权就立马给房间上锁(只有自己的钥匙才能打开自己的锁),就可以保证一个用户可以那到全部的资源,等到该用户使用完资源(种完树)之后,就将锁打开,然后剩余的人继续按刚才的方法去争用那个房间。

在这个故事中对应我们刚才的概念:

三个人都必须种完树(总任务):进程间的协作

每个人都要种树(个人任务):进程间的竞争。个人任务完成之后,总任务才成完成所以进程间的竞争是一种特殊的进程间的协作关系。

铁锹、树苗和水桶:临界资源,进程之间应采用互斥的方式,实现对资源的共享。

房间,钥匙和锁:临界区,每个进程中访问临界资源的那段代码被称为临界区(保证临界资源被互斥的访问),临界区不是一个具体的办法,而是一个抽象的概念。

钥匙和锁:互斥锁,即当一个进程去执行临界区时,其他进程不能进入临界区,而互斥锁是实现临界区的一种方式。

那当用户使用完工具如何让其他用户知道那?

方法1:没有拿到房间控制权的用户(即没有进入临界区的进程)不停的去查看这个房间的状态,查看该房间的锁是否被打开。一旦打开就立即抢占。

方法2:当一个用户拿到控制权后,其他没有拿到控制权的用户都去先休息, 该用户工作完成后去通知(进程间的同步)它们,然后再争抢。或者只通知它们种的一个,另一个继续休息。

从这里就可以看出进程同步就是进程通信的一种方式,只是进程间的通信有时需要交互大量的数据,而进程间的同步,有时只是一种通知。

四、死锁

在前面的例子中提到过死锁的概念。

死锁:一组进程如果都获得了部分资源,还想得到其他进程的资源,但每个进程都不去主动释放已经占有的资源,最终所有的进程都会无限制的等待,这就是死锁。

1、死锁的四个必要条件:

(1)互斥条件:进程对所分配到的资源进行排它性使用,即在一段时间内,某些资源只能被一个进程占用。如果此时还有其他进程请求该资源,则请求进程只能等待,直至占有该资源的进程用完释放。

(2)请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己以获得的资源保持不放。

(3)不可抢占条件:进程以获得的资源在未使用完毕之前不能被抢占,只能在进程完由自己释放。

(4)循环等待条件:在发生死锁时,必然存在一个进程---资源循环链,即进程集合中{p1,p2,p2......pn},p1等待p2,p2等待p3.......pn等待p1正在占用的资源。

2、处理死锁的方法

死锁的进程占用了大量的系统资源,但却不能做任何事情,这对资源本就有限的系统,带来很大的麻烦。那要如何处理死锁那?

(1)预防死锁:这是一种比较简单和直观的事先预防的方法。该方法时通过设置某些限制条件,去破坏产生死锁四个必要条件中的一个或者几个来预防死锁。预防死锁是一种较易实现的方法,已被广泛使用。

(2)避免死锁:同样时属于事先预防策略,但它并不是事先采用各种限制措施,去破坏产生死锁的四个必要条件,而是在资源动态分配过程中,用某些方法防止系统进入不安全状态,从而可以避免发生死锁。这其中就有著名的:银行家算法。

(3)检测死锁:这种方法无需事先采取任何限制性的措施,允许进程在运行过程中发生死锁。但可以通过检测机构及时的检测出死锁的发生,然后采取适当的措施,把进程从死锁中解脱出来。

(4)解除死锁:当检测到系统中已发生死锁时,就采取相应的措施,将进程从死锁状态中解脱出来。常用的方法时撤销一些进程回收它们的资源。通常和检测死锁配合使用。

3、银行家算法

银行家算法时迪杰斯特拉设计的一种避免死锁的办法,之所以叫银行加算法,是因为该算法最初是为银行系统设计的。

为了实现银行家算法,每一个新进程在进入系统时,它必须申明运行过程中,可能需要各种资源类型的最大数目,其数目不应超过系统所拥有的资源总量。当进程请求一组资源时,系统必须首先确定是否有足够的资源分配个该进程。若有,再进一步计算再将这些资源分配给进程后,是否会使系统处于不安全的状态。如果则将分配后系统任然安全,则将资源分配,否则,继续等待。

银行家算法中有两个关键的算法:(1)银行家算法   (2)安全性算法

3.1 银行家算法中的数据结构

(1)系统当前可利用资源:是一个包含了m个元素的数组,其中的每一种元素代表一类可以利用的资源数目,其初始值是系统中所配置的该类的全部的可用资源数目,其数值随着该类资源的分配和回收而动态的改变。

(2)最大需求矩阵:是一个mxn的二维矩阵,用来表示每一个进程需要的最大的资源数。

(3)分配矩阵:是一个mxn的二维矩阵,用来表示系统给每一个进程分了多少各种资源。

(4)需求矩阵:是一个mxn的二维矩阵,用来表示该进程还需要多少各种资源。

3.2银行家算法

当进程向系统申请资源时:

(1)对比申请的资源数和需求矩阵中的资源数,如果申请的资源数小于等于需求矩阵中资源数,那进入步骤(2),否则,报错。

(2)对比申请的资源数和系统单签可利用的资源数,如果申请的资源树小于当前可利用的资源,那进入步骤(3),否则,进程等待。

(3)系统试着将可利用资源分配,并更新各个数据结构中的值。

(4)系统执行安全性算法,判断按照该方法分配后系统是否处于安全状态,如果按照此方法分配系统任然处于安全状态,那就会同意进程的申请方案,否则,将数据结构恢复,进程继续等待。

3.3安全性算法

(1)从进程集合中寻找一个满足系统当前可利用资源数大于等于该进程需求矩阵资源数的进程,如果有进入步骤(2),否则,系统处于不安全状态。

(2)当该进程拿到自己需要的资源后,就说明该进程可以完成自己的任务,再完成任务后该进程需要释放它的资源;所以此时的系统可用资源就可以加上该进程的占用的资源。

(3)重复(1)(2)动作,如果系统中的所有进程都满足条件,就说明系统处于安全状态,否则,系统处于不安全的状态。

五、多进程/多线程程序的调试  

gdb是linux环境下的代码调试工具。在使用gdb工具调试代码是在程序编译时需要加上-g的参数。

1、gdb常用的命令

   list/l 行号:显示binFile源代码,接着上次的位置往下列,每次列10行。
   list/l 函数名:列出某个函数的源代码。
   r或run:运行程序。
   s或step:进入函数调用
   breaktrace(bt):查看各级函数调用及参数
   info(i) locals:查看当前栈帧局部变量的值
   info break :查看断点信息。
   finish:执行到当前函数返回,然后挺下来等待命令
   print(p):打印表达式的值,通过表达式可以修改变量的值或者调用函数
   set var:修改变量的值
   quit:退出gdb
   break(b) 行号:在某一行设置断点
   break 函数名:在某个函数开头设置断点
   continue(或c):从当前位置开始连续而非单步执行程序
   run(或r):从开始连续而非单步执行程序
   delete breakpoints:删除所有断点
   delete breakpoints n:删除序号为n的断点
   disable breakpoints:禁用断点
   enable breakpoints:启用断点
   info(或i) breakpoints:参看当前设置了哪些断点
   display 变量名:跟踪查看一个变量,每次停下来都显示它的值
   undisplay:取消对先前设置的那些变量的跟踪
   until X行号:跳至X行
   p 变量:打印变量值
   n 或 next:单条执行

如果只按回车的话,默认时执行上一条指令。

2、多进程调试

#include <stdio.h>
#include <unistd.h>
#include <error.h>
#include <stdlib.h>
int main()
{
    int childDis=fork();
    if(childDis==-1)
    {
        perror("fork");
        exit(1);
    }
    else if(childDis==0)
    {
        printf("I my chlider\n");
        exit(0);
    }
    else
    {
        wait(NULL);
        printf("I my parent\n");
        exit(0);
    }
}

在默认设置下,在调试多进程程序时GDB只会调试主进程。但GDB(7.0)以上的版本支持多进程的分别以及同时调试。

在gdb7.0以上的版本中有两个属性控制GDB调试的方式:follow-fork-mode和detach-on-fork。

follow-fork-mode:parent   detach-on-fork:on    只调试主进程
follow-fork-mode:parent   detach-on-fork:off   同时调试多个进程,gdb跟主进程,子进程会阻塞到fork的位置。
follow-fork-mode:child    detach-on-fork:on    只调试子进程
follow-fork-mode:child    detach-on-fork:off    同时调试多个进程,gdb跟子进程,父进程会阻塞到fork的位置。
即:follow-fork-mode属性控制gdb跟那个进程;detach-on-fork:属性控制是否可以调试多个进程
set follow-fork-mode parent/child     //设置GDB跟踪父进程/子进程
set detach-on-fork on/off             //设置GDB是否同时调试多个进程
show follow-fork-mode                 //查看follow-fork-mode的状态
show detach-on-fork                   //查看detach-on-fork的状态

GDB调试多进程的步骤:

(1)编译程序并加入调试信息,启动GDB。

(2)设置follow-fork-mode和detach-on-fork的属性。

(3)设置断点。

(4)运行程序

程序停留在子进程中。

(5)使用info  inferiors命令查看当前有哪些进程。

这里注意一下再gdb任然可以使用bash下的命令,不够需要再命令前加shell。

(6)使用n命令单步执行子进程的程序。

(7)利用inferior <num> 切换执行的进程。

再本程序中需要注意,如果先调试父进程,会有可能导致程序死锁。因为再父进程中等待了子进程,如果子进程不结束父进程会一直阻塞在wait处。

2、多线程程序调试

#include <stdio.h>
#include <error.h>
#include <stdlib.h>
#include <pthread.h>
#define NUM 4
void* pthreadFun(void* arg)
{
    printf("我是线程:%x\n",pthread_self());
    pthread_exit(NULL);
}
int main()
{
    pthread_t pid[NUM];
    int i=0;
    for(;i<NUM;++i)
    {
        pthread_create(pid+i,NULL,pthreadFun,NULL);
    }
    for(i=0;i<NUM;++i)
    {
        pthread_join(pid[i],NULL);
    }
    printf("我是线程:%x\n",pthread_self());
    pthread_exit(NULL);
}

GDB默认支持调试多线程程序(默认:跟主线程,子线程阻塞在pthread_create处 )。

GDB调试多线程的步骤:

(1)编译程序并加入调试信息,启动GDB。

(2)设置断点,并查看。

(3)运行程序,并查看线程

(4)通过thread 《num》切换线程。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值