Linux高级编程——进程通信(信号signal)

进程间通信(IPC,Inter-Process Communication)

两个或多个进程间进行数据传送,参与通信的这些进程可能位于同一台机器上,也可能位于不同的机器上。

常用 IPC 方式:

  1. 信号(Signal)
  2. 管道(Pipe)
  3. 共享内存(Shared Memory)
  4. 消息队列(Message Queue)
  5. 套接字(Socket)

信号(Signal)

Linux 系统上古老经典的 IPC方式,主要用于实现异步通知,不能传输数据。

信号是一种软件中断(Software Break)

void (*signal(int sig, void (*func)(int)))(int)
  • sig – 在信号处理程序中作为变量使用的信号码。下面是一些重要的标准信号常量:
信号
SIGABRT (Signal Abort) 程序异常终止。
SIGFPE (Signal Floating-Point Exception) 算术运算出错,如除数为 0 或溢出(不一定是浮点运算)。
SIGILL (Signal Illegal Instruction) 非法函数映象,如非法指令,通常是由于代码中的某个变体或者尝试执行数据导致的。
SIGINT (Signal Interrupt) 中断信号,如 ctrl-C,通常由用户生成。
SIGSEGV (Signal Segmentation Violation) 非法访问存储器,如访问不存在的内存单元。
SIGTERM (Signal Terminate) 发送给本程序的终止请求信号。
  • func – 一个指向函数的指针。它可以是一个由程序定义的函数,也可以是下面预定义函数之一:
SIG_DFL 默认的信号处理程序。
SIG_IGN 忽视信号。

同步(Sync)

异步(Async)效率高

查看 Linux 系统支持的所有信号:kill -l命令

从-34号信号开始,信号没有固定意义,可以手动设置,并且是可靠的(有信号的处理时,来了信号,则进入队列,取队头进行处理)、实时的,前面的信号是不可靠(同上,没有队列,直接执行新的信号,原信号消失)、不实时的

信号的处理方式:

  • 默认处理(通常就是结束进程)
  • 忽略
  • 自定义处理(即给定某个信号指定一个信号处理函数,如果在某个时刻该进程接收到这个信号,进程就会暂停主程序的执行,转去调用信号处理函数,信号处理函数返回后再继续执行主程序)

信号产生的来源:

  • 自己或其他进程(raise 函数、kill 函数)
  • 系统内核(当用户按键或发生某些事件时,系统内核可能也会给对应的进程发送信号)

//当除数为0时,系统会发SIGFPE信号

#include 
#include <>

int main()
{
    //signal(SIGINT, SIG_IGN);//忽略SIGINT信号
    //signal(SIGHUP, SIG_IGN);//忽略SIGHUP信号
   	//忽略全部信号,除9号,9号信息无法被屏蔽,默认处理
    //-9 为SIGKILL 信号不能被忽略,也不能自定义处理,任何进程接收到该信号后默认行就是强制结束。该信号给系统管理员提高了一种结束进程的终极解决方案。(-15信号使进程正常退出)
    for(int i = 1; i < n)
    {
        signal(i,SIG_IGN);
    }
    while(1)
    {
        printf("hello\n");
    	sleep(1);
    }return 0;
}

向此程序法信号,首先得知道此程序的PID,利用kill()//向某个进程发送一个信号。
    命令行-》kill -2 PID (结束)// -2为-SIGINT,也就是ctrl+c    ctrl+z 为SIGTSTP
    命令行-》kill -1 PID (挂起)// -1为-SIGHUP
    // -19 -SIGSTOP (暂停) 不能被忽略,也不能自定义处理
    //若要继续运行则使用 -18 -SIGCONT (继续)
    //从-34号信号开始,信号没有固定意义,可以手动设置
    //SIGSEGV为段错误处理
    命令行-》kill -3 `ps -e | grep signal_demo | awk `{print $1}``
    //后面表示利用ps命令获取此进程的信息,再利用awk获取第一列——PID
    
    
    

相关的系统调用:

  • kill:向指定进程发送某个信号
  • raise:向当前进程发送某个信号,等效于:kill(getpid(), 信号编号)
  • alarm:设置闹钟,注意:每个进程最多只能拥有一个闹钟。
  • pause:挂起当前进程,直到接收到一个信号。
  • abort:终止当前进程。(raise(SIGABRT))
  • sleep:挂起当前进程,直到超时或接收到一个信号。返回值为剩余时间,若执行完则返回 0,中途终止则返回剩余的时间。

避免僵尸进程产生的另外一种常用办法:忽略 SIGCHLD 信号或者在 SIGCHLD 信号的处理函数中调用 wait/waitpid 函数。

SIGCHLD 信号:在一个进程终止或者停止时,将SIGCHLD信号发送给其父进程。按系统默认将忽略此信号。如果父进程希望被告知其子进程的这种状态,则应捕捉此信号。信号的捕捉函数中通常调用wait函数以取得进程ID和其终止状态。

手动实现kill命令

//手动实现kill命令
#include 
#include 
#include 
#include 
int main(int argc,char** argv)
{
    int signum;
    int pid;
    if(argv[1][0] == '-')
        //取信号的数值
        signum = atoi(argv[1] + 1);//atoi将字符串转换为整形,不能强转,直接强转就是将字符地址强转成了整形
	else
        signum = atoi(argv[1]);
    //取进程编号
    pid = atoi(argv[2]);
	return kill(pid, signum);//成功返回0,失败返回-1
}

执行完将可执行文件移动到/user/bin/下则可直接当命令执行

