linux:10信号底层实现机制进程替换

1.异步执行:两个以上的进程或线程,执行互不影响,同时向下执行,当某个时刻,一个进程或线程会通过系统机制通知其他进程或线程。
(非阻塞状态)

2.同步执行:一个进程或线程的执行需要依赖其他的进程或线程执行的条件(阻塞状态)
在这里插入图片描述
3.阻塞:条件未准备好,进程在这停下,一直等着
4.非阻塞:条件准备好,进程在这没停下。

2.信号的发送

1.函数:int kill(pid_t pid,int sigtype);
pid_t pid:指定将那个信号发送给进程

  1. pid>0:指定接收信号进程的pid
  2. pid == 0:将信号发送给当前进程组中所有的进程
  3. pid == -1:将信号发送给系统上所有的进程(有权限发送)
  4. pid < -1:将信号发送给进程ID为-pid组内所有的进程

sigtype:发送的信号类型
2.发送的过程
例子:kill(1234,SIGINT):其中SIGINT==2;
调用底层函数:sys_kill(1234,2);
发送信号的函数:

///向指定任务(*p)发送信号(sig),权限为priv
static _inline int
send_sig (long sig ,struct task_struct *p,int priv);
{
//若信号不正确或任务指针为空则出错退出
if (!p || sig<1 || sig>32)
return -EINVAL;
//若有权或进程有效用户标识符(euid)就是指定进程的euid或者是超级用户,则在进程位图中添加
//该信号,否则出错退出。其中suser()定义为(current->euid == 0),用于判断是否是超级用户。
if(priv || (current->euid == p->euid) || suser())
p->signal |= (1<<(sig - 1));
else
return -EPERM;
return 0;
}

p->signal |= (1<<(sig - 1));当p进程接受到信号后,信号触发中断机制,会有中断处理程序来处理,接着会调用信号处理函数,信号处理函数通过signal函数来注册

3. 注册信号处理函数:

代码实现:

int sys_signal(int signum,long handler, long restorer)
{
     struct sigaction tmp;
     
     if(signum < 1 || signum > 32 || signum == SIGKILL) //信号值要在(1-32)范围内 。signum == SIGKILL为9号信号
        return -1;      //并且不得是SIGKILL
     tmp.sa_handler = (void (*)(int)) handler; //指定的信号处理句柄
     tmp.sa_mask = 0 ;   //执行时的信号屏蔽码
     tm.sa_flags = SA_ONESHOT | SA_NOMASK;

     tmp.sa_restorer = (void)(*)(void)) restorer;  //保存返回地址
     handler = (long)current -> sigaction[signum - 1].sa_handler;//当前进程PCB的成员
     current -> sigaction[signum - 1] = tmp ;//句柄传递过来给到数组的[signum -1],current为当前进程的PCB 
     return handler;
}

注:sa_restorer 为恢复过程指针,用于保存原返回的过程指针

流程图讲解过程:
在这里插入图片描述
当中断机制处理起来时,在PCB,数组中调用信号处理函数,sigaction中记录要找到的值,在调用signal函数。

我们来思考一个问题:

p->signal |= (1<<(sig - 1)):进程接收到信号是signal long类型移位来表示的,如果连续两次发送同一个信号给同一进程 或 两个进程同时给一个进程发送相同的信号那怎么来解决?
代码实现:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <signal.h>

void fun(int sign)
{
    printf("fun start\n");
    sleep(10);
    printf("fun end\n");
}

int main()
{
    signal(SIGINT,fun);

    while(1)
    {
        sleep(1);
        printf("main running\n");
    }
}

在这里插入图片描述

结果:发现如果连续收到n个相同的信号,就收信号的进程会将所有的信号都会合并成一个信号!

同时得到以下结论:如果第一个信号正在处理,则第二个信号会等待第一个信号处理完成

结果我们发现:信号响应方式一旦修改,后续接受信号一直沿用,直到进程终止或再次修改

第二个问题:进程第一次接收信号打印helloworld,第二次接收结束进程

信号处理程序分成两部分:
上部分:识别信号
下部分:处理信号

结果:
第一次接收到信号和第二次接收到信号之间将响应方式修改。
在第一次修改的信号处理函数中修改信号的响应方式。

4.进程替换

当一个进程调用一种 exec 函数时,该进程执行的程序完全替换为新程序,而新程序从其 main 函数开始执行。

