1. 进程的基本概念
2. ps查看进程
- ps -ef 查看当前所有进程
- ps 查看当前中断的进程
- ps -ef | grep book 查看当前所有进程。从结果中过滤出包含"book"的记录
3. getpid库函数
获取程序运行时进程的编号
pid_t getpid();
4. fork
fork函数用于产生一个新的进程,函数返回值pid_t是一个整数,在父进程中,返回值是子进程编号,在子进程中,返回值是0
子进程拷贝了父进程的堆栈段和数据段,在父进程中定义的变量,子进程中会复制一个副本,fork之后,子进程对变量的操作不会影响父进程,父进程对变量的操作不会影响子进程。
5. 多进程的应用
6. 僵尸进程
僵尸进程是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。如果父进程先退出 ,子进程被init接管,子进程退出后init会回收其占用的相关资源。
一个进程在调用exit命令结束自己的生命的时候,其实它并没有真正的被销毁, 而是留下一个称为僵尸进程(Zombie)的数据结构(系统调用exit,它的作用是 使进程退出,但也仅仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁)
由于子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程 到底什么时候结束. 那么会不会因为父进程太忙来不及wait子进程,或者说不知道 子进程什么时候结束,而丢失子进程结束时的状态信息呢? 不会。因为UNⅨ提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息, 就可以得到。这种机制就是: 在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。直到父进程通过wait / waitpid来取时才释放. 但这样就导致了问题,如果进程不调用wait / waitpid的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。
解决方法:
如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCHLD,SIG_IGN) 通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收, 并不再给父进程发送信号。
signal(SIGCHLD, SIG_IGN);//忽略子进程退出的信号,避免产生僵尸进程
/*
* 程序名:demo48.cpp,此程序演示采用freecplus框架的CTcpServer类实现socket通信的服务端。
* 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include "_freecplus.h"
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
//父进程退出调用代码
void FathEXIT(int sig)
{
if(sig > 0){//把信号屏蔽掉
signal(sig, SIG_IGN);
signal(SIGINT, SIG_IGN);
signal(SIGTREM, SIG_IGN);
printf("catching the signal(%d).\n", sig);
}
kill(0, 15);//通知子进程,退出 发送给当前进程所属进程组里的所有进程
printf("父进程退出!\n");
exit(0);
}
void ChldEXIT(int sig)
{
if(sig > 0){
signal(sig, SIG_IGN);
signal(SIGINT, SIG_IGN);
signal(SIGTREM, SIG_IGN);
printf("catching the signal(%d).\n", sig);
}
printf("子进程退出!\n");
exit(0);
}
int main(int argc,char *argv[])
{
//关闭全部的信号
for(int ii=0; ii<100; ++ii)
{
signal(ii, SIG_IGN);
}
//设置信号,在shell状态下可用"kill + 进程号"正常终止线程
signal(SIGINT, FathExit);//ctrl+C
signal(SIGTERM, FathExit);//kill
signal(SIGCHLD, SIG_IGN);//忽略子进程退出的信号,避免产生僵尸进程
//如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCHLD,SIG_IGN) 通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收, 并不再给父进程发送信号。
if (argc!=2)
{
printf("Using:./demo48 port\nExample:./demo48 5005\n\n"); return -1;
}
CTcpServer TcpServer; // 创建服务端对象。
if (TcpServer.InitServer(atoi(argv[1]))==false) // 初始化TcpServer的通信端口。
{
printf("TcpServer.InitServer(%s) failed.\n",argv[1]); return -1;
}
while(true)
{
if (TcpServer.Accept()==false) // 等待客户端连接。
{
printf("TcpServer.Accept() failed.\n"); return -1;
}
if(fork() > 0)
{
TcpServer.CloseClient();//关闭父进程的客户端socket
continue;//核心代码
}
//子进程设置信号
signal(SIGINT, FathExit);//ctrl+C
signal(SIGTERM, FathExit);//kill
TcpServer.CloseListen();//关闭子进程的监听socket
printf("client(%s)connected\n",TcpServer.GetIP());
char strbuffer[1024]; // 存放数据的缓冲区。
while (true)
{
memset(strbuffer,0,sizeof(strbuffer));
//if (TcpServer.Read(strbuffer,300)==false) break; // 接收客户端发过来的请求报文。
if (TcpServer.Read(strbuffer,10)==false) break; // 接收客户端发过来的请求报文。
printf("receve:%s\n",strbuffer);
strcat(strbuffer,"ok"); // 在客户端的报文后加上"ok"。
printf("send:%s\n",strbuffer);
if (TcpServer.Write(strbuffer)==false) break; // 向客户端回应报文。
}
printf("client disconnect\n"); // 程序直接退出,析构函数会释放资源。
exit(0);
}
}