进程和线程关键问题总结

目录

一、进程相关概念理解

1、进程的起源

2、什么是进程

3、单核CPU同一时刻能处理多少个进程

4、如何理解进程是由进程创建的

5、fork函数

6、进程的退出状态存储在哪里

7、僵尸态进程的产生与避免方法(wait函数)

8、僵尸进程如果父进程不主动回收的流程

9、等待态和停止态

10、状态切换图

二、守护进程

1、文件掩码及umask函数

1、文件掩码File Mode Creation Mask

2、umask函数

2、进程与文件的关系

3、进程与终端的关系

三、进程间通信

1、通信模式

2、无名管道使用的注意事项

3、有名管道使用的注意事项

4、信号

1、信号的产生

2、kill()函数和raise()函数

四、System V IPC对象

1、IPC对象的唯一标识

2、ftok函数的实现逻辑

3、共享内存id与key键的关系

4、共享内存的进程间通信与无名、有名管道的区别

五、线程

1、线程出现的契机

2、pthread_t类型

3、创建一个线程的底层逻辑(即pthread_create函数)

4、内核对线程的管理

5、线程的调度

6、一个进程中的所有线程共享以下资源

7、pthread_join函数和pthread_detach函数

1、pthread_detach

2、pthread_join函数

8、多线程的控制

1、同步(对多线程的有序调度),通过无名信号量

2、互斥锁(多线程对临界资源的有序使用)

9、有名信号和无名信号量在进程和线程之间作用的区别

1、有名信号量

2、无名信号量

10、线程间互斥


一、进程相关概念理解

1、进程的起源

在当代计算机系统中,正在执行的程序(任务)越来越多,但CPU的处理能力往往有限(单核CPU在同一时间只能处理一个进程;多核CPU能在同一时间处理多个进程,但不超过核的数量。由此看来,用一台机器的进程之间存在竞争的关系),所以操作系统呢,就在内核空间建立了进程表(任务表)来管理这些任务,比如分配优先级等,使得这些任务能够按照人为的想法去有先后顺序的使用CPU资源。

2、什么是进程

进程是指一个正在执行中的程序实例。每一个进程在用户空间都拥有独立的虚拟内存空间,其中存放了代码段、数据段以及堆栈等,还有与该进程相关联的一些系统资源,如打开的文件和网络连接等。

进程是操作系统进行资源分配和调度的基本单位,使得多个程序可以并发的运行,共享系统资源。

3、单核CPU同一时刻能处理多少个进程

· 单核CPU在同一时刻只能处理一个进程,如果单核CPU要处理多个进程,那么每个进程在享用CPU资源时的时间被称为“时间片”;

· 多核CPU可以同时执行多个进程,进程个数不高于核数

4、如何理解进程是由进程创建的

5、fork函数

fork函数是一个创建进程的系统调用接口,用来创建一个子进程。

在父进程中,使用fork函数之后,发生的事情如下:

1)操作系统会为新的子进程创建一个新的进程表项(PCB结构体),以在系统中标识这个新的进程。这个进程表项会复制父进程的一些信息,如文件描述符、信号处理器等;

2)操作系统重新分配一片虚拟内存空间,并将父进程的绝大部分信息拷贝复制给这片新的空间,包括堆栈、代码段和数据段等;

3)子进程的某些属性会被修改,以使其成为一个独立的进程。最显著的是子进程的进程ID(PID),它会被设置为一个新的值,以与其他进程区分开来;

4)在所有准备就绪后,操作系统会启动子进程,从其入口点开始执行,对于子进程,即是从fork函数之后开始执行;

6、进程的退出状态存储在哪里

当一个进程结束时,无论是正常退出(比如exit(2)、_exit(3)、return 0等)还是异常退出,其退出状态都会存储在其对应的进程表结构体中(PCB),可以调用相关函数去获取这个状态,以让我们知晓进程退出的具体原因

7、僵尸态进程的产生与避免方法(wait函数)

当一个子进程结束退出时,其用户空间的数据将被释放,但内核空间的进程表中依然存在其PCB结构体,虽然此结构体中关于它的退出状态码已经更新,但这个结构体依然存在于进程表中,占用着系统资源,一直等到其父进程结束,这个PCB结构体才会被操作系统删除,那么在这段时间内,这个子进程已经不能进行任何活动了,但是它依然占据着系统资源,我们称之为僵尸进程。

产生僵尸进程的原因是,子进程先于父进程结束时,操作系统会将子进程的终止信息保留在系统中,然后等待父进程来检测子进程的状态。如果父进程没有及时检测子进程的状态,子进程的进程表项会一直存在,从而导致子进程变成僵尸进程。这意味着操作系统已经知道子进程的终止,但因为父进程没有主动去获取这些信息,所以子进程的资源无法被完全清理只有在父进程调用相关的系统调用(如 waitwaitpid)来获取子进程的状态码时,操作系统才会执行进一步的清理工作,将子进程的资源和进程表项从系统中移除

避免产生僵尸进程的方法,可以通过wait(int *status)或者waitpid(pid_t pid, int *status, int options)来对已经退出的子进程进行彻底的删除。