手动实现raise命令

//手动实现raise命令
#include 
#include 
#include 
#include 
#include 


// void signal_handler(int signum)
// {
//     printf("SIGINT\n");
// }


void my_sleep(int seconds);

void signal_handler(int signum)
{
    // printf("hello\n");

    // alarm(3);
}


int main()
{
    // signal(SIGINT, signal_handler);
    // raise(SIGINT);
    // signal(SIGALRM, signal_handler);
    signal(SIGABRT, signal_handler);

    // alarm(3);

    // while(1) pause();

    // printf("start...\n");

    // my_sleep(5);

    
    printf("start...\n");
    abort(); // raise(SIGABRT);
    printf("end...\n");

    return 0;
}

void my_sleep(int seconds)
{
    alarm(seconds);
    pause();
}

手动实现alarm命令

//手动实现alarm命令
#include 
#include 
#include 
#include 
#include 

void mt_sleep(int seconds)
{
	alarm(seconds);
    pause();
}


int main()
{
	signal(SIGALRM, SIG_IGM);
    printf("start...");
    
    my_sleep(5);
    printf("end...");
	return 0;
}

文件拷贝程序(多进程控制)

#include 
#include 
#include 
#include 
#include 
#include 
#include 


int is_start = 0;
int is_end = 0;
int copy_result = 0;


void signal_handler(int signum);
void copy_file(const char* src_file, const char* dst_file);
void ui(pid_t child);


int main(int argc, char** argv)
{
    if(argc != 3)
    {
        fprintf(stderr, "命令用法错误!\n");
        return 1;
    }

    if(-1 == access(argv[1], R_OK))
    {
        fprintf(stderr, "待拷贝的文件不存在或不可读!\n");
        return 1;
    }

    if(0 == access(argv[2], F_OK))
    {
        printf("目标文件 %s 已存在,是否覆盖?(Y/n)", argv[2]);

        char choice = getchar();
        if(choice == 'Y' || choice == 'y')
        {
            while(getchar() != '\n');
        }
        else
            return 1;
    }    

    pid_t pid = fork();

    if(pid == -1) 
    {
        perror("fork");
        return 1;
    }

    if(pid == 0)
    {
        // 子进程负责拷贝文件任务
        signal(SIGTERM, signal_handler);
        signal(SIGRTMIN + 1, signal_handler);
        signal(SIGRTMIN + 2, signal_handler);

        copy_file(argv[1], argv[2]);
    }
    else
    {
        // 父进程负责 UI 处理任务
        signal(SIGCHLD, signal_handler);
        signal(SIGRTMIN + 3, signal_handler);

        ui(pid);
    }
    
    return 0;
}


void signal_handler(int signum)
{
    if(signum == SIGTERM)
    {
        is_end = 1;
    }
    else if(signum == SIGCHLD)
    {
        is_end = 1;
    }
    else if(signum == SIGRTMIN + 1)
    {
        is_start = 1;
    }
    else if(signum == SIGRTMIN + 2)
    {
        is_start = 0;
    }
    else if(signum == SIGRTMIN + 3)
    {
        copy_result = 1;
    }
}


void copy_file(const char* src_file, const char* dst_file)
{
    int fd_src, fd_dst;
    unsigned int ret, write_cnt = 0;
    char buf[1024];
    struct stat st;

    fd_src = open(src_file, O_RDONLY);
    fd_dst = open(dst_file, O_WRONLY | O_CREAT | O_TRUNC, 0666);

    fstat(fd_src, &st);

    while((ret = read(fd_src, buf, sizeof(buf))) > 0)
    {
        while(!is_end && !is_start) pause();

        if(is_end) 
        {   
            unlink(dst_file);
            break;
        }

        ret = write(fd_dst, buf, ret);

        if(ret == -1) break;

        write_cnt += ret;
    }

    close(fd_src);
    close(fd_dst);

    if(!is_end)
    {
        if(write_cnt == st.st_size)
            kill(getppid(), SIGRTMIN + 3);
    }
}


void ui(pid_t child)
{
    int op;

    printf("欢迎使用本拷贝工具!\n\n");
    printf("使用帮助:\n");
    printf("开始或继续拷贝请输入 1\n");
    printf("暂停拷贝请输入 2\n");
    printf("查看拷贝状态请输入 3\n");    
    printf("退出程序请输入 0\n");

    while(1)
    {
        printf("\n:> ");

        if(1 != scanf("%d", &op))
        {
            op = -1;
            while(getchar() != '\n');
        }

        switch(op)
        {
            case 0:
            {
                kill(child, SIGTERM);
                printf("\n感谢您的使用,下次再见!\n");
                exit(0);
            }
            break;

            case 1:
            {
                kill(child, SIGRTMIN + 1);
                is_start = 1;
            }
            break;

            case 2:
            {
                kill(child, SIGRTMIN + 2);
                is_start = 0;
            }
            break;

            case 3:
            {
                if(is_end)
                {
                    if(copy_result)
                        printf("拷贝已经成功完成!\n");
                    else
                        printf("拷贝失败!\n");
                }
                else
                {
                    if(is_start)
                        printf("拷贝正在进行,可以输入 2 暂停拷贝!\n");
                    else
                        printf("拷贝已经暂停,可以输入 1 继续拷贝!\n");                    
                }
            }
            break;

            default:
            {
                printf("\n输入错误,请重新输入!\n");
            }
        }
    }
}

你可能感兴趣的:(Linux高级编程,linux,服务器,debian)