目录
一、简述进程的几种状态
进程有5种状态:创建、就绪、执行、阻塞、终止。
一个进程创建后,被放入队列处于就绪状态,等待操作系统调度执行,执行过程中可能切换到阻塞状态(并发),任务完成后,进程销毁终止。
1、创建
一个应用程序从系统中启动,首先就是进入创建状态,需要获取系统资源创建进程管理块(PCB:Process Control Block)完成资源分配。
2、就绪
在创建状态完成之后,进程已经准备好,处于就绪状态,但是还未获得处理器资源,无法运行。
3、运行
获取处理器资源,被系统调度,当具有时间片开始进入运行状态。如果进程的时间片用完了,就进入就绪状态。
4、阻塞
在运行状态期间,如果进行了阻塞的操作,如耗时的I/O操作,此时进程暂时无法操作就进入到阻塞状态,在这些操作完成之后就进入就绪状态。等待再次获取处理器资源,被系统调度,当具有时间片就进入运行状态。
5、终止
进程结束或者被系统终止,进入终止状态。
二、简述进程通信中的管道实现原理
操作系统在内核中开辟一块缓冲区(称为管道)用于通信。管道是一种两个进程间进行单向通信的机制。因为这种单向性,管道又称为半双工管道,所以其使用是有一定的局限性的。半双工是指数据只能由一个进程流向另一个进程(一个管道负责读,一个管道负责写);如果是全双工通信,需要建立两个管道。管道分为无名管道和命名管道,无名管道只能用于具有亲缘关系的进程之间的通信(父子进程或兄弟进程),可以看作是一种特殊的文件,管道本质是一种文件;命名管道可以允许无亲缘关系进程间的通信。
管道的原型为:
#include <unistd.h>
int pipe(int fd[2]);
pipe()函数创建的管道处于一个进程中间,因此一个进程在由pipe()创建管道后,一般再使用fork()建立一个子进程,然后通过管道实现父子进程间的通信。管道两端可分别用描述字fd[0]以及fd[1]来描述。注意管道的两端的任务是固定的,即一端只能用于读,由描述子fd[0]表示,称其为管道读端;另一端则只能用于写,由描述字fd[1]来表示,称其为管道写端。如果试图从管道写端读取数据,或者向管道读端写入数据都将发生错误。一般文件的I/O函数都可以用于管道,如close()、read()、write()等。
具体步骤如下:
①父进程调用pipe开辟管道,得到两个文件描述符指向管道的两端。
②父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
③父进程关闭管道读端,子进程关闭管道写端。父进程可以往管道里写,子进程可以从管道里读,管道是用环形队列实现的,数据从写端流入,从读端流出,这样就实现了进程间通信。
如下面代码:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define INPUT 0
#define OUTPUT 1
int main()
{
//创建管道
int fd[2];
pipe(fd);
//创建子进程
pid_t pid=fork();
if(pid<0)
{
printf("fork error!\n");
exit(-1);
}
else if(pid == 0)
{
//执行子进程
printf("Child process is starting...\n");
//子进程向父进程写数据,关闭管道的读端
close(fd[INPUT]);
write(fd[OUTPUT],"hello!",strlen("hello!"));
exit(0);
}
else
{
//执行父进程
printf("Parent process is starting...\n");
//父进程从管道读取子进程写的数据,关闭管道的写端
close(fd[OUTPUT]);
char buf[255];
int output=read(fd[INPUT),buf,sizeof(buf));
printf("%d bytes of data from child process:%s\n",output,buf);
}
return 0;
}
三、简述mmap的原理和使用场景
mmap是一种内存映射文件的方法。
原理是将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对应关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read、write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。如下图:
使用场景:
①对同一块区域频繁读写操作;
②可用于实现用户空间和内核空间的高效交互;
③可提供进程间共享内存及相互通信;
④可实现高效的大规模数据传输。
四、互斥量是否可以在进程中使用
能。
不同的进程之间,存在资源竞争或并发使用的问题,所以需要互斥量。
进程中也需要互斥量,因为一个进程中可以包含多个线程,线程与线程之间需要通过互斥的手段进行同步,避免导致共享数据修改引起冲突。可以使用互斥锁,属于互斥量的一种。
五、协程是轻量级线程,轻量级表现在哪里
1、协程调用与切换比线程效率高:协程执行效率极高。其不需要多线程的锁机制,可以不加锁的访问全局变量,所以上下文的切换非常快。
2、协程占用内存少:执行协程只需要极少的栈内存(大概是4~5KB),而默认情况下,线程栈的大小为1MB。
3、切换开销更小:协程直接操作栈基本没有内存切换的开销,所以切换开销比线程小。
六、简述常见信号及其含义
编号为1~31的信号为传统UNIX支持的信号,是不可靠信号(非实时的)。不可靠信号和可控信号的区别在于前者不支持排队,可能造成信号丢失,而后者不会。编号为1~31的信号如下:
代号 | 信号名称 | 说 明 |
1 | SIGHUP | 该信号让进程立即关闭,然后重新读取配置文件之后重启 |
2 | SIGINT | 程序中止信号,用于中止前台进程。相当于输出Ctrl+C快捷键 |
3 | SIGQUIT | 和SIGINT类似,但由于QUIT字符(通常是Ctrl-/)来控制,进程在因收到SIGQUIT退出时会产生core文件,在这个意义上类似于一个程序错误信号 |
4 | SIGILL | 执行了非法指令,通常是因为可执行文件本身出现错误,或者试图执行数据段,堆栈溢出时也有可能产生这个信号 |
5 | SIGTRAP | 由断点指令或其它trap指令产生,由debugger使用 |
6 | SIGBUS | 非法地址,包括内存地址对齐(alignment)出错 |
7 | SIGFPE | 在发生致命的算术运算错误时发出,不仅包括浮点运算错误,还包括溢出及除数为0等其他所有的算术运算错误 |
8 | SIGKILL | 用来立即结束程序的运行,该信号不能被阻塞、处理和忽略,一般用于强制中止进程 |
9 | SIGUSR1 | 留给用户使用 |
10 | SIGSEGV | 试图访问未分配给自己的内存,或试图往没有写权限的内存地址写数据 |
11 | SIGUSR2 | 留给用户使用 |
12 | SIGPIPE | 管道破裂,该信号通常在进程间通信产生,比如采用FIFO(官道)通信的两个进程,读管道没有打开或意外终止就向管道写,写进程会收到SIGPIPE信号 |
13 | SIGALRM | 时钟定时信号,计算的是实际的时间或时钟时间,alarm函数使用该信号 |
14 | SIGABRT | 调用abort函数生成的信号 |
15 | SIGTERM | 正常结束进程的信号,kill命令的默认信号。如果进程已经发生了问题,那么这个信号是无法正常中止进程的,这时我们才会尝试SIGKILL信号,也就是信号8 |
17 | SIGCHLD | 子进程结束时,父进程会收到这个信号 |
18 | SIGCONT | 该信号可以让暂停的进程恢复执行,本信号不能被阻断 |
19 | SIGSTOP | 该信号可以暂停前台进程,相当于输入Ctrl+Z快捷键,本信号不能被阻断 |
20 | SIGTSTP | 停止进程的运行,但该信号可以被处理和忽略,用户键入SUSP字符时(通常是Ctrl-Z)发出这个信号 |
21 | SIGTTIN | 当后台作业要从用户终端读取数据时,该作业中的所有进程会收到SIGTTIN信号,缺省时这些进程会停止执行 |
22 | SIGTTOU | 类似于SIGTTIN,但在写终端(或修改终端模式)时收到 |
23 | SIGURG | 有“紧急”数据或out-of-band数据到达socket时产生 |
24 | SIGXCPU | 超过CPU时间资源限制,这个限制可以由getrlimit/setrlimit来读取/改变 |
25 | SIGXFSZ | 当进程企图扩大文件以至于超过文件大小资源限制 |
26 | SIGVTALRM | 虚拟时钟信号,类似于SIGALRM,但是计算的是该进程占用的CPU时间 |
27 | SIGPROF | 类似于SIGALRM/SIGVTALRM,但包括该进程用的CPU时间以及系统调用的时间 |
28 | SIGWINCH | 窗口大小改变时发出 |
29 | SIGIO | 文件描述符准备就绪,可以开始进行输入/输出操作 |
30 | SIGPWR | Power failure |
31 | SIGSYS | 非法的系统调用 |
其中常见信号为SIGHUP、SIGINT、SIGFPE、SIGKILL、SIGALRM、SIGTERM、SIGCHLD、SIGCONT和SIGSTOP。