它们的工作原理是:

1、父进程调用 wait 函数: 当父进程调用 wait 函数时,操作系统会在内核中启动相应的处理流程;

2、查找终止的子进程: 内核会检查当前是否有已经终止但尚未被回收的子进程。如果没有,父进程可能会被阻塞,直到有子进程终止。

3、获取子进程信息: 如果有已经终止的子进程,内核会从这个子进程的进程表项中提取有关终止的信息,包括退出状态码、终止原因等。

4、移除子进程的进程表项: 内核会从进程表中移除已终止的子进程的进程表项,这个操作会释放子进程在进程表中占用的空间。

5、将状态信息传递给父进程: 内核会将获取的子进程的退出状态信息写入到父进程提供的指针变量中,以便父进程可以在函数返回后读取这些信息(退出状态可以存储到wait的参数 int *status中)。

6、解除父进程的阻塞状态: 如果父进程在等待时被阻塞,内核会解除其阻塞状态,从而允许父进程继续执行。

7、返回子进程ID:wait 函数会返回已终止的子进程的进程ID,以便父进程可以识别是哪个子进程终止了。

waitpid函数一般要用轮询的方式去回收子进程资源,因为执行一次后,发现子进程未退出,那么后续就不管了

8、僵尸进程如果父进程不主动回收的流程

如果子进程先于父进程结束,而父进程又没有主动调用wait函数去回收子进程的话,将一直等到父进程结束,僵尸进程被systemd接管才会回收其进程表资源。

9、等待态和停止态

10、状态切换图

二、守护进程

1、文件掩码及umask函数

1、文件掩码File Mode Creation Mask

文件掩码的作用是屏蔽掉在文件创建时不希望设置的权限。掩码的值会和文件的权限位进行按位与运算,从而生成实际的权限。

规则如下:

实际的权限 = 设置的权限(Open(0_CREAT)一个文件或创建一个文件) & (~022)

比如:设置的是666,实际得到的是644

2、umask函数

mode_t umask(mode_t mask)

函数功能:umask可以重设调用该函数的进程在创建文件时的掩码

函数参数(mode_t mask),新设的文件掩码,一般设置为0,增加守护进程的灵活性

函数返回值:返回之前的文件掩码

2、进程与文件的关系

每个进程在自己的内存空间中维护了一张文件描述符表(其实文件描述符相当于文件的指针,一个进程打开一个文件并不是这个文件真的被打开了,而是创建了一个文件描述符被保存在该进程的文件描述符表中,而关闭文件则是将该文件描述符从表中进行删除,当进程结束时,文件描述符表中的数据也会自动清除),其中存放了该进程打开的文件和I/O设备的信息,包括文件句柄、权限、位置指针等。当进程需要访问一个文件或进行I/O操作时,它通过文件描述符来引用文件或设备。

进程可以使用文件描述符来进行I/O操作,包括读取和写入文件、管道通信、套接字通信等。进程通过操作文件描述符进行数据的输入和输出。

而且每个进程所维护的文件描述符表都是独立的,所以一个进程的文件操作不会直接影响其他进程的文件操作。这种隔离性有助于确保进程间的数据不会相互干扰,同时也使得并发处理和多任务执行成为可能。

3、进程与终端的关系

三、进程间通信

1、通信模式

单工:A--->B, 只能A发,B收

半双工:A---->B,B------>A,但是在同一时刻只能从一方给另一方发消息,也就是一方不能在同一时刻既收也发

双工:A<====>B,在同一时刻通信双方都能进行信息的收发

同步:收方和发方的频率、步调一致

异步:发方的频率不固定,也就是不知道它什么时候发,所以收方只能一直在那监听

2、无名管道使用的注意事项

(1)当管道中无数据时,读操作会阻塞

(2)向管道中写入数据时,linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读走管道缓冲区中的数据,那么写操作将会一直阻塞。

(3)只有在管道的读端存在时,向管道中写入数据才有意义。否则,向管道中写入数据的进程将收到内核传来的SIGPIPE信号(通常Broken pipe错误)-->管道破裂

3、有名管道使用的注意事项

当只有一个读端或者写端存在时,系统会阻塞,只有让另一个进程以另一种读写方式打开有名管道后,才能够进行管道的读写操作。

有名管道也是存在于内存中的,不在磁盘

4、信号

1、信号的产生

信号是由内核进程产生,发送给其它进程的。如果其它进程不是运行态,则内核会将信号保存下来,等到其他进程恢复为运行态时,此信号就被发送过去了。

2、kill()函数和raise()函数

kill函数用来给别的进程发送信号,但是也能给自己发送,给本进程发送信号一般用raise()函数。

四、System V IPC对象

1、IPC对象的唯一标识

IPC对象包括共享内存、消息队列、信号灯,它们这些一个个对象的唯一标识是用key键值来区别的,这个键值可以用来在不同的进程之间共享资源,特别是在进程间通信(IPC)中使用。

Key值的生成依靠下面的ftok函数来实现。

2、ftok函数的实现逻辑

函数原型:

key_t ftok(char *pathname, int proj_id)