调用 exec 并不创建新进程, 前后的进程 ID 并未改变, 只是用磁盘上的一个新程序替换当前进程的正文段、数据段、堆段和栈段

接下来我们一个个的看看:

execl 函数:

(1) 函数原型: int execl(const char * path, const char * arg, …);
(2) 这里的 path 是可执行程序的路径 取路径名作为参数
(3) arg 是所要替换程序的参数
(4) 返回值: 成功没有返回值,失败返回 -1

#include <stdio.h>
#include <stdlib.h>

#include <unistd.h>

int main(void)
{
    
    int res = execl("ls", "ls", "-l", "/", NULL);
    if(res == -1)
    {
        perror("execl error");
        exit(1);
    }
    printf("hahaha!\n");

    return 0;
}

execlp 函数:

(1) 函数原型:
int execlp(const char * file, const char * arg, …);
(2) 这里 file 参数可以是一个 可执行程序名,并且可以不用指定全路径,它会自动在 PATH 中的一个找到该可执行文件,如果该文件不是由连接编译器产生的机器可执行文件,则就认为该文件是一个 shell 脚本,于是试着调用 /bin/sh, 并以该 filename 作为 shell 的输入, 取文件名作为参数
(3) arg 还是可执行程序的参数
(4) 返回值: 成功返回

#include <stdio.h>
#include <stdlib.h>

#include <unistd.h>

int main(void)
{
    int res = execlp("ls", "ls", "-l", "/", NULL);
    if(res == -1)
    {
        perror("execl error");
        exit(1);
    }
    printf("hahaha!\n");

    return 0;
}

execle 函数

(1) 函数原型:
int execle(const char * path, const char * arg, …, char * const envp[]);
(2)这里的最后一个参数是一个自己构建的环境,是一个字符串指针数组,取路径名作为参数

// filename: process_exec.c 
#include <stdio.h>
#include <stdlib.h>

#include <unistd.h>

int main(void)
{
    char *env[] = {
        "AAAA=aaaa",
        NULL 
    };

    int res = execle("./exec_test", "./exec_test", NULL, env);
    if(res == -1)
    {
        perror("execl error");
        exit(1);
    }
    printf("hahaha!\n");

    return 0;
}
// filename: test_exec.c
#include <stdio.h>
#include <stdlib.h>

int main()
{
    printf("AAAA = %s\n", getenv("AAAA")/*需要包含 stdlib.h */);

    return 0;
}

程序编译结果如下:
AAAA = aaaa

execv 函数

(1) 函数原型:
int execv(const char * path, char * const argv[]);
(2) 这和 execl 函数一样,区别就是,execl 函数的第二个参数为可变参数列表,而 execv 函数的第二个参数为字符串指针数组。取路径名作为参数

#include <stdio.h>
#include <stdlib.h>

#include <unistd.h>

int main(void)
{
    char * arg[] = {
        "ls",
        "-l",
        "/",
        NULL 
    };

    int res = execv("/usr/bin/ls", arg);
    if(res == -1)
    {
        perror("execl error");
        exit(1);
    }
    printf("hahaha!\n");

    return 0;
}

execvp 函数

(1) 函数原型:
int execvp(const char * file, char * const argv[]);
(2) 与函数 execlp 一样,取文件名作为参数

#include <stdio.h>
#include <stdlib.h>

#include <unistd.h>

int main(void)
{
    char * arg[] = {
        "ls",
        "-l",
        "/",
        NULL 
    };

    int res = execvp("ls", arg);
    if(res == -1)
    {
        perror("execl error");
        exit(1);
    }
    printf("hahaha!\n");

    return 0;
}

execve 函数

(1) 函数原型:
int execve(const char * file, char * const argv[], char * const envp[]);
(2) 与函数 execle 函数一样, 取路径名作为参数

#include <stdio.h>
#include <stdlib.h>

#include <unistd.h>

int main(void)
{
    char * arg[] = {
        "./exec_test",
        NULL 
    };

    char * env[] = {
        "AAAA=aaaa",
        NULL
    };

    int res = execve("./exec_test", arg, env);
    if(res == -1)
    {
        perror("execl error");
        exit(1);
    }
    printf("hahaha!\n");

    return 0;
}

fexecle 函数

这也是一个进程替换函数,依赖调用进程来完成这项工作。调用进程可以使用文件描述符验证所需要的文件并且无竞争地执行该文件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值