项目学习地址:【牛客网C++服务器项目学习】
day05
1.Linux中查看进程信息
- ls /proc/
前面蓝色数字代表的进程的ID。如果你想查看PID为1的进程信息,你需要查看/porc/1这个文件夹
-
我们也可以使用ps -ef -aux指令来直接显示进程状态或者使用ps aux/auj
-
top:动态显示进程状态
-
kill:杀死进程
插一嘴:
今天在腾讯云上开了一个云服务器用于替代VMware虚拟机,由于腾讯云的centos7.6安装的gcc版本为4.8.5,默认不支持c99以后的标准,这带来的问题就是,c99之前的标准不支持在for循环中声明和初始化变量,于是我把centos重装为了8.0,gcc版本为8.2便解决了该问题
但是这样又带来了一个新的问题,vscode的remote远程开发连不上了。这是因为vscode保存了之前连接服务器的ip和密钥,重置了服务器后,需要修改vscode中保存的这部分信息。具体的操作方法请查看这篇文章
vscode配置远程连接失败:过程试图写入的管道不存在(已解决)
2.进程
-
进程的创建fork
-
详细内容可以看这篇文章Linux下fork函数详解
-
fork的中文含义是叉子,对应于进程的创建,非常的形象。父进程(一般为执行程序)调用fork函数后,此时程序的执行状态就分成了两个叉子,系统在父进程的基础上,创建了子进程,子进程复制了父进程的所有信息。有区别的是,父进程和子进程的PCB进程号是不同的,每一个进程都有自己独立的进程号。区分父进程和子进程的是fork函数的返回值:在父进程中返回的是子进程的pid进程号,子进程中返回的是0。(fork函数一旦开始执行,系统就会为子进程分配资源,子进程拷贝了父进程的所有信息,父进程和子进程都要继续执行fork函数的相关命令,最后在两个进程中都会有fork函数的返回值。通过返回值区别父子进程)
-
Linux系统为了追求效率和减少资源浪费。开辟进程时,执行的是:读时共享、写时复制。
-
-
GDB调试进程
-
GDB调试默认跟踪一个进程,父进程。在程序上打断点,只对父进程程序有效,子进程正常运行。如果要设置跟踪子进程,有以下的命令
-
show/set follow-fork-mode:默认parent,可以修改为child。GDB跟踪子进程,不再跟踪父进程
-
show/set detach-on-fork:默认on,表示调试时进程分离。设置为off后,未被跟踪的进程阻塞在fork函数处
-
inferiors:show inferiors,显示调试的进程、inferiors id,切换调试的进程、detach inferiors id,使某个进程脱离调试
-
-
-
exec函数族
-
进程退出、孤儿进程、僵尸进程
-
进程退出:exit()函数
-
孤儿进程:
- 父进程运行结束退出,但是子进程还在运行,这样的子进程被称为孤儿进程
- 孤儿进程会被内核指定新的父进程为init进程,进程号为1.
-
僵尸进程:
-
一个进程结束退出后,用户区的数据由当前进程自己释放,归还操作系统、内核区的数据由父进程释放;
-
子进程已结束,但父进程迟迟不调用wait函数等去回收子进程的内核区数据,子进程的进程号会一直保存在操作系统中,这样的进程称为僵尸进程
-
若产生大量的僵尸进程,就可能没办法创建新的进程,故,僵尸进程不可取。
-
-
-
wait函数
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus)
-
作用:回收子进程。父进程执行wait函数后,会立刻阻塞自己,等待子进程的状态转变为僵尸态,回收该子进程,然后父进程继续执行
-
参数:wstatus
-
参数可以传递一个int指针,wait函数正确执行后,会将子进程结束的方式传递给这个int指针,通过下面几个函数,可以得知子进程结束的状态:
- WIFEXITED(status) 这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值。
- WEXITSTATUS(status) 当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出,WEXITSTATUS(status) 就会返回5;如果子进程调用exit(7),WEXITSTATUS(status)就会返回7。请注意,如果进程不是正常退出的,也就是说, WIFEXITED返回0,这个值就毫无意义。
-
参数也可以是传递为值、或者NULL,表示我们不需要记录子进程结束的状态。
-
-
返回值:
- > 0 : 返回子进程的id
- = -1 :错误,或者没有子进程了
- waitpid函数
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);
-
功能:回收指定进程号的子进程,可以设置是否阻塞。
-
参数:
- - pid:
pid > 0 : 某个子进程的pid
pid = 0 : 回收当前进程组的所有子进程
pid = -1 : 回收所有的子进程,相当于 wait() (最常用)
pid < -1 : 某个进程组的组id的绝对值,回收指定进程组中的子进程 - - options:设置阻塞或者非阻塞
0 : 阻塞
WNOHANG : 非阻塞
- - pid:
-
- 返回值:
> 0 : 返回子进程的id
= 0 : options=WNOHANG, 表示还有子进程或者
= -1 :错误,或者没有子进程了
waitpid和wait函数的功能基本上差不多的,最主要的区别在于waitpid可以让父进程不阻塞。
- 进程间通信有哪几种方式你知道吗?
Linux几乎支持全部UNIX进程间通信方法,包括管道(有名管道和无名管道)、消息队列、共享内存、
信号量和套接字。其中前四个属于同一台机器下进程间的通信,套接字则是用于网络通信。
- 无名管道
-
无名管道特点:
-
无名管道是一种特殊的文件,这种文件只存在于内存中。其实是一个在内核内存中维护的缓冲器。
-
无名管道只能用于父子进程或兄弟进程之间,必须用于具有亲缘关系的进程间的通信。
-
这是因为管道实际上是内存的一个缓冲区,
-
管道的写入端是一个文件描述符fd[1],对应的是文件x1;
-
管道的读取端也是一个文件描述符fd[0],对应的文件是x2
-
在进程之间通过管道通信,实际上是通过缓冲区+两个文件通信。如果两个进程没有关系(父子或者兄弟)则文件描述符对应的文件不是同一个文件,便无法通信。
-
-
无名管道只能由一端向另一端发送数据,是半双工方式,如果双方需要同时收发数据需要两个
管道。
-
-
相关接口:
-
int pipe(int fd[2]);
- fd[2]:管道两端用fd[0]和fd[1]来描述,读的一端用fd[0]表示,写的一端用fd[1]表示。通信双
方的进程中写数据的一方需要把fd[0]先close掉,读的一方需要先把fd[1]给close掉。
- fd[2]:管道两端用fd[0]和fd[1]来描述,读的一端用fd[0]表示,写的一端用fd[1]表示。通信双
-
-
无名管道读写的特点:
使用管道时,需要注意以下几种特殊的情况(假设都是阻塞I/O操作)
1.所有的指向管道写端的文件描述符都关闭了(管道写端引用计数为0),有进程从管道的读端
读数据,那么管道中剩余的数据被读取以后,再次read会返回0,就像读到文件末尾一样。2.如果有指向管道写端的文件描述符没有关闭(管道的写端引用计数大于0),而持有管道写端的进程也没有往管道中写数据,这个时候有进程从管道中读取数据,那么管道中剩余的数据被读取后,
再次read会阻塞,直到管道中有数据可以读了才读取数据并返回。3.如果所有指向管道读端的文件描述符都关闭了(管道的读端引用计数为0),这个时候有进程
向管道中写数据,那么该进程会收到一个信号SIGPIPE, 通常会导致进程异常终止。4.如果有指向管道读端的文件描述符没有关闭(管道的读端引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道中写数据,那么在管道被写满的时候再次write会阻塞,直到管道中有空位置才能再次写入数据并返回。
总结:
读管道:
管道中有数据,read返回实际读到的字节数。
管道中无数据:
写端被全部关闭,read返回0(相当于读到文件的末尾)
写端没有完全关闭,read阻塞等待
写管道:
管道读端全部被关闭,进程异常终止(进程收到SIGPIPE信号)
管道读端没有全部关闭:
管道已满,write阻塞
管道没有满,write将数据写入,并返回实际写入的字节数