第二章 Linux多进程开发(4)

17 内存映射(1)

  • 内存映射(Memory-mapped I/O)是1将磁盘文件的数据映射到内存,用户通过修改内存就能修改磁盘文件

在这里插入图片描述

内存映射相关系统调用

在这里插入图片描述

/*
    #include <sys/mman.h>
    void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
        - 功能:将一个文件或者设备的数据映射到内存中
        - 参数:
            - void *addr: NULL, 由内核指定
            - length : 要映射的数据的长度,这个值不能为0。建议使用文件的长度。
                    获取文件的长度:stat lseek
            - prot : 对申请的内存映射区的操作权限
                -PROT_EXEC :可执行的权限
                -PROT_READ :读权限
                -PROT_WRITE :写权限
                -PROT_NONE :没有权限
                要操作映射内存,必须要有读的权限。
                PROT_READ、PROT_READ|PROT_WRITE
            - flags :
                - MAP_SHARED : 映射区的数据会自动和磁盘文件进行同步,进程间通信,必须要设置这个选项
                - MAP_PRIVATE :不同步,内存映射区的数据改变了,对原来的文件不会修改,会重新创建一个新的文件。(copy on write)
            - fd: 需要映射的那个文件的文件描述符
                - 通过open得到,open的是一个磁盘文件
                - 注意:文件的大小不能为0,open指定的权限不能和prot参数有冲突。
                    prot: PROT_READ                open:只读/读写 
                    prot: PROT_READ | PROT_WRITE   open:读写
            - offset:偏移量,一般不用。必须指定的是4k的整数倍,0表示不便宜。
        - 返回值:返回创建的内存的首地址
            失败返回MAP_FAILED,(void *) -1

    int munmap(void *addr, size_t length);
        - 功能:释放内存映射
        - 参数:
            - addr : 要释放的内存的首地址
            - length : 要释放的内存的大小,要和mmap函数中的length参数的值一样。
*/

/*
    使用内存映射实现进程间通信:
    1.有关系的进程(父子进程)
        - 还没有子进程的时候
            - 通过唯一的父进程,先创建内存映射区
        - 有了内存映射区以后,创建子进程
        - 父子进程共享创建的内存映射区
    
    2.没有关系的进程间通信
        - 准备一个大小不是0的磁盘文件
        - 进程1 通过磁盘文件创建内存映射区
            - 得到一个操作这块内存的指针
        - 进程2 通过磁盘文件创建内存映射区
            - 得到一个操作这块内存的指针
        - 使用内存映射区通信

    注意:内存映射区通信,是非阻塞。
*/

#include<stdio.h>
#include<sys/mman.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<sys/wait.h>

