Linux信号
先上 demo 吧。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
void func(int sig)
{
printf("接收到了信号 %d\n", sig);
}
void alarmfunc(int sig)
{
printf("接收到了时钟信号%d\n", sig);
// 如何解决只能用一次? 递归调用
alarm(3);
}
int main()
{
for (int ii=0;ii<=64;ii++)
{
// 如果接收到了ii这个信号, 就调用func这个函数
signal(ii, func);
}
signal(SIGALRM, alarmfunc);
alarm(3); // 只能用一次
/*
signal(15, SIG_IGN); // 忽略这个信号
signal(15, SIG_DFL); // 设置为系统默认操作
// 9不能被捕获, 也不能被忽略
signal(9, SIG_IGN); // 无法忽略9
*/
while (1)
{
printf("执行了一次任务\n");
sleep(1);
}
return 0;
}
上面的demo有几个点:
(1)killall book 默认是15
(2)信号9,不能被捕获, 也不能被忽略。可以用 9 强制杀死进程,程序突然死亡
(3)ctrl + c 是信号2
(4)kill 的格式如下:kill -15 23471
用ps -ef | grep book
获取id
信号可以用于通知进程,让进程处理善后工作
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
void EXIT(int sig)
{
printf("收到了信号 %d, 程序将进行善后工作后退出\n", sig);
// 这里写善后的代码
exit(0);
}
int main()
{
for (int ii=0;ii<=64;ii++) signal(ii, SIG_IGN); // 忽略所有信号,不希望程序被干扰
// 设置Ctrl+c和kill或killall的处理函数
signal(SIGINT, EXIT);
signal(15,EXIT);
while (1)
{
printf("执行了一次任务\n");
sleep(1);
}
return 0;
}
再补充几个点:
1)pid>0 将信号传给进程号为pid 的进程。
2)pid=0 将信号传给和目前进程相同进程组的所有进程,常用于父进程给子进程发送信号,注意,发送信号者进程也会收到自己发出的信号。
3)pid=-1 将信号广播传送给系统内所有的进程,例如系统关机时,会向所有的登录窗口广播关机信息。
Linux多进程
Linux的0、1、2号进程
- idle进程:系统创建的第一个进程,加载系统。
- systemd进程:系统初始化,是所有其它用户进程的祖先。
- kthreadd进程:负责所有内核线程的调度和管理。
往上回溯(ps -ef | grep 999
)可以发现用户进程的祖先是 1。
fork
(1) 一个现有的进程调用函数fork创建一个新的进程
(2)子进程和父进程继续执行fork函数后的代码
(3)fork函数调用一次,返回两次
(4)子进程返回0,父进程返回子进程的进程ID
(5)子进程是父进程的副本
(6)子进程获得了父进程的数据控件、堆和栈的副本,不是共享
(7)父进程中打开的文件描述符也会被复制进子进程中
(8)如果父进程先退出,子进程会变为孤儿进程
(9)如果子进程先退出,内核会向父进程发送SIGCHLD信号,如果父进程不处理这个信号,子进程会成为僵尸进程
(1)(2)demo:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
int main()
{
printf("aaa=%d\n", getpid());
sleep(10);
fork();
printf("bbb=%d\n", getpid());
sleep(10);
return 0;
}
创建新的进程:
[wsm@iZ2vchi15pdv9v0bwzok5kZ ~]$ ps -ef | grep book
wsm 825 706 0 12:32 pts/4 00:00:00 vim book.cpp
wsm 1504 778 0 12:43 pts/5 00:00:00 ./book
wsm 1530 894 0 12:43 pts/6 00:00:00 grep --color=auto book
wsm 22918 22496 0 09:05 pts/0 00:00:00 vim book.cpp
[wsm@iZ2vchi15pdv9v0bwzok5kZ ~]$ ps -ef | grep book
wsm 825 706 0 12:32 pts/4 00:00:00 vim book.cpp
wsm 1504 778 0 12:43 pts/5 00:00:00 ./book
wsm 1531 1504 0 12:43 pts/5 00:00:00 ./book
wsm 1533 894 0 12:43 pts/6 00:00:00 grep --color=auto book
wsm 22918 22496 0 09:05 pts/0 00:00:00 vim book.cpp
(3)(4)demo
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
int main()
{
printf("父进程的pid是:%d\n", getpid()); // 这里不加\n的话,输出在缓冲,输出两次
int pid = fork();
if (pid == 0)
{
printf("这是子进程 %d\n", getpid());
sleep(2);
}
else if (pid > 0)
{
printf("这是父进程 %d\n", getpid());
sleep(2);
}
return 0;
}
(5)(6)(7)demo
操作文本
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
int main()
{
FILE *fp = fopen("/tmp/tmp.txt", "w+");
fprintf(fp,"我要成为优秀的程序员\n");
fflush(fp); // 将缓冲写入文本
int pid = fork();
if (pid == 0)
{
printf("子进程 %d\n", getpid());
fprintf(fp, "aaa\n");
sleep(1);
}
else if (pid > 0)
{
printf("父进程 %d\n", getpid());
fprintf(fp, "bbb\n");
sleep(1);
}
fclose(fp);
return 0;
}
运行结果
[wsm@iZ2vchi15pdv9v0bwzok5kZ c]$ cat /tmp/tmp.txt
我要成为优秀的程序员
bbb
aaa
子进程关闭文件指针对父进程没有影响;父进程关闭文件指针对子进程也没有影响。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
int main()
{
FILE *fp = fopen("/tmp/tmp.txt", "w+");
fprintf(fp,"我要成为优秀的程序员\n");
fflush(fp); // 将缓冲写入文本
int pid = fork();
if (pid == 0)
{
printf("子进程 %d\n", getpid());
fclose(fp);
}
else if (pid > 0)
{
printf("父进程 %d\n", getpid());
fprintf(fp, "bbb\n"); sleep(1);
fprintf(fp, "bbb\n"); sleep(1);
fprintf(fp, "bbb\n"); sleep(1);
fprintf(fp, "bbb\n"); sleep(1);
fclose(fp);
}
return 0;
}
结果
[wsm@iZ2vchi15pdv9v0bwzok5kZ c]$ cat /tmp/tmp.txt
我要成为优秀的程序员
bbb
bbb
bbb
bbb
(8)
孤儿进程由 1号进程 接管
[wsm@iZ2vchi15pdv9v0bwzok5kZ c]$ ps -ef | grep book
wsm 4508 706 0 13:38 pts/4 00:00:00 vim book.cpp
wsm 4687 778 0 13:41 pts/5 00:00:00 ./book
wsm 4688 4687 0 13:41 pts/5 00:00:00 ./book
wsm 4698 894 0 13:41 pts/6 00:00:00 grep --color=auto book
wsm 22918 22496 0 09:05 pts/0 00:00:00 vim book.cpp
[wsm@iZ2vchi15pdv9v0bwzok5kZ c]$ ps -ef | grep book
wsm 4508 706 0 13:38 pts/4 00:00:00 vim book.cpp
wsm 4688 1 0 13:41 pts/5 00:00:00 ./book
wsm 4700 894 0 13:41 pts/6 00:00:00 grep --color=auto book
wsm 22918 22496 0 09:05 pts/0 00:00:00 vim book.cpp
(9)
观察僵尸进程,sleep不同的秒数实现
[wsm@iZ2vchi15pdv9v0bwzok5kZ c]$ ps -ef | grep book
wsm 825 706 0 12:32 pts/4 00:00:01 vim book.cpp
wsm 3555 778 0 13:23 pts/5 00:00:00 ./book
wsm 3556 3555 0 13:23 pts/5 00:00:00 ./book
wsm 3584 894 0 13:23 pts/6 00:00:00 grep --color=auto book
wsm 22918 22496 0 09:05 pts/0 00:00:00 vim book.cpp
[wsm@iZ2vchi15pdv9v0bwzok5kZ c]$ ps -ef | grep book
wsm 825 706 0 12:32 pts/4 00:00:01 vim book.cpp
wsm 3555 778 0 13:23 pts/5 00:00:00 ./book
wsm 3556 3555 0 13:23 pts/5 00:00:00 [book] <defunct>
wsm 3587 894 0 13:23 pts/6 00:00:00 grep --color=auto book
wsm 22918 22496 0 09:05 pts/0 00:00:00 vim book.cpp
僵尸进程过多会占用系统资源(如进程号)
解决僵尸进程的三种方法
一、忽略SIGCHLD信号
signal(SIGCHLD, SIG_IGN); // 忽略这个信号
二、wait函数
但是wait会阻塞在那里等
else if (pid > 0)
{
printf("父进程 %d\n", getpid());
int sts;
wait(&sts);
sleep(20);
}
三、信号处理函数
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
void func(int sig)
{
int sts;
wait(&sts);
}
int main()
{
signal(SIGCHLD, func);
int pid = fork();
if (pid == 0)
{
printf("子进程 %d\n", getpid());
sleep(10);
}
if (pid > 0)
{
printf("父进程 %d\n", getpid());
sleep(20);
}
return 0;
}
结果,发现父进程进入信号处理函数后没有再sleep了。查了下资料,说是无法保留上下文信息。
[wsm@iZ2vchi15pdv9v0bwzok5kZ c]$ ps -ef | grep book
wsm 825 706 0 12:32 pts/4 00:00:01 vim book.cpp
wsm 4369 778 0 13:36 pts/5 00:00:00 ./book
wsm 4370 4369 0 13:36 pts/5 00:00:00 ./book
wsm 4402 894 0 13:36 pts/6 00:00:00 grep --color=auto book
wsm 22918 22496 0 09:05 pts/0 00:00:00 vim book.cpp
[wsm@iZ2vchi15pdv9v0bwzok5kZ c]$ ps -ef | grep book
wsm 825 706 0 12:32 pts/4 00:00:01 vim book.cpp
wsm 4404 894 0 13:36 pts/6 00:00:00 grep --color=auto book
wsm 22918 22496 0 09:05 pts/0 00:00:00 vim book.cpp
可以再加一个sleep解决
if (pid > 0)
{
printf("父进程 %d\n", getpid());
sleep(20);
sleep(5);
}
服务程序的调度
fork + exec家族。秀!
exec
exec可以调用系统命令。
exec是用参数中指定程序替换了当前进程中的正文段、数据段、堆和栈。
所以下面的这个程序不会运行exec后面的代码。exec正常运行的话后面的代码将不被执行
execl
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
printf("aaa\n");
execl("/bin/ls", "/bin/ls", "-lt", "/tmp/tmp.txt", (char*)0);
printf("bbb\n");
return 0;
}
输出:
[wsm@iZ2vchi15pdv9v0bwzok5kZ c]$ ./procct1
aaa
-rw-r--r-- 1 wsm dba 55 Feb 15 13:18 /tmp/tmp.txt
可以和fork结合:
先执行fork函数,创建一个子进程,让子进程调用execl执行新的程序。新程序将替换子进程,不会影响父进程。在父进程中,可以调用wait函数等待新程序运行的结果,这样就可以实现调度的功能。
这样就实现了简单的结合
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
// ./procct1 5 /bin/ls -lt /tmp/tmp.txt
while (true)
{
if (fork() == 0)
{
execl(argv[2], argv[2], argv[3], argv[4], (char*)0);
// execl("/bin/ls", "/bin/ls", "-lt", "/tmp/tmp.txt", (char*)0);
}
else
{
int status;
wait(&status);
sleep(atoi(argv[1]));
}
}
return 0;
}
execv
但是这样有一个问题,参数不定长,难道要写十二十个if判断来处理参数吗?
使用 execv 来处理
char* pargv[argc];
for (int ii = 2; ii < argc; ii++)
pargv[ii - 2] = argv[ii];
pargv[argc - 2] = NULL;
while (true)
{
if (fork() == 0)
{
execv(argv[2], pargv);
exit(0); //execv调用失败被执行,不加死循环
}
else
{
int status;
wait(&status);
sleep(atoi(argv[1]));
}
}
实现调度程序
关闭所有的信号和IO
for (int ii = 0; ii < 64; ii++)
{
signal(ii, SIG_IGN);
close(ii);
}
生成子进程,父进程退出,让程序运行在后台,由一号进程接管
if (fork() != 0) exit(0);
启用SIGCHLD信号,让父进程可以wait子进程退出的状态(僵尸进程)
signal(SIGCHLD, SIG_DFL);
Linux共享内存
主要是几个API
下面给出这几个API的使用示范
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
struct st_pid
{
int pid; // 进程编号
char name[51]; //进程名称
};
int main(int argc, char *argv[])
{
// 共享内存的标志
int shmid;
// 获取或创建共享内存,键值为0x5005
if ((shmid = shmget(0x5005, sizeof(struct st_pid), 0640|IPC_CREAT)) == -1)
{
printf("shmget(0x5050) failed\n");
return -1;
}
// 用于指向共享内存的结构体变量
struct st_pid *stpid=0;
// 把共享内存连接到当前进程的地址空间
if ((stpid = (struct st_pid*)shmat(shmid,0,0)) == (void *)-1)
{
printf("shmat failed\n");
return -1;
}
printf("pid=%d,name=%s\n", stpid->pid, stpid->name);
stpid->pid = getpid();
strcpy(stpid->name, argv[1]);
printf("pid=%d,name=%s\n", stpid->pid, stpid->name);
// 把共享内存从当前进程中分离
shmdt(stpid);
// 删除共享内存
if (shmctl(shmid, IPC_RMID, 0) == -1)
{
printf("shmctl failed\n");
return -1;
}
return 0;
}
命令行查看和删除共享内存的方法
[wsm@iZ2vchi15pdv9v0bwzok5kZ c]$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00005005 0 wsm 640 56 0
[wsm@iZ2vchi15pdv9v0bwzok5kZ c]$ ipcrm -m 0
[wsm@iZ2vchi15pdv9v0bwzok5kZ c]$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
Linux信号量
用于给共享资源加锁。P减一,V加一
一个信号量的 demo,这里被封装得很好,直接init后调PV(可以带参)。
#include "_public.h"
CSEM sem; // 用于给共享内存加锁的信号量
struct st_pid
{
int pid; // 进程编号
char name[51]; //进程名称
};
int main(int argc, char *argv[])
{
if (argc < 2)
{
printf("Using:./book procname\n");
return 0;
}
// 共享内存的标志
int shmid;
// 获取或创建共享内存,键值为0x5005
if ((shmid = shmget(0x5005, sizeof(struct st_pid), 0640|IPC_CREAT)) == -1)
{
printf("shmget(0x5050) failed\n");
return -1;
}
// 如果信号量已存在,获取信号量;如果信号量不存在,则创建它并初始化为value
if (sem.init(0x5005) == false)
{
printf("sem init(0x5005) failed\n");
return -1;
}
// 用于指向共享内存的结构体变量
struct st_pid *stpid=0;
// 把共享内存连接到当前进程的地址空间
if ((stpid = (struct st_pid*)shmat(shmid,0,0)) == (void *)-1)
{
printf("shmat failed\n");
return -1;
}
printf("aaa time=%d, val=%d\n", time(0), sem.value());
// 加锁
sem.P();
printf("bbb time=%d, val=%d\n", time(0), sem.value());
printf("pid=%d,name=%s\n", stpid->pid, stpid->name);
stpid->pid = getpid(); // 进程编号
strcpy(stpid->name, argv[1]); // 进程名称
sleep(10);
printf("pid=%d,name=%s\n", stpid->pid, stpid->name);
// 解锁
printf("ccc time=%d, val=%d\n", time(0), sem.value());
sem.V();
printf("ddd time=%d, val=%d\n", time(0), sem.value());
// 把共享内存从当前进程中分离
shmdt(stpid);
return 0;
}
编译命令
g++ -g -o book book.cpp -I/project/public /project/public/_public.cpp
四个窗口,分别是aaa,bbb,ccc,ddd
[wsm@iZ2vchi15pdv9v0bwzok5kZ c]$ ./book aaa
aaa time=1676507864, val=1
bbb time=1676507864, val=0
pid=0,name=
pid=8445,name=aaa
ccc time=1676507874, val=0
ddd time=1676507874, val=0
[wsm@iZ2vchi15pdv9v0bwzok5kZ c]$ ./book bbb
aaa time=1676507866, val=0
bbb time=1676507874, val=0
pid=8445,name=aaa
pid=8447,name=bbb
ccc time=1676507884, val=0
ddd time=1676507884, val=0
[wsm@iZ2vchi15pdv9v0bwzok5kZ c]$ ./book ccc
aaa time=1676507867, val=0
bbb time=1676507884, val=0
pid=8447,name=bbb
pid=8449,name=ccc
ccc time=1676507894, val=0
ddd time=1676507894, val=0
[wsm@iZ2vchi15pdv9v0bwzok5kZ c]$ ./book ddd
aaa time=1676507868, val=0
bbb time=1676507894, val=0
pid=8449,name=ccc
pid=8451,name=ddd
ccc time=1676507904, val=0
ddd time=1676507904, val=1
观察到获得锁的时间就是上一个释放锁的时间,直到最后结束了锁才为1
进程的心跳机制
共享内存放进程的信息
对于一个操作共享内存的对象来说,他有以下几个功能需要实现
class PActive
{
private:
CSEM m_sem; // 用于给共享内存加锁的信号量id
int m_shmid; // 共享内存的id
int m_pos; // 共享进程在共享内存进程组中的位置
struct st_pinfo *m_shm; // 指向共享内存的地址空间
public:
PActive(); // 初始化成员变量
~PActive(); // 从共享内存中删除当前进程的心跳信息
bool AddInfo(const int timeout, const char *pname); // 把当前进程的心跳信息加入共享内存中
bool UptATime();// 更新共享内存中当前进程的心跳信息
};
心跳信息的结构体如下
// 进程心跳信息的结构体
struct st_pinfo
{
int pid; // 进程id
char pname[51]; // 进程名称,可以为空
int timeout; // 超时时间,单位:秒
time_t atime; // 最后一次心跳的时间,用整数表示
};
下面是实现的一个demo
#include "_public.h"
#define MAXNUMP_ 1000 // 最大的进程数量
#define SHMKEYP_ 0x5095 // 共享内存的key
#define SEMKEYP_ 0x5095 // 信号量的key
// 进程心跳信息的结构体
struct st_pinfo
{
int pid; // 进程id
char pname[51]; // 进程名称,可以为空
int timeout; // 超时时间,单位:秒
time_t atime; // 最后一次心跳的时间,用整数表示
};
class PActive
{
private:
CSEM m_sem; // 用于给共享内存加锁的信号量id
int m_shmid; // 共享内存的id
int m_pos; // 共享进程在共享内存进程组中的位置
struct st_pinfo *m_shm; // 指向共享内存的地址空间
public:
PActive(); // 初始化成员变量
~PActive(); // 从共享内存中删除当前进程的心跳信息
bool AddInfo(const int timeout, const char *pname); // 把当前进程的心跳信息加入共享内存中
bool UptATime();// 更新共享内存中当前进程的心跳信息
};
int main(int argc, char *argv[])
{
if (argc < 2) {printf("Using:./book procname\n"); return 0;}
PActive Active;
Active.AddInfo(30, argv[1]);
while(true)
{
Active.UptATime();
sleep(10);
}
return 0;
}
// 初始化成员变量
PActive::PActive()
{
m_shmid=-1; // 共享内存的id
m_pos=-1; // 共享进程在共享内存进程组中的位置
m_shm=(struct st_pinfo *)0; // 指向共享内存的地址空间
}
// 从共享内存中删除当前进程的心跳信息
PActive::~PActive()
{
// 把当前进程从共享内存中移出
if (m_pos != -1)
memset(m_shm+m_pos,0,sizeof(struct st_pinfo));
// 把共享内存从当前进程中分离
if (m_shm != 0)
shmdt(m_shm);
}
// 把当前进程的心跳信息加入共享内存中
bool PActive::AddInfo(const int timeout, const char *pname)
{
if (m_pos != -1) return true;
// 创建/获取共享内存, 大小为 n*sizeof(struct st_pinfo)
if ( (m_shmid=shmget(SHMKEYP_, MAXNUMP_*sizeof(struct st_pinfo), 0640|IPC_CREAT)) == -1)
{ printf("shmget(%x) failed\n",SHMKEYP_); return false;}
if ( (m_sem.init(SEMKEYP_) == false))
{ printf("m_sem.init(%x) failed\n",SEMKEYP_); return false;}
// 将共享内存连接到当前进程的地址空间
m_shm = (struct st_pinfo *) shmat(m_shmid,0,0);
// 创建当前进程心跳信息结构体变量,把本进程的信息填进去
struct st_pinfo stpinfo;
memset(&stpinfo, 0, sizeof(struct st_pinfo));
stpinfo.pid = getpid(); // 进程id
STRNCPY(stpinfo.pname, sizeof(stpinfo.pname), pname, 50); // 进程名称
stpinfo.timeout=timeout; // 超时时间,单位:秒
stpinfo.atime=time(0); // 最后一次心跳时间
// 如果共享内存中存在当前进程编号,一定是其它进程残留的数据,当前进程就重用该位置
for (int ii=0;ii<MAXNUMP_;ii++)
if (m_shm[ii].pid==stpinfo.pid)
{
m_pos=ii;
break;
}
m_sem.P(); // 加锁
// 在共享内存中查找一个空位置, 把当前进程的心跳信息存入共享内存中
if (m_pos==-1)
{
for (int ii=0;ii<MAXNUMP_;ii++)
{
if ( (m_shm+ii)->pid == 0) //(m_shm+ii)是一个指针 但是m_shm[ii]不是,数组形式用.
{
// 找到了一个空位置
m_pos=ii;
break;
}
}
}
if (m_pos == -1)
{
m_sem.V();
printf("共享内存已用完\n");
return false;
}
// 把当前进程的心跳信息存入共享内存的进程组中
memcpy(m_shm+m_pos,&stpinfo,sizeof(struct st_pinfo));
m_sem.V(); // 解锁
return true;
}
// 更新共享内存中当前进程的心跳信息
bool PActive::UptATime()
{
if (m_pos == -1) return false;
// 更新共享内存中本进程的心跳时间
m_shm[m_pos].atime=time(0);
return true;
}
经测试,达到预期
(gdb) p Active.m_shm[1]
$1 = {pid = 13678, pname = "bbb", '\000' <repeats 47 times>, timeout = 30, atime = 1676514151}
(gdb) p Active.m_shm[0]
$2 = {pid = 13676, pname = "aaa", '\000' <repeats 47 times>, timeout = 30, atime = 1676514149}
(gdb) p Active.m_shm[2]
$3 = {pid = 13698, pname = "ccc", '\000' <repeats 47 times>, timeout = 30, atime = 1676514171}
原来那个开发框架已经将这个实现了
#include "_public.h"
int main(int argc, char *argv[])
{
if (argc < 2) {printf("Using:./book procname\n"); return 0;}
CPActive Active;
Active.AddPInfo(30, argv[1]);
while(true)
{
Active.UptATime();
sleep(10);
}
return 0;
}
测试结果和上面的一样
守护进程
守护程序的作用是“检查后台服务程序是否超时, 如果已超时, 就终止它”。由另一个程序启动。
此时的心跳程序是这样的
#include "_public.h"
void EXIT(int sig)
{
printf("sid=%d\n", sig);
exit(0);
}
int main(int argc, char *argv[])
{
if (argc < 3) {printf("Using:./book procname timeout\n"); return 0;}
// 设置2和15的信号
signal(2,EXIT); signal(15,EXIT);
CPActive Active;
Active.AddPInfo(atoi(argv[2]), argv[1]);
while(true)
{
Active.UptATime();
sleep(10);
}
return 0;
}
其能够正常捕获中断,但是并没有执行清除工作。
解决方法是将CPActive Active
变为全局变量,能够实现退出时清理。(改成return不行,无法结束)
原因是:exit函数不会调用局部对象的析构函数;但是会调用全局对象的析构函数。return会调用局部和全局的析构函数
2023-02-16 15:41:35 ii=0,pid=29758,pname=aaa,timeout=60,atime=1676532291
2023-02-16 15:41:35 ii=1,pid=29764,pname=bbb,timeout=70,atime=1676533287
2023-02-16 15:41:35 ii=2,pid=29770,pname=ccc,timeout=80,atime=1676533293
2023-02-16 15:41:35 ii=3,pid=31081,pname=ddd,timeout=60,atime=1676533294
2023-02-16 15:41:43 ii=0,pid=29758,pname=aaa,timeout=60,atime=1676532291
2023-02-16 15:41:43 ii=1,pid=29764,pname=bbb,timeout=70,atime=1676533297
2023-02-16 15:41:43 ii=2,pid=29770,pname=ccc,timeout=80,atime=1676533293
最后 checkproc.cpp
是这样:
#include "_public.h"
// 程序运行的日志
CLogFile logfile;
int main(int argc, char *argv[])
{
// 程序的帮助
if (argc != 2)
{
printf("\n");
printf("Using:./checkproc logfilename\n");
printf("Example:/project/toosl/bin/procctl 10 /project/tools/bin/checkproc /tmp/log/checkproc.log\n\n");
printf("本程序用于检查后台服务程序是否超时, 如果已超时, 就终止它\n");
printf("注意:\n");
printf(" 1) 本程序由procctl启动, 建议运行周期为10秒\n");
printf(" 2) 为了避免被普通用户误杀, 本程序应该用root用户启动\n");
printf(" 3) 如果要停止本程序, 只能用killall -9 终止\n\n\n");
return 0;
}
// 忽略全部的信号, 不希望程序被干扰
CloseIOAndSignal(true);
// 打开日志文件
if (logfile.Open(argv[1],"a+") == false)
{printf("logfile Open(%s) failed.\n", argv[1]); return -1;}
int shmid = 0;
// 创建/获取共享内存 键值SHMKEYP 大小MAXNUMP
if ( (shmid = shmget((key_t)SHMKEYP, MAXNUMP*sizeof(struct st_procinfo), 0666|IPC_CREAT)) == -1)
{
logfile.Write("创建/获取共享内存(%x)失败\n", SHMKEYP);
return false;
}
// 将共享内存连接到当前进程的地址空间
struct st_procinfo *shm = (struct st_procinfo *)shmat(shmid, 0, 0);
// 遍历共享内存中的全部的进程心跳记录
for (int ii=0;ii<MAXNUMP;ii++)
{
// 如果pid==0, 空记录, continue
if (shm[ii].pid == 0) continue;
// 如果pid!=0, 表示是服务程序的心跳记录(程序稳定后不用写了)
// logfile.Write("ii=%d,pid=%d,pname=%s,timeout=%d,atime=%d\n", \
// ii, shm[ii].pid, shm[ii].pname, shm[ii].timeout, shm[ii].atime);
// 向进程发送信号0, 判断它是否还存在, 如果不存在, 从共享内存中删除该记录, continue
int iret = kill(shm[ii].pid, 0);
if (iret == -1)
{
logfile.Write("进程pid=%d(%s)已经不存在\n",(shm+ii)->pid,(shm+ii)->pname);
memset(shm+ii,0,sizeof(struct st_procinfo));
continue;
}
// 如果进程未超时, continue
time_t nowt = time(0); // 当前时间
if (nowt - shm[ii].atime < shm[ii].timeout) continue;
// 如果已超时
logfile.Write("进程pid=%d(%s)已经超时\n",(shm+ii)->pid,(shm+ii)->pname);
// 发送信号15, 尝试正常终止进程
kill(shm[ii].pid,15);
for(int jj=0;jj<5;jj++)
{
sleep(1);
iret = kill(shm[ii].pid, 0);
if (iret == -1) break; //进程已退出
}
// 如果进程仍存在, 就发送信号9, 强制终止它
if (iret == -1)
{
logfile.Write("进程pid=%d(%s)已经正常中止\n",(shm+ii)->pid,(shm+ii)->pname);
}
else
{
kill(shm[ii].pid, 9);
logfile.Write("进程pid=%d(%s)已经强制中止\n",(shm+ii)->pid,(shm+ii)->pname);
}
// 从共享内存中删除已超时进程的心跳记录
memset(shm+ii,0,sizeof(struct st_procinfo));
}
// 把共享内存从当前进程中分离
shmdt(shm);
return 0;
}
运行服务程序
/etc/rc.local
里面配置运行脚本,可以让服务器启动就自动执行。
# Please note that you must run 'chmod +x /etc/rc.d/rc.local' to ensure
# that this script will be executed during boot.
touch /var/lock/subsys/local
# 检查服务程序是否超时 (守护进程用root启动)
/project/tools1/bin/procctl 30 /project/tools1/bin/checkproc
# 启动数据中心的后台服务 (普通脚本由普通用户启动)
su - wsm -c "/bin/sh /project/idc1/c/start.sh"
定时启动进程(多个):procctl
。由1号进程监管。
守护进程:维护共享内存,定时清理超时进程和已经死了的进程。被杀了的也没关系,会被procctl再次调用。
查看进程的命令:
ps -ef | grep procctl