函数参数pathname:一个指向文件的路径的字符串

函数参数proj_id:一个用户定义的整数标识

函数功能:根据这两个参数生成一个唯一的键值

返回值:键值key

键值的生成是基于文件的 inode 号和 proj_id。ftok 函数会将文件的 inode 号和 proj_id 组合成一个32位的键值,其中高16位是文件的 inode 号,低16位是 proj_id。

这个生成的键值在进程间通信中很有用,因为在创建共享资源(如共享内存区域、消息队列等)时,需要一个唯一的标识符。通过ftok 函数生成的键值,不同进程可以使用相同的键值来访问同一个共享资源,从而实现进程间的数据交换和同步。

需要注意的是,ftok 的一个潜在问题是,如果文件被删除或者路径改变,那么生成的键值可能会失效。

3、共享内存id与key键的关系

使用shmget函数将IPC对象的key值传进去,可以生成一个基于此key唯一标识的共享内存区域,函数将返回此共享内存区域的Id来作为进程间使用共享内存通信的“唯一句柄”。

对于内核中所有已创建的共享内存IPC对象而言,id是唯一的,key也是唯一的,但是操作共享内存的系统调用一般传入id进行访问操作。

4、共享内存的进程间通信与无名、有名管道的区别

读写的区别:

无名、有名管道:管道中的数据遵循“先进先出”,类似于队列,读端从中读多少字节,管道中的数据就清空几个字节。

共享内存:写入共享内存中的数据被读端进程读完后,并不会清空此区域,等待下一次写端向其中写入数据时,其中的数据将被覆盖(trunct)。

五、线程

1、线程出现的契机

1、进程是资源分配的最小的那元,多进程需要占用多个虚拟空间;

2、OS对进程的调度消耗比较大,因为进程的地址空间是私有的,进程在被调度切换的时候,每个进程都需要保存等待时的状态,消耗比较大;

3、进程间通信比较麻烦,需要来回的复制和粘贴数据,而线程是共享进程内的资源(地址空间)的,那么就可以避免这一步;

2、pthread_t类型

3、创建一个线程的底层逻辑(即pthread_create函数)

4、内核对线程的管理

如同PCB(process control block)一样,内核对线程的管理也是通过一个结构体实现的,即task_struct,此结构体通常是继承拷贝主线程(也就是进程)的task_struct来的,所以一个进程中的多个线程拥有一些相同的东西,比如文件描述符等。

在Linux系统中,PCB和task_struct指的是同一个东西

每个线程的task_struct包含了线程的状态、调度信息、内存管理、文件描述符表等。

5、线程的调度

进程的调度由操作系统的专用内核程序的调度算法进行调度,线程也是如此,而且,资源(CPU)调度的最小单位就是线程,在没学线程之前,对多进程的调度可以看作是对多个主线程的调度。

线程之间被调度时,也是存在资源竞争的情况的。

6、一个进程中的所有线程共享以下资源

基本都是通过共享地址空间和继承拷贝主线程task_struct中部分成员来实现的:

1、可执行的指令通过共享进程的地址空间

2、静态数据指的是进程中的全局变量数据,所有的线程入口(start_route)函数都能够使用,临界资源就是静态数据的一种

3、文件描述符,主线程的task_struct中的所有文件描述符在创建子线程时,将把此结构体中部分成员数据拷贝至新线程的task_struct中,而文件描述符就在其中

4、信号处理函数,通过共享地址空间

5、当前工作目录:继承主线程的task_struct结构体

6、用户ID和用户组ID:继承主线程的task_struct结构体

7、pthread_join函数和pthread_detach函数

1、pthread_detach

线程分离函数,函数功能是使传入的线程与主线程在“退出层面”上分离,当已经分离的线程退出时,不需要主线程退出,才回收其task_struct资源,而是自动地被内核回收。

多用于被分离线程先于调用函数的线程退出,我们才会使用这个函数。

2、pthread_join函数

线程等待函数,函数功能是等待传入该函数的线程终结,当被传入的线程还未终结时,那么调用该函数的哪一个线程将等待阻塞在该函数调用处,直到被传入的线程退出。

8、多线程的控制

1、同步(对多线程的有序调度),通过无名信号量

2、互斥锁(多线程对临界资源的有序使用)

9、有名信号和无名信号量在进程和线程之间作用的区别

1、有名信号量

1、有名信号量用于不同进程之间的同步,即实现不同进程的有序调度;

2、有名信号量用于不同进程之间的同步,所以需要在磁盘创建不同的信号量文件来控制不同的进程;

2、无名信号量

1、无名信号量用于不同线程之间的同步,即实现不同线程的有序调度;

2、又因为不同的线程对进程中的资源是共享的,所以有序调度也能实现对临界资源完整性的保护(即大家在同时使用临界资源时不会出现混乱的现象);

3、无名信号量直接创建在进程的全局变量中,是一个内存空间,而不需要像有名信号量再创建磁盘信号量文件;

10、线程间互斥

注意:互斥将用于解决共享临界资源受多个进程同时访问造成的竞态条件问题,保证了临界资源的完整性,但不保证线程之间的同步

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值