int main(){

    //1、打开一个文件
    int fd = open("text.txt",O_RDWR);
    //获取文件的大小
    int size = lseek(fd,0,SEEK_END);

    //2、创建内存映射区
    void *ptr = mmap(NULL,size,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
    if(ptr == MAP_FAILED){
        perror("mmap");
        return -1;
    }

    //3、创建子进程
    pid_t pid = fork();

    if(pid > 0){
        wait(NULL);
        //父进程
        char buf[64];
        strcpy(buf,(char *)ptr);
        printf("read data : %s \n",buf);
        
    }else if(pid == 0){
        //子进程
        strcpy((char *)ptr,"nihao a, son \n");
    }

    //关闭内存映射区
    munmap(ptr,size);


    return 0;
}

使用内存映射实现没有亲缘关系的进程间通信

write.c

#include<stdio.h>
#include<sys/mman.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<sys/wait.h>
int main(){

    //1、打开文件
    int fd = open("text.txt",O_RDWR);
    if(fd == -1){
        perror("open");
        return -1;
    }
    //获取文件长度
    int size = lseek(fd,0,SEEK_END);
    //内存映射
    void *ptr = mmap(NULL,size,PROT_WRITE | PROT_READ,MAP_SHARED,fd,0);
    if(ptr == MAP_FAILED){
        perror("mmap");
        return -1;
    }
    //写数据
    char buf[1024];
    int nIndex = 0;
    while(1){
        sleep(2);
        memset(buf,0,1024);
        sprintf(buf,"%d hello!",nIndex++);
        printf("write: %s \n",buf);
        strcpy((char *)ptr,buf);
    }
    //释放内存映射
    munmap(ptr,size);
    //关闭文件
    close(fd);

    return 0;
}

read.c

#include<stdio.h>
#include<sys/mman.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<sys/wait.h>
int main(){
    //打开文件
    int fd = open("text.txt",O_RDWR);
    if(fd == -1){
        perror("open");
        return -1;
    }

    //获取文件长度
    int size = lseek(fd,0,SEEK_END);

    //建立内存映射
    void *ptr = mmap(NULL,size,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
    if(ptr == MAP_FAILED){
        perror("mmap");
        return -1;
    }

    //读数据
    char buf[1024];
    while(1){
        sleep(2);
        memset(buf,0,1024);
        strcpy(buf,(char *)ptr);
        printf("read : %s \n",buf);
    }

    //释放内存映射
    munmap(ptr,size);
    //关闭文件
    close(fd);
    
    return 0;
}

18 内存映射(2)

1.如果对mmap的返回值(ptr)++操作(ptr++), munmap是否能够成功?
void * ptr = mmap(...);
ptr++;  可以对其进行++操作
munmap(ptr, len);   // 错误,要保存地址

2.如果open时O_RDONLY, mmap时prot参数指定PROT_READ | PROT_WRITE会怎样?
错误,返回MAP_FAILED
open()函数中的权限建议和prot参数的权限保持一致。

3.如果文件偏移量为1000会怎样?
偏移量必须是4K的整数倍,返回MAP_FAILED

4.mmap什么情况下会调用失败?
    - 第二个参数:length = 0
    - 第三个参数:prot
        - 只指定了写权限
        - prot PROT_READ | PROT_WRITE
          第5个参数fd 通过open函数时指定的 O_RDONLY / O_WRONLY

5.可以open的时候O_CREAT一个新文件来创建映射区吗?
    - 可以的,但是创建的文件的大小如果为0的话,肯定不行
    - 可以对新的文件进行扩展
        - lseek()
        - truncate()

6.mmap后关闭文件描述符,对mmap映射有没有影响?
    int fd = open("XXX");
    mmap(,,,,fd,0);
    close(fd); 
    映射区还存在,创建映射区的fd被关闭,没有任何影响。

7.对ptr越界操作会怎样?
void * ptr = mmap(NULL, 100,,,,,);
4K
越界操作操作的是非法的内存 -> 段错误


内存映射实现文件复制的功能

// 使用内存映射实现文件拷贝的功能
/*
    思路:
        1.对原始的文件进行内存映射
        2.创建一个新文件(拓展该文件)
        3.把新文件的数据映射到内存中
        4.通过内存拷贝将第一个文件的内存数据拷贝到新的文件内存中
        5.释放资源
*/
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main(){

    //1.对原始的文件进行内存映射
    int fd = open("english.txt",O_RDWR);
    if(fd == -1){
        perror("open");
        return -1;
    }

    //获取原始文件的大小
    int len = lseek(fd,0,SEEK_END);

    //2.创建一个新文件(拓展该文件)
    int fd1 = open("cpy.txt",O_RDWR | O_CREAT,0664);
    if(fd1 == -1){
        perror("open");
        return -1;
    }

    //对新创建的文件进行扩展(可以使用lseek和truncate)
    truncate("cpy.txt",len);
    write(fd1," ",1);

    //3.分别做内存映射
    void *ptr = mmap(NULL,len,PROT_WRITE | PROT_READ,MAP_SHARED,fd,0);
    void *ptr1 = mmap(NULL,len,PROT_WRITE | PROT_READ,MAP_SHARED,fd1,0);
    if(ptr == MAP_FAILED){
        perror("mmap");
        return -1;
    }
    if(ptr1 == MAP_FAILED){
        perror("mmap");
        return -1;
    }

    //内存拷贝
    memcpy(ptr1,ptr,len);

    //释放资源
    munmap(ptr1,len);
    munmap(ptr,len);

    close(fd1);
    close(fd);


    return 0;
}

匿名映射

/*

    匿名映射:不需要文件实体进程一个内存映射

    只能进行父子间映射,没有亲情关系的进程不可以,因为是匿名

*/

#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>

int main(){

    //1、创建匿名内存映射区
    int len = 4096;
    void *ptr = mmap(NULL,len,PROT_READ | PROT_WRITE,MAP_SHARED | MAP_ANONYMOUS,-1,0);
    if(ptr == MAP_FAILED){
        perror("mmap");
        return -1;
    }

    //父子进程间通信
    pid_t pid = fork();
    if(pid > 0){
        //父进程
        strcpy((char *)ptr,"hello world");
        wait(NULL);
    }else if(pid == 0){
        //子进程
        sleep(1);
        printf("%s\n",(char *)ptr);
    }

    //释放内存映射区
    munmap(ptr,len);
    

    return 0;
}

19 信号概述

信号的概念

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

信号的5种默认处理动作

在这里插入图片描述

20 kill、raise、abort函数

信号相关的函数

在这里插入图片描述

/**
 * #include<sys/types.h>
 * #include<signal.h>
 * int kill(pid_t pid, int sig);
 *      功能:给某个进程或者进程组pid,发送某个信号sig
 *      参数:
 *          pid:需要发送给的进程的id
 *              >0:将信号发送给指定的进程
 *              =0:将信号发送给当前的进程组
 *              =-1:将信号发送给每一个有权限接收这个信号的进程
 *              <-1:这个pid=某个进程组的id取反(-1234)
 *          sig:需要发送的信号的编号或者宏值,0表示不发送任何信号
 *      kill(getppid(),sig);
 *      kill(getpid(),sig);
*/

/*
    int raise(int sig);
    功能:给当前进程发送信号
    参数:
        sig:要发送的信号
    返回值:
        成功:0
        失败:非0
    kill(getpid(),sig);


    void abort(void);
    功能:发送SIGABRT信号给当前的进程,杀死当前进程
    如果用kill实现就是kill(getpid(),SIGABRT);

*/
#include<signal.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>

int main(){

    pid_t pid = fork();
    if(pid == 0){
        for(int i=0;i<6;++i){
            printf("child process !!!\n");
            sleep(1);
        }
    }else if(pid >0){
        printf("parent process \n");
        sleep(2);
        printf("kill child\n");
        kill(pid,SIGINT);
    }

    return 0;
}

在这里插入图片描述

21 alarm函数

/*

    #include<unistd.h>
    unsigned int alarm(unsigned int seconds);
        功能:设置定时器(闹钟)。函数调用,开始倒计时,当倒计时为0的时候,
              函数会给当前的进程发送一个信号:SIGALARM
        参数:
            seconds:倒计时的时长,单位:秒。如果参数为0,定时器无效(不进行倒计时,不发信号)
                     取消一个定时器,通过alarm(0)
        返回值:
            之前没有定时器,返回0
            之前有定时器,返回之前定时器剩余的时间

    SIGALARM:默认终止当前的进程,每一个进程都有且只有唯一的一个定时器

    alarm(10); ->返回0
    过了1秒
    alarm(5); ->返回9

    alarm(100); -> 该函数是不阻塞的
*/

#include<stdio.h>
#include<unistd.h>
int main(){
    int seconds = alarm(5);
    printf("seconds = %d \n",seconds);//0
    sleep(2);
    seconds = alarm(2);
    printf("seconds = %d \n",seconds);//3

    while(1){}
    return 0;
    
}

案例:计算机1秒能数多少个数

#include<stdio.h>
#include<unistd.h>

int main(){
    int seconds = alarm(1);
    int count=0;
    while(1){
        printf("数字:%d\n",count++);
    }
    
    return 0;
}

22 setitimer定时器函数

/*
    #include<sys/time.h>
    int setitimer(int which,const struct itimerval *new_value,
                    struct itimerval *old_value);
        功能:设置定时器(闹钟),可以替代alarm函数,精度:微秒us,
                可以实现周期性定时
        参数:
            which:定时器以什么时间定时
                ITIMER_REAL:真实时间,时间到达,发送SIGALRM 常用
                ITIMER_VIRTUAL:用户时间,时间到达,发送SIGVTALRM
                ITIMER_PROF:以该进程在用户态和内核态下所消耗的时间来计算,
                            时间到达,发送SIGPROF

            new_value:设置定时器的属性

                struct itimerval{         //定时器的结构体
                    struct timeval it_interval;  //每个阶段的时间,间隔时间
                    struct timeval it_value;    //延迟多长时间执行定时器  
                };

                struct timeval   {        //时间的结构体
                    time_t       tv_sec;  //秒数
                    suseconds_t  tv_usec; //微秒
                };

            过10秒后,每个2秒定时一次

            old_value:记录上一次的定时的时间参数,一般不使用,指定NULL
        
        返回值:成功0,失败-1,并返回错误号

*/
/*
    #include<sys/time.h>
    int setitimer(int which,const struct itimerval *new_value,
                    struct itimerval *old_value);
        功能:设置定时器(闹钟),可以替代alarm函数,精度:微秒us,
                可以实现周期性定时
        参数:
            which:定时器以什么时间定时
                ITIMER_REAL:真实时间,时间到达,发送SIGALRM 常用
                ITIMER_VIRTUAL:用户时间,时间到达,发送SIGVTALRM
                ITIMER_PROF:以该进程在用户态和内核态下所消耗的时间来计算,
                            时间到达,发送SIGPROF

            new_value:设置定时器的属性

                struct itimerval{         //定时器的结构体
                    struct timeval it_interval;  //每个阶段的时间,间隔时间
                    struct timeval it_value;    //延迟多长时间执行定时器  
                };

                struct timeval   {        //时间的结构体
                    time_t       tv_sec;  //秒数
                    suseconds_t  tv_usec; //微秒
                };

            过10秒后,每个2秒定时一次

            old_value:记录上一次的定时的时间参数,一般不使用,指定NULL
        
        返回值:成功0,失败-1,并返回错误号

*/
#include<stdio.h>
#include<sys/time.h>
#include<stdlib.h>
//过3秒以后,每隔2秒定时一次
int main(){

    struct itimerval new_value;
    //设置间隔的时间,秒和微秒
    new_value.it_interval.tv_sec = 2;
    new_value.it_interval.tv_usec = 0;
    //设置延迟的时间
    new_value.it_value.tv_sec = 3;
    new_value.it_value.tv_usec = 0;

    int ret = setitimer(ITIMER_REAL,&new_value,NULL);//非阻塞
    printf("定时器开始了。。。。\n");
    if(ret == -1){
        perror("setitimer");
        return -1;
    }

    getchar();

    return 0;
}


案例还未实现,需要下一节的信号捕捉

23 signal信号捕捉函数

信号捕捉函数

在这里插入图片描述

/*
    #include <signal.h>
    typedef void (*sighandler_t)(int);
    sighandler_t signal(int signum, sighandler_t handler);
        - 功能:设置某个信号的捕捉行为
        - 参数:
            - signum: 要捕捉的信号
            - handler: 捕捉到信号要如何处理
                - SIG_IGN : 忽略信号
                - SIG_DFL : 使用信号默认的行为
                - 回调函数 :  这个函数是内核调用,程序员只负责写,捕捉到信号后如何去处理信号。
                回调函数:
                    - 需要程序员实现,提前准备好的,函数的类型根据实际需求,看函数指针的定义
                    - 不是程序员调用,而是当信号产生,由内核调用
                    - 函数指针是实现回调的手段,函数实现之后,将函数名放到函数指针的位置就可以了。

        - 返回值:
            成功,返回上一次注册的信号处理函数的地址。第一次调用返回NULL
            失败,返回SIG_ERR,设置错误号
            
    SIGKILL SIGSTOP不能被捕捉,不能被忽略。
*/
#include<stdio.h>
#include<sys/time.h>
#include<stdlib.h>
#include<signal.h>

void myalarm(int num){
    printf("捕捉到了信号的编号是:%d\n",num);
    printf("xxxxxxxxx\n");
}

//过3秒以后,每隔2秒定时一次
int main(){

    //注册信号捕捉
    //signal(SIGALRM,SIG_IGN);
    //signal(SIGALRM,SIG_DFL);
    // sighandler_t ret = signal(SIGALRM,myalarm);
    // if(ret == SIG_ERR){
    //     perror("signal");
    //     return -1;
    // }
    signal(SIGALRM,myalarm);

    struct itimerval new_value;
    //设置间隔的时间,秒和微秒
    new_value.it_interval.tv_sec = 2;
    new_value.it_interval.tv_usec = 0;
    //设置延迟的时间
    new_value.it_value.tv_sec = 3;
    new_value.it_value.tv_usec = 0;

    int ret = setitimer(ITIMER_REAL,&new_value,NULL);//非阻塞
    printf("定时器开始了。。。。\n");
    if(ret == -1){
        perror("setitimer");
        return -1;
    }

    getchar();

    return 0;
}

在这里插入图片描述

24 信号集及相关函数

信号集

在这里插入图片描述

阻塞信号集和未决信号集

1.用户通过键盘  Ctrl + C, 产生2号信号SIGINT (信号被创建)

2.信号产生但是没有被处理 (未决)
    - 在内核中将所有的没有被处理的信号存储在一个集合中 (未决信号集)
    - SIGINT信号状态被存储在第二个标志位上
        - 这个标志位的值为0, 说明信号不是未决状态
        - 这个标志位的值为1, 说明信号处于未决状态
    
3.这个未决状态的信号,需要被处理,处理之前需要和另一个信号集(阻塞信号集),进行比较
    - 阻塞信号集默认不阻塞任何的信号
    - 如果想要阻塞某些信号需要用户调用系统的API

4.在处理的时候和阻塞信号集中的标志位进行查询,看是不是对该信号设置阻塞了
    - 如果没有阻塞,这个信号就被处理
    - 如果阻塞了,这个信号就继续处于未决状态,直到阻塞解除,这个信号就被处理

信号集相关的函数

在这里插入图片描述

/*
    以下信号集相关的函数都是对自定义的信号集进行操作。

    int sigemptyset(sigset_t *set);
        - 功能:清空信号集中的数据,将信号集中的所有的标志位置为0
        - 参数:set,传出参数,需要操作的信号集
        - 返回值:成功返回0, 失败返回-1

    int sigfillset(sigset_t *set);
        - 功能:将信号集中的所有的标志位置为1
        - 参数:set,传出参数,需要操作的信号集
        - 返回值:成功返回0, 失败返回-1

    int sigaddset(sigset_t *set, int signum);
        - 功能:设置信号集中的某一个信号对应的标志位为1,表示阻塞这个信号
        - 参数:
            - set:传出参数,需要操作的信号集
            - signum:需要设置阻塞的那个信号
        - 返回值:成功返回0, 失败返回-1

    int sigdelset(sigset_t *set, int signum);
        - 功能:设置信号集中的某一个信号对应的标志位为0,表示不阻塞这个信号
        - 参数:
            - set:传出参数,需要操作的信号集
            - signum:需要设置不阻塞的那个信号
        - 返回值:成功返回0, 失败返回-1

    int sigismember(const sigset_t *set, int signum);
        - 功能:判断某个信号是否阻塞
        - 参数:
            - set:需要操作的信号集
            - signum:需要判断的那个信号
        - 返回值:
            1 : signum被阻塞
            0 : signum不阻塞
            -1 : 失败

*/
#include <signal.h>
#include <stdio.h>
int main(){

    // 创建一个信号集
    sigset_t set;

    // 清空信号集的内容
    sigemptyset(&set);

    // 判断SIGINT是否在信号集set里
    int ret = sigismember(&set,SIGINT);
    if(ret == 0){
        printf("sigint 未阻塞 \n");
    }else if(ret == 1){
        printf("sigint 阻塞 \n");
    }

    // 添加几个信号到信号集中SIGINT,SIGQUIT
    sigaddset(&set,SIGINT);
    sigaddset(&set,SIGQUIT);

    // 判断SIGINT是否在信号集中
    ret = sigismember(&set,SIGINT);
    if(ret == 0){
        printf("sigint 未阻塞 \n");
    }else if(ret == 1){
        printf("sigint 阻塞 \n");
    }

    // 从信号集中删除一个信号SIGQUIT
    ret = sigdelset(&set,SIGQUIT);
    if(ret == 0){
        printf("sigquit 删除成功");
    }else if(ret == -1){
        printf("sigquit 删除失败");
    }

    // 判断SIGQUIT是否还在信号集中
    ret = sigismember(&set,SIGQUIT);
    if(ret ==1){
        printf("sigquit 阻塞 \n");
    }else if(ret == 0){
        printf("sigquit 未阻塞 \n");
    }


    return 0;
}

25 sigprocmask函数使用

在这里插入图片描述

注:执行程序时在后面加一个& 就表示在后台执行程序,该程序输出过程中输入命令(例如ls)是有效的,如果在前台执行,输入命令无效。
在这里插入图片描述

/*
    int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
        - 功能:将自定义信号集中的数据设置到内核中(设置阻塞,解除阻塞,替换)
        - 参数:
            - how : 如何对内核阻塞信号集进行处理
                SIG_BLOCK: 将用户设置的阻塞信号集添加到内核中,内核中原来的数据不变
                    假设内核中默认的阻塞信号集是mask, mask | set
                SIG_UNBLOCK: 根据用户设置的数据,对内核中的数据进行解除阻塞
                    mask &= ~set
                SIG_SETMASK:覆盖内核中原来的值
            
            - set :已经初始化好的用户自定义的信号集
            - oldset : 保存设置之前的内核中的阻塞信号集的状态,可以是 NULL
        - 返回值:
            成功:0
            失败:-1
                设置错误号:EFAULT、EINVAL

    int sigpending(sigset_t *set);
        - 功能:获取内核中的未决信号集
        - 参数:set,传出参数,保存的是内核中的未决信号集中的信息。
*/

// 编写一个程序,把所有的常规信号(1-31)的未决状态打印到屏幕
// 设置某些信号是阻塞的,通过键盘产生这些信号

#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
int main(){
    // 设置2、3号信号阻塞
    sigset_t set;
    sigemptyset(&set);
    

    // 将2号3号信号添加到信号集中
    sigaddset(&set,SIGINT);
    sigaddset(&set,SIGQUIT);
   
    //修改内核中的阻塞信号集
    sigprocmask(SIG_BLOCK,&set,NULL);
    
    int num =0;
    while(1){

        num++;

        // 获取当前的未决信号集的数据
        sigset_t pendingset;
        sigemptyset(&pendingset);
        sigpending(&pendingset);

        //遍历前32位
        for(int i=1;i<=31;++i){
            if( sigismember(&pendingset,i)==1){
                printf("1 ");
            }else if(sigismember(&pendingset,i)==0){
                printf("0 ");
            }else{
                perror("sigismember");
                return -1;
            }
        }
        printf("\n");
        sleep(2);

        if(num == 10){
            //解除阻塞
            sigprocmask(SIG_UNBLOCK,&set,NULL);
        }
    }
    return 0;
}

26 sigaction信号捕捉函数

信号捕捉函数

在这里插入图片描述

/*
    #include <signal.h>
    int sigaction(int signum, const struct sigaction *act,
                            struct sigaction *oldact);

        - 功能:检查或者改变信号的处理。信号捕捉
        - 参数:
            - signum : 需要捕捉的信号的编号或者宏值(信号的名称)
            - act :捕捉到信号之后的处理动作
            - oldact : 上一次对信号捕捉相关的设置,一般不使用,传递NULL
        - 返回值:
            成功 0
            失败 -1

     struct sigaction {
        // 函数指针,指向的函数就是信号捕捉到之后的处理函数
        void     (*sa_handler)(int);
        // 不常用
        void     (*sa_sigaction)(int, siginfo_t *, void *);
        // 临时阻塞信号集,在信号捕捉函数执行过程中,临时阻塞某些信号。
        sigset_t   sa_mask;
        // 使用哪一个信号处理对捕捉到的信号进行处理
        // 这个值可以是0,表示使用sa_handler,也可以是SA_SIGINFO表示使用sa_sigaction
        int        sa_flags;
        // 被废弃掉了
        void     (*sa_restorer)(void);
    };

*/
#include<stdio.h>
#include<sys/time.h>
#include<stdlib.h>
#include<signal.h>

void myalarm(int num){
    printf("捕捉到了信号的编号是:%d\n",num);
    printf("xxxxxxxxx\n");
}

//过3秒以后,每隔2秒定时一次
int main(){

    struct sigaction act;
    act.sa_flags = 0;
    act.sa_handler = myalarm;
    sigemptyset(&act.sa_mask); //清空临时阻塞信号集

    //注册信号捕捉
    sigaction( SIGALRM,&act,NULL);

    struct itimerval new_value;
    //设置间隔的时间,秒和微秒
    new_value.it_interval.tv_sec = 2;
    new_value.it_interval.tv_usec = 0;
    //设置延迟的时间
    new_value.it_value.tv_sec = 3;
    new_value.it_value.tv_usec = 0;

    int ret = setitimer(ITIMER_REAL,&new_value,NULL);//非阻塞
    printf("定时器开始了。。。。\n");
    if(ret == -1){
        perror("setitimer");
        return -1;
    }

    //getchar();

    while(1){}

    return 0;
}

在这里插入图片描述

27 SIGCHLD信号

在这里插入图片描述

查看信号的命令 kill -l

在这里插入图片描述

在这里插入图片描述

/*
    SIGCHLD信号产生的3个条件:
        1.子进程结束
        2.子进程暂停了
        3.子进程继续运行
        都会给父进程发送该信号,父进程默认忽略该信号。
    
    使用SIGCHLD信号解决僵尸进程的问题。
*/
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<signal.h> 
#include<sys/wait.h>
void myFun(int num){
    printf("捕捉到的信号: %d\n",num);
    //捕捉到信号说明有子进程结束了,
    //结束之后要回收子进程PCB的资源
    //wait(NULL);
    while(1){
        int ret = waitpid(-1,NULL,WNOHANG);
        if(ret > 0){
            printf("child die, pid = %d\n",ret);
        }else if(ret == 0){
            //说明还有子进程活着
            break;
        }else if(ret == -1){
            //没有子进程,说明都回收完毕
            break;
        }
    }
}
int main(){

    //提前设置好阻塞信号集,阻塞SIGCHLD,因为有可能子进程很快结束,父进程还没有注册好信号捕捉
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set,SIGCHLD);
    sigprocmask(SIG_BLOCK,&set,NULL);

    pid_t pid;
    //创建20个子进程
    for(int i=0;i<20;++i){
        pid = fork();
        if(pid == 0){
            //禁止子进程来创建子进程
            break;
        }
    }

    if(pid > 0){
        //父进程

        //捕捉子进程死亡时发送的SIGCHLD信号,
        //并进行回收(其他时间做自己的事情)
        struct sigaction act;
        act.sa_flags = 0;
        act.sa_handler = myFun;
        sigemptyset(&act.sa_mask);

        //注册信号捕捉
        sigaction(SIGCHLD,&act,NULL);

        //注册完信号捕捉以后,解除阻塞
        sigprocmask(SIG_UNBLOCK,&set,NULL);
        
        while(1){
            printf("parent process, pid : %d\n",getpid());
            sleep(2);
        }
    }else if(pid == 0){
        //子进程
        printf("child process,pid : %d\n",getpid());
    }


    return 0;
}

28 共享内存

01 共享内存

在这里插入图片描述

02 共享内存使用步骤

在这里插入图片描述

03 共享内存操作函数

在这里插入图片描述

共享内存相关的函数
#include <sys/ipc.h>
#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);
    - 功能:创建一个新的共享内存段,或者获取一个既有的共享内存段的标识。
        新创建的内存段中的数据都会被初始化为0
    - 参数:
        - key : key_t类型是一个整形,通过这个找到或者创建一个共享内存。
                一般使用16进制表示,非0- size: 共享内存的大小(共享内存以页为单位,size=5,就是有5页,linux的一页等于4kb,5页就是20kb)
        - shmflg: 属性
            - 访问权限
            - 附加属性:创建/判断共享内存是不是存在
                - 创建:IPC_CREAT
                - 判断共享内存是否存在: IPC_EXCL , 需要和IPC_CREAT一起使用
                    IPC_CREAT | IPC_EXCL | 0664
        - 返回值:
            失败:-1 并设置错误号
            成功:>0 返回共享内存的引用的ID,后面操作共享内存都是通过这个值。


void *shmat(int shmid, const void *shmaddr, int shmflg);
    - 功能:和当前的进程进行关联
    - 参数:
        - shmid : 共享内存的标识(ID),由shmget返回值获取
        - shmaddr: 申请的共享内存的起始地址,指定NULL,内核指定
        - shmflg : 对共享内存的操作
            - 读 : SHM_RDONLY, 必须要有读权限
            - 读写: 0
    - 返回值:
        成功:返回共享内存的首(起始)地址。  失败(void *) -1


int shmdt(const void *shmaddr);
    - 功能:解除当前进程和共享内存的关联
    - 参数:
        shmaddr:共享内存的首地址
    - 返回值:成功 0, 失败 -1

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
    - 功能:对共享内存进行操作。删除共享内存,共享内存要删除才会消失,创建共享内存的进程  被销毁了对共享内存是没有任何影响。
    - 参数:
        - shmid: 共享内存的ID
        - cmd : 要做的操作
            - IPC_STAT : 获取共享内存的当前的状态
            - IPC_SET : 设置共享内存的状态
            - IPC_RMID: 标记共享内存被销毁
        - buf:需要设置或者获取的共享内存的属性信息
            - IPC_STAT : buf存储数据
            - IPC_SET : buf中需要初始化数据,设置到内核中
            - IPC_RMID : 没有用,NULL

key_t ftok(const char *pathname, int proj_id);
    - 功能:根据指定的路径名,和int值,生成一个共享内存的key
    - 参数:
        - pathname:指定一个存在的路径
            /home/nowcoder/Linux/a.txt
            / 
        - proj_id: int类型的值,但是这系统调用只会使用其中的1个字节
                   范围 : 0-255  一般指定一个字符 'a'


问题1:操作系统如何知道一块共享内存被多少个进程关联?
    - 共享内存维护了一个结构体struct shmid_ds 这个结构体中有一个成员 shm_nattch
    - shm_nattach 记录了关联的进程个数

问题2:可不可以对共享内存进行多次删除 shmctl
    - 可以的
    - 因为shmctl 标记删除共享内存,不是直接删除
    - 什么时候真正删除呢?
        当和共享内存关联的进程数为0的时候,就真正被删除
    - 当共享内存的key为0的时候,表示共享内存被标记删除了
        如果一个进程和共享内存取消关联,那么这个进程就不能继续操作这个共享内存。也不能进行关联。

    共享内存和内存映射的区别
    1.共享内存可以直接创建,内存映射需要磁盘文件(匿名映射除外)
    2.共享内存效果更高
    3.内存
        所有的进程操作的是同一块共享内存。
        内存映射,每个进程在自己的虚拟地址空间中有一个独立的内存。
    4.数据安全
        - 进程突然退出
            共享内存还存在
            内存映射区消失
        - 运行进程的电脑死机,宕机了
            数据存在在共享内存中,没有了
            内存映射区的数据 ,由于磁盘文件中的数据还在,所以内存映射区的数据还存在。

    5.生命周期
        - 内存映射区:进程退出,内存映射区销毁
        - 共享内存:进程退出,共享内存还在,标记删除(所有的关联的进程数为0),或者关机
            如果一个进程退出,会自动和共享内存进行取消关联。

write_shm.c

#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<string.h>
int main(){

    //1、创建一个共享内存
    int shmid = shmget(100,4096,IPC_CREAT | 0664);
    printf("shmid : %d \n",shmid);

    //2、和当前进程进行关联
    void *ptr = shmat(shmid,NULL,0);

    //3、写数据
    char *buf = "hello,world";
    memcpy(ptr,buf,strlen(buf)+1);

    printf("按任意键继续!\n");
    getchar();

    //4、解除关联
    shmdt(ptr);

    //5、删除共享内存
    shmctl(shmid,IPC_RMID,NULL);

    return 0;
}

read_shm.c

#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<string.h>
int main(){

    //1、获取一个共享内存
    int shmid = shmget(100,0,IPC_CREAT);
    printf("shmid : %d \n",shmid);

    //2、和当前进程进行关联
    void *ptr = shmat(shmid,NULL,0);

    //3、读数据
    printf("%s \n",(char *)ptr);

    printf("按任意键继续!\n");
    getchar();

    //4、解除关联
    shmdt(ptr);

    //5、删除共享内存
    shmctl(shmid,IPC_RMID,NULL);

    return 0;
}

04 共享内存操作命令

在这里插入图片描述

29 守护进程

01 终端

在这里插入图片描述

两个指令

tty命令

Linux tty命令用于显示终端机连接标准输入设备的文件名称。

echo $命令

  • echo $$ 返回登录shell的PID
  • echo $? 返回上一个命令的状态,0表示没有错误,其它任何值表明有错误
  • echo $# 返回传递到脚本的参数个数
  • echo $* 以一个单字符串显示所有向脚本传递的参数,与位置变量不同,此选项参数可超过9个
  • echo $! 返回后台运行的最后一个进程的进程ID号
  • echo $@ 返回传递到脚本的参数个数,但是使用时加引号,并在引号中返回每个参数
  • echo $- 显示shell使用的当前选项
  • echo $0 是脚本本身的名字
  • echo $_ 是保存之前执行的命令的最后一个参数
  • echo $1 传入脚本的第一个参数
  • echo $2 传入脚本的第二个参数

在这里插入图片描述

02 进程组

在这里插入图片描述

03 会话

在这里插入图片描述

04 进程组、会话、控制终端之间的关系

在这里插入图片描述

05 进程组、会话操作函数

在这里插入图片描述

06 守护进程

在这里插入图片描述

07 守护进程的创建步骤

在这里插入图片描述

/*
    写一个守护进程,每隔2s获取一下系统时间,将这个时间写入到磁盘文件中
*/
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/time.h>
#include<signal.h>
#include<time.h>
#include<stdlib.h>
#include<string.h>

void work(int num){
    //捕捉到信号之后,获取系统时间,写入磁盘文件
    time_t tm = time(NULL);

    //localtime(&tm)可以把time_t格式的时间转化为本地的时间
    struct tm *loc = localtime(&tm);
    // char buf[1024];

    // sprintf(buf,"%d-%d-%d %d:%d:%d",loc->tm_year,loc->tm_mon,
    //         loc->tm_mday,loc->tm_hour,loc->tm_min,loc->tm_sec);
    // printf("%s \n",buf);

    char *str = asctime(loc);
    int fd = open("time.txt",O_RDWR | O_CREAT | O_APPEND,0664);
    write(fd,str,strlen(str));
    close(fd);

}
int main(){

    //1、创建子进程,退出父进程
    pid_t pid = fork();

    if(pid > 0){
        exit(0);
    }

    //2、将子进程重新创建一个会话
    setsid();

    //3、设置掩码
    umask(022);

    //4、更改工作目录
    chdir("/home/nowcoder/");

    //5、关闭、重定向文件描述符
    //本案例没有打开文件,所以只需要重定向文件描述符
    int fd = open("/dev/null",O_RDWR);
    dup2(fd,STDIN_FILENO);
    dup2(fd,STDOUT_FILENO);
    dup2(fd,STDERR_FILENO);

    //6、业务逻辑

    //捕捉定时信号
    struct sigaction act;
    act.sa_flags = 0;
    act.sa_handler = work;
    sigemptyset(&act.sa_mask);

    sigaction(SIGALRM,&act,NULL);

    struct itimerval val;
    //延迟多少s开始
    val.it_value.tv_sec = 2;
    val.it_value.tv_usec = 0;

    //间隔2s
    val.it_interval.tv_sec = 2;
    val.it_interval.tv_usec = 0;

    //创建定时器
    setitimer(ITIMER_REAL,&val,NULL);

    //不让进程结束
    while(1){
        sleep(10);
    }


    return 0;
}

守护进程挺重要的,需要掌握

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值