保证服务程序永不停机

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 23471ps -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

file

下面给出这几个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
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