第2章Linux多进程开发

进程概述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
ulimit -a查看资源上限

进程状态转换

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
ps aux/ajx 中 stat
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
kill -9/-SIGKILL ID 强制杀死
在这里插入图片描述

进程创建

在这里插入图片描述
示例:

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

int main()
{
    //创建子进程
    pid_t pid = fork();

    //判断是父进程还是子进程
    if(pid > 0)
    {
        printf("pid : %d\n",pid);
        //如果大于0,返回的是创建的子进程的进程号,当前是在父进程中
        printf("i am parent process, pid : %d, ppid : %d\n",getpid(),getppid());
    }
    else if(pid == 0)
    {
        //当前是在子进程中
        printf("i am child process, pid : %d, ppid : %d\n",getpid(),getppid());
    }

    //父子进程共享代码:
    for(int i=0;i<5;i++)
    {
        printf("pid:%d , i:%d\n",getpid(),i);
        sleep(1);
    }
    return 0;
}

父子进程虚拟地址空间情况

Linux的fork()函数使用写时拷贝(copy-on-write)实现。
写时拷贝可以推迟甚至避免拷贝数据。
fork()时,内核不复制整个进程的地址空间,而是让父子进程共享同一个地址空间,只有在需要写入时才会复制地址空间,让各个进程拥有各自的地址空间。
也就是说,资源的复制在需要写入的时候才会进行,在此之前,都以只读方式共享。
fork()后父子进程共享文件,fork产生的子进程和父进程通过文件描述符指向相同的文件表,引用计数增加,共享文件偏移指针。

父子进程关系及GDB多进程调试

父子进程之间的关系:
区别:
  1.fork()函数的返回值不同
    父进程中:>0 返回的是子进程的ID
    子进程中:=0
  2.pcb中的一些数据
    当前的进程的id:pid
    当前的进程的父进程的id:ppid
    信号集
共同点:
  某些状态下,子进程刚被创建出来,还没执行任何的写数据操作
    -用户区的数据
    -文件描述符表
父子进程对变量是否共享?
  -刚开始的时候,是一样的,共享的,如果修改了数据,就不共享了
  -读时共享(子进程被创建,两个进程没有做任何的写操作),写时拷贝
在这里插入图片描述
show follow-fork-mode
show detach-on-fork

exec函数族

在这里插入图片描述
一般从父进程fork出子进程后,在子进程中exec
在这里插入图片描述
在这里插入图片描述
最后一个是linux函数,前6个都是标准C库函数
execl

//需要头文件
#include<unistd.h>
//函数原型
int execl( const char *path, const char *arg, .../* (char *) NULL */);
/*
	参数:
		path:需要指定执行的文件的路径或者名称
		arg:是执行可执行文件所需要的参数列表
			第一个参数一般没有什么作用,为了方便,一般写的是执行的程序的名称
			从第二个参数开始往后,就是程序执行所需要的参数列表
			参数左后需要以NULL结束(哨兵)
	返回值:
		只有失败时返回-1,并设置errno
		调用成功则无返回值
*/

示例:

//execl.c:
#include<unistd.h>
#include<stdio.h>
int main()
{
	//创建一个子进程,在子进程中执行exec函数族中的函数
	pid_t pid = fork();
	if(pid > 0{
		printf("i m parent process, pid:%d\n",getpid());
	}
	else if(pid == 0)
	{
		execl("/bin/ps","ps","aux",NULL);//需要写路径,子进程执行ps -aux,输出查看进程
		perror("execl");
		printf("i m child process\n");//无法执行,子进程被替换了
	}
	
	for(int i=0;i<3;i++)//父进程执行;子进程无法执行,子进程被替换了
	{
		printf("i=%d,pid=%d\n",i,getpid());
	}
	return 0;
}

execlp

//需要头文件
#include<unistd.h>
//函数原型
int execlp( const char *file, const char *arg, .../* (char *) NULL */);
/*
	会到环境变量中查找指定的可执行文件,如果找到了就执行,找不到就执行不成功
	参数:
		path:需要指定执行的文件名
		arg:是执行可执行文件所需要的参数列表
			第一个参数一般没有什么作用,为了方便,一般写的是执行的程序的名称
			从第二个参数开始往后,就是程序执行所需要的参数列表
			参数左后需要以NULL结束(哨兵)
	返回值:
		只有失败时返回-1,并设置errno
		调用成功则无返回值
*/

示例:

//execl.c:
#include<unistd.h>
#include<stdio.h>
int main()
{
	//创建一个子进程,在子进程中执行exec函数族中的函数
	pid_t pid = fork();
	if(pid > 0{
		printf("i m parent process, pid:%d\n",getpid());
	}
	else if(pid == 0)
	{
		execlp("ps","ps","aux",NULL);//execlp直接文件名,不需要路径
		perror("execlp");
		printf("i m child process\n");//无法执行,子进程被替换了
	}
	
	for(int i=0;i<3;i++)//父进程执行;子进程无法执行,子进程被替换了
	{
		printf("i=%d,pid=%d\n",i,getpid());
	}
	return 0;
}

进程退出,孤儿进程,僵尸进程

在这里插入图片描述
第一个是标准c库的,第二个是linux的
常用第一个,做的事情更多
status参数:是进程退出时的一个状态信息,父进程回收子进程资源的时候可以获取到。
示例:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
    printf("hello\n");
    //\n会刷新IO缓冲区,将hello打印出来
    
    printf("world");
    //没有刷新缓冲区,只是将world放入IO缓冲区
    
    //exit(0);
    //调用标准C库exit,会刷新缓冲区,将world打印出来
    
    _exit(0);
    //调用Linux _exit,不刷新缓冲区,不打印

    return 0;
}

在这里插入图片描述
示例:

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

int main()
{
    //创建子进程
    pid_t pid = fork();

    //判断是父进程还是子进程
    if(pid > 0)
    {
        printf("pid : %d\n",pid);
        //如果大于0,返回的是创建的子进程的进程号,当前是在父进程中
        printf("i am parent process, pid : %d, ppid : %d\n",getpid(),getppid());
    }
    else if(pid == 0)
    {
        sleep(1);//睡1秒,让父进程死掉
        //当前是在子进程中
        printf("i am child process, pid : %d, ppid : %d\n",getpid(),getppid());
        //打印ppid:1,因为父进程死了,由进程为1的进程领养并回收
    }

    for(int i=0;i<5;i++)
    {
        printf("pid:%d , i:%d\n",getpid(),i);
    }
    return 0;
}

在这里插入图片描述

进程回收

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

wait

//需要头文件
#include<sys/types.h>
#include<sys/wait.h>
//函数原型
pid_t wait(int *wstatus);
/*
	功能:等待任意一个子进程结束,如果任意一个子进程结束,此函数会回收子进程的资源
	参数:int *wstatus
		进程退出时的状态信息,传入的是一个int类型的地址,传出参数
	返回值:
		成功,返回被回收的子进程的id
		失败,返回-1(所有子进程都结束,或调用函数失败)
		
	调用wait函数的进程会被挂起(阻塞),直到它的一个子进程退出或者收到一个不能被忽略的信号时
	才被唤醒(相当于继续往下执行)
	如果没有子进程了,函数立即返回,返回-1,如果子进程都已经结束了,也立即返回-1
*/

示例:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
	//有一个父进程
    pid_t pid;

    //创建5个子进程(兄弟)
    for(int i=0;i<5;i++)
    {
        pid=fork();
        if(pid == 0)break;//子进程不要fork
    }

    if(pid > 0)
    {
        //父进程
        while(1)
        {
            printf("parent,pid=%d\n",getpid());
            //int ret = wait(NULL);
            int st;
            int ret = wait(&st);
            if(ret == -1)break;

            if(WIFEXITED(st))
            {
                //是否正常退出
                printf("退出的状态码:%d\n",WEXITSTATUS(st));
            }
            if(WIFSIGNALED(st))
            {
                //是否异常终止
                printf("被哪个信号干掉了:%d\n",WTERMSIG(st));
            }

            printf("chile die, pid=%d\n",ret);
            sleep(1);
        }
    }
    else if (pid == 0)
    {
        //子进程
        printf("child,pid=%d\n",getpid());
        sleep(1);
        
        exit(0);//其中的参数为退出的状态码
    }

    return 0;
}

waitpid

//需要头文件
#include<sys/types.h>
#include<sys/wait.h>
//函数原型
pid_t waitpid(pid_t pid, int *wstatus, int options);
/*
	功能:回收指定进程号的子进程,可以设置是否阻塞
	参数:
		-pid_t pid:
			pid>0:某个要回收的子进程的pid
			pid=0:回收当前进程组的所以子进程	(也会用)
			pid=-1:回收所有的子进程,相当于wait()  (最常用)
			pid<-1:某个进程组的组id的绝对值,回收指定进程组中的子进程
		-int *wstatus:
			进程退出时的状态信息,传入的是一个int类型的地址,传出参数
		-int options:
			设置阻塞或非阻塞
			0:阻塞
			WNOHANG:非阻塞
			
		当设置为waitpid( -1, &wstatus, 0 );时,相当于wait(&wstatus)函数
	返回值:
		>0:返回被回收的子进程的id
		=0:options=WNOHANG,表示还有子进程活着
		=-1:错误,或没有子进程了
*/

示例:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
	//有一个父进程
    pid_t pid;

    //创建5个子进程(兄弟)
    for(int i=0;i<5;i++)
    {
        pid=fork();
        if(pid == 0)break;//子进程不要fork
    }

    if(pid > 0)
    {
        //父进程
        while(1)
        {
            printf("parent,pid=%d\n",getpid());
            sleep(1);
            //int ret = wait(NULL);
            int st;
            int ret = waitpid(-1,&st,WNOHANG);
            if(ret == -1)break;
            else if(ret == 0)
            {
                //说明还有子进程存在
                continue;
            }
            else if(ret>0)
            {
                if(WIFEXITED(st))
                {
                    //是否正常退出
                    printf("退出的状态码:%d\n",WEXITSTATUS(st));
                }
                if(WIFSIGNALED(st))
                {
                    //是否异常终止
                    printf("被哪个信号干掉了:%d\n",WTERMSIG(st));
                }
                 printf("chile die, pid=%d\n",ret);
            }
        }
    }
    else if (pid == 0)
    {
        //子进程
        printf("child,pid=%d\n",getpid());
        sleep(1);
        
        exit(0);//其中的参数为退出的状态码
    }

    return 0;
}

进程间通信

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

匿名管道

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

//需要头文件
#include<unistd.h>
//函数原型
int pipe(int pipefd[2]);
/*
	作用:创建一个匿名管道,用来进程间通信
	参数:
		int pipefd[2]:传出参数
		pipefd[0]对应管道读端
		pipefd[1]对应管道写端
	返回值:
		成功:0
		失败:-1 并改errno
	
	管道默认是阻塞的,如果管道中没有数据,read阻塞,如果管道满了,write阻塞
	
	注意:匿名管道只能用于具有关系的进程之间的通信(父子进程,兄弟进程)
*/

示例:

#include<unistd.h>
#include<sys/types.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//父进程读,子进程写
int main()
{
    //在fork之前创建管道
    int pipefd[2];
    int ret = pipe(pipefd);
    if(ret == -1)
    {
        perror("pipe");
        exit(0);
    }

    //创建子进程
    pid_t pid = fork();
    if(pid > 0)
    {//父进程
        printf("i am parent process,pid:%d\n",getpid());

        //关闭写端;防止自己读自己写
        close(pipefd[1]);
        char buf[1024]={0};
        while(1)
        {
            //从管道的读取端获取数据
            int len = read(pipefd[0],buf,sizeof(buf));
            printf("parent recv : %s,pid : %d\n",buf,getpid());

            //向管道中写入数据
            //char *str = "hello, i am parent";
            //write(pipefd[1],str,strlen(str));
            //sleep(1);
        }
    }
    else if(pid == 0)
    {//子进程
        printf("i am child process,pid:%d\n",getpid());
        //关闭读端
        close(pipefd[0]);
        //char buf[1024]={0};
        while(1)
        {
            //向管道中写入数据
            char *str = "hello, i am child";
            write(pipefd[1],str,strlen(str));
            //sleep(1);

            //从管道的读取端获取数据
            //int len = read(pipefd[0],buf,sizeof(buf));
            //printf("child recv : %s,pid : %d\n",buf,getpid());
            //bzero(buf,1024);
        }
    }
    return 0;
}

fpathconf
示例:

#include<unistd.h>
#include<stdio.h>
int main()
{
    int pipefd[2];
    int ret = pipe(pipefd);

    //获取管道的大小
    long size = fpathconf(pipefd[0],_PC_PIPE_BUF);
    printf("pipe size: %ld\n",size);
    
    return 0;
}

案例:

/*
    实现 ps aux | grep xxx 父子进程间通信
    子进程:ps aux,子进程结束后,将数据发给父进程
    父进程:获取数据,过滤
    pipe()
    execlp()
    子进程将标准输出stdout_fileno重定向到管道的写端。dup2
*/
#include<unistd.h>
#include<sys/types.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<wait.h>
int main()
{
    //创建一个管道
    int fd[2];
    int ret = pipe(fd);
    if(ret==-1)
    {
        perror("pipe");
        exit(0);
    }

    //创建子进程
    pid_t pid = fork();
    if(pid>0)
    {   //父进程
        //关闭写端
        close(fd[1]);
        //从管道中获取
        char buf[1024]={0};
        int len = -1;
        while((len = read(fd[0],buf,sizeof(buf)-1))>0)
        {
            //过滤数据输出
            printf("%s",buf);
            memset(buf,0,1024);
        }
        wait(NULL);
    }
    else if(pid==0)
    {   //子进程
        //关闭读端
        close(fd[0]);
        //文件描述符的重定向 stdout_fileno -> fd[1]
        dup2(fd[1],STDOUT_FILENO);
        //执行ps aux
        execlp("ps","ps","aux",NULL);
        perror("execlp");
        exit(0);
    }
    else
    {
        perror("fork");
        exit(0);
    }
}

管道的读写特点

使用管道时,应注意以下几种特殊的情况(假设都是阻塞I/O操作)
  1.所有的指向管道写端的文件描述符都关闭了(管道写端引用计数为0),
    有进程从管道的读端读数据,那么管道中剩余的数据被读取以后,
    再次read会返回0,就像读到文件末尾一样
  2.如果有指向管道写端的文件描述符没有关闭(管道写端引用计数>0),
    而持有管道写端的进程也没有往管道中写数据,这个时候,
    有进程从管道中读取数据,那么管道中剩余的数据被读取后,
    再次read会阻塞,直到管道中有数据可以读了,才读取数据并返回
  3.如果所有指向管道读端的文件描述符都关闭了(管道读端引用计数为0),
    这个时候有进程向管道中写数据,那么该进程会收到一个信号SIGPIPE,
    通常会导致进程异常终止
  4.如果有指向管道读端的文件描述符没有关闭了(管道读端引用计数>0),
    而持有管道读端的进程也没有从管道中读数据,这时,
    有进程向管道中写数据,那么在管道被写满的时候,再次write会阻塞
    直到管道中有空位置才能再次写入并返回

总结:
  读管道:
    管道中有数据:read返回实际读到的字节数
    管道中无数据:
      写端被全部关闭,read返回0,(相当于读到文件末尾)
      写端没有被完全关闭,read阻塞等待
  写管道:
    管道读端全部被关闭:进程异常终止(进程收到SIGPIPE信号)
    管道读端没有被全部关闭:
      管道已满:write阻塞
      管道没满:write将数据写入,并返回实际写入的字节数

管道设置为非阻塞

#include<unistd.h>
#include<sys/types.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
/*
 设置管道非阻塞
 int flags = fcntl(fd[0],F_GETFL);获取原来的flag 
 flags |= O_NONBLOCK;修改flag
 fcntl(fd[0],F_SETFL,flags);设置新的flag
*/
int main()
{
    //在fork之前创建管道
    int pipefd[2];
    int ret = pipe(pipefd);
    if(ret == -1)
    {
        perror("pipe");
        exit(0);
    }

    //创建子进程
    pid_t pid = fork();
    if(pid > 0)
    {//父进程
        printf("i am parent process,pid:%d\n",getpid());

        //关闭写端;防止自己读自己写
        close(pipefd[1]);
        char buf[1024]={0};
        
        int flags = fcntl(pipefd[0],F_GETFL);    //获取原来的flag 
        flags |= O_NONBLOCK;                     //修改flag
        fcntl(pipefd[0],F_SETFL,flags);          //设置新的flag
        while(1)
        {
            //从管道的读取端获取数据
            int len = read(pipefd[0],buf,sizeof(buf));
            printf("len: %d\n",len);
            printf("parent recv : %s,pid : %d\n",buf,getpid());
            memset(buf,0,1024);
            sleep(1);
        }
    }
    else if(pid == 0)
    {//子进程
        printf("i am child process,pid:%d\n",getpid());
        //关闭读端
        close(pipefd[0]);
        while(1)
        {
            //向管道中写入数据
            char *str = "hello, i am child";
            write(pipefd[1],str,strlen(str));
            sleep(5);
        }
    }
    return 0;
}

有名管道

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

//需要头文件
#include<sys/types.h>
#include<sys/stat.h>
//函数原型
int mkfifo(const char *pathname, mode_t mode);
/*
	参数:
		-pathname:管道名称的路径
		-mode:文件的权限,和open的mode一样
	返回值:
		成功:0
		失败:-1 并设置errno
	
	注意:
		1.一个为只读而打开一个管道的进程会阻塞,直到另一个进程为写而打开管道
		2.一个为只写而打开一个管道的进程会阻塞,直到另一个进程为读而打开管道
		
		读管道:
			管道中有数据,read返回实际读到的字节数
			管道中无数据:
				管道写端被全部关闭,read返回0(相当于读到文件末尾)
				写端没有全部关闭,read阻塞等待
		写管道:
			管道读端被全部关闭,进程异常终止(收到一个SIGPIPE信号)
			管道读端没有全部关闭:
				管道已经满了,write会阻塞
				管道没有满,write将数据写入,并返回实际写入的字节数
	
*/

示例write.c read.c

//write.c
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
//向管道中写数据

int main()
{
    //1.判断管道是否存在
    int ret = access("test",F_OK);
    if(ret == -1)
    {
        printf("管道不存在,创建管道\n");
        
        //2.创建管道文件
        ret = mkfifo("test",0664);
        if(ret == -1)
        {
            perror("mkfifo");
            exit(0);
        }
    }

    //3.以只写方式打开管道
    int fd = open("test",O_WRONLY);
    if (fd==-1)
    {
        perror("open");
        exit(0);
    }

    //4.写数据
    for(int i=0;i<100;i++)
    {
        char buf[1024];
        sprintf(buf,"hello, %d\n",i);
        printf("write data: %s\n",buf);
        write(fd,buf,strlen(buf));
        sleep(1);
    }

    close(fd);
    
    return 0;
}


//read.c
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
//从管道中读数据

int main()
{
    //1.只读方式打开管道文件
    int fd = open("test",O_RDONLY);
    if (fd == -1)
    {
        perror("open");
        exit(0);
    }

    //2.读数据
    while(1)
    {
        char buf[1024]={0};
        int len = read(fd,buf,sizeof(buf));
        if(len == 0)
        {
            printf("写端断开连接了...\n");
            break;
        }
        printf("recv buf : %s\n",buf);
    }

    close(fd);
    return 0;
}

使用有名管道完成聊天功能

在这里插入图片描述
chatA.c

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
int main()
{
    //1.判断有名管道文件是否存在
    int ret = access("fifo1",F_OK);
    if(ret == -1)
    {   //文件不存在
        printf("管道不存在,创建对应有名管道\n");
        ret = mkfifo("fifo1",0664);
        if(ret == -1)
        {
            perror("mkfifo");
            exit(0);
        }
    }

    ret = access("fifo2",F_OK);
    if(ret == -1)
    {   //文件不存在
        printf("管道不存在,创建对应有名管道\n");
        ret = mkfifo("fifo2",0664);
        if(ret == -1)
        {
            perror("mkfifo");
            exit(0);
        }
    }

    //2.以只写方式打开管道fifo1
    int fdw = open("fifo1",O_WRONLY);
    if(fdw == -1)
    {
        perror("open");
        exit(0);
    }
    printf("打开管道fifo1成功,等待写入...\n");

    //3.以只读方式打开管道fifo2
    int fdr = open("fifo2",O_RDONLY);
    if(fdr == -1)
    {
        perror("open");
        exit(0);
    }
    printf("打开管道fifo2成功,等待读取...\n");

    //4.循环写读数据
    char buf[128];
    while(1)
    {
        //写数据
        memset(buf,0,128);
        //获取标准输入的数据
        fgets(buf,128,stdin);
        ret = write(fdw,buf,strlen(buf));
        if(ret == -1)
        {
            perror("write");
            exit(0);
        }

        //读数据
        memset(buf,0,128);
        ret = read(fdr,buf,128);
        if(ret <= 0)
        {
            perror("read");
            break;
        }
        printf("buf:%s\n",buf);
    }
    //关闭文件描述符
    close(fdr);
    close(fdw);

    return 0;
}

chatB.c

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
int main()
{
    //1.判断有名管道文件是否存在
    int ret = access("fifo1",F_OK);
    if(ret == -1)
    {   //文件不存在
        printf("管道不存在,创建对应有名管道\n");
        ret = mkfifo("fifo1",0664);
        if(ret == -1)
        {
            perror("mkfifo");
            exit(0);
        }
    }

    ret = access("fifo2",F_OK);
    if(ret == -1)
    {   //文件不存在
        printf("管道不存在,创建对应有名管道");
        ret = mkfifo("fifo2",0664);
        if(ret == -1)
        {
            perror("mkfifo");
            exit(0);
        }
    }

    //2.以只读方式打开管道fifo1
    int fdr = open("fifo1",O_RDONLY);
    if(fdr == -1)
    {
        perror("open");
        exit(0);
    }
    printf("打开管道fifo1成功,等待读取...\n");

    //3.以只写方式打开管道fifo2
    int fdw = open("fifo2",O_WRONLY);
    if(fdw == -1)
    {
        perror("open");
        exit(0);
    }
    printf("打开管道fifo2成功,等待写入...\n");

    //4.循环读写数据
    char buf[128];
    while(1)
    {
        //读数据
        memset(buf,0,128);
        ret = read(fdr,buf,128);
        if(ret <= 0)
        {
            perror("read");
            break;
        }
        printf("buf:%s\n",buf);
        
        //写数据
        memset(buf,0,128);
        //获取标准输入的数据
        fgets(buf,128,stdin);
        ret = write(fdw,buf,strlen(buf));
        if(ret == -1)
        {
            perror("write");
            exit(0);
        }
    }
    //关闭文件描述符
    close(fdr);
    close(fdw);

    return 0;
}

内存映射

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

//需要头文件
#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<sys/mman.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
#include<wait.h>
#include<stdlib.h>
int main()
{
    //1.打开一个文件
    int fd = open("test.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");
        exit(0);
    }

    //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!!!");
    }

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

注意事项:
在这里插入图片描述
在这里插入图片描述

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

用内存映射做小文件的拷贝

//使用内存映射实现文件拷贝的功能
/*
    思路:
        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<stdlib.h>
#include<string.h>
int main()
{
    //1.对原始的文件进行内存映射
    int fd = open("english.txt",O_RDWR);
    if(fd == -1)
    {
        perror("open");
        exit(0);
    }
    //获取原文件的大小
    int len = lseek(fd,0,SEEK_END);

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

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

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

    //5.释放资源
    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<stdlib.h>
#include<string.h>
#include<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");
        exit(0);
    }

    //2.父子进程间通信
    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);
    }

    //3.释放内存映射区
    int ret = munmap(ptr,len);
    if(ret == -1)
    {
        perror("munmap");
        exit(0);
    }
    return 0;
}

信号

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

kill、raise、abort、alarm、setitimer函数

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

//需要头文件
#include<signal.h>
//函数原型
int raise(int sig);
/*
	功能:给当前进程发送信号
	参数:
		-sig:要发送的信号
	-返回值:
		成功:0
		失败:非0
	kill(getpid(),sig);
*/

//需要头文件
#include<stdlib.h>
//函数原型
void abort(void);
/*
	功能:发送SIGABRT信号给当前进程,杀死当前进程
	kill(getpid(),ABRT);
*/


//需要头文件
#include<unistd.h>
//函数原型
unsigned int alarm(unsigned int seconds);
/*
	功能:设置定时器(闹钟),函数调用,开始倒计时,当倒计时为0时,函数会给当前进程发送一个信号:SIGALRM
	参数:
		seconds:倒计时的时长,单位:秒,如果参数为0,表示定时器无效(不倒计时,不发信号)。
		取消一个定时器,通过alarm(0)
	返回值:
		之前没有定时器,返回0
		之前有定时器,返回之前的定时器剩余的时间
	
	SIGALARM:默认终止当前的进程,每一个进程都有且只有唯一的一个定时器
		alarm(10); -->返回0
		过1秒
		alarm(5); -->返回9,覆盖前面的定时器
	alarm(100) -->该函数是不阻塞的
	
	定时器与进程的状态无关(自然定时法),无论进程处于什么状态,alarm都会计时
*/

//需要头文件
#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秒定时1次

		-old_value:
			记录上一次的定时的时间参数,一般不使用,NULL
		
	返回值:
		成功:0
		失败:-1,并设置错误号
	非阻塞
struct itimerval new_value;
设置间隔的时间
new_value.it_interval.tv_sec=2;
new_value.it_interval.tv_usec=0;
设置延迟的时间,3秒后开始第一次定时
new_value.it_value.tv.sec=3;
new_value.it_value.tv.usec=0;
setitimer(ITIMER_REAL,&new_value,NULL);
*/

signal信号捕捉函数

在这里插入图片描述

//需要头文件
#include<signal.h>
//函数原型
typedef void (*sighandler_t)(int);
sighander_t signal(int signum,sighandler_t handler);
/*
	功能:
		设置某个信号的捕捉行为
	参数:
		signum:要捕捉的信号
		handler:捕捉到信号要如何处理
			-SIG_IGN:忽略信号
			-SIG_DFL:使用信号的默认行为
			-回调函数:这个函数是内核调用,程序员只负责写:捕捉到信号后如何处理
				回调函数:
					-需要程序员实现,提前准备好的,函数类型根据实际需求,看函数指针的定义
					-不是程序员调用,而是当信号产生,有框架(此处内核)调用
					-函数指针是实现回调的手段,函数实现之后,将函数名放到函数指针的位置就可以了
	返回值:
		成功:返回上一次注册的信号处理函数的地址,第一次调用返回NULL
		失败:返回SIG_ERR,设置错误号
		
	SIGKILL SIGSTOP不能被捕捉,不能被忽略
*/

signal示例:

#include<sys/time.h>
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
//过3秒,每隔2秒定一次时

//回调函数
void myalarm(int num)
{
    printf("捕捉到信号,编号:%d\n",num);
    printf("xxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
}

int main()
{
    //先注册信号捕捉
    //signal(SIGALRM,SIG_IGN);//忽略
    //signal(SIGALRM,SIG_DFL);//默认中止
    //void (*sighandler_t)(int);函数指针,int型参数表示捕捉到的信号编号
    signal(SIGALRM,myalarm);//使用回调函数

    struct itimerval new_value;

    //设置间隔的时间
    new_value.it_interval.tv_sec=2;
    new_value.it_interval.tv_usec=0;

    //设置延迟的时间,3秒后开始第一次定时
    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");
        exit(0);
    }
    getchar();
    return 0;
}

信号集

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
以下信号集相关的函数都是对自定义的信号集进行操作:

//需要头文件
#include<signal.h>
//函数原型
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>
//-std=gnu99
int main()
{
    //创建一个信号集
    sigset_t set;

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

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

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

    //判断SIGINT是否在信号集中
    ret = sigismember(&set,SIGINT);
    if(ret == 0)
    {
        printf("SIGINT 不阻塞\n");
    }
    else if(ret == 1)
    {
        printf("SIGINT 阻塞\n");
    }
    
    //从信号集中删除一个信号
    sigdelset(&set,SIGINT);

    //判断SIGINT是否在信号集中
    ret = sigismember(&set,SIGINT);
    if(ret == 0)
    {
        printf("SIGINT 不阻塞\n");
    }
    else if(ret == 1)
    {
        printf("SIGINT 阻塞\n");
    }
    return 0;
}
//需要头文件
//函数原型
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
/*
	功能:将自定义信号集中的数据设置到内核中(设置阻塞,解除阻塞,替换)
	参数:
		-how:如何对内核阻塞信号集进行处理
			SIG_BLOCK:将用户设置的阻塞信号添加到内核中,内核中原来的数据不变
				假设内核中默认的阻塞信号集是mask.    mask | set
			SIG_UNBLOCK:根据用户设置的数据,对内核中的数据进行解除阻塞
				假设内核中默认的阻塞信号集是mask.    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");
                exit(0);
            }
        }
        printf("\n");
        sleep(1);
        if(num==10)
        {
            //解除阻塞
            sigprocmask(SIG_UNBLOCK,&set,NULL);
        }
    }
    return 0;
}

sigaction信号捕捉函数(多使用sigaction,少使用signal)

在这里插入图片描述

//需要头文件
#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;	//临时阻塞信号集,在信号捕捉函数执行过程中,临时阻塞某些信号
	int sa_flags;	//使用哪一个信号处理对捕捉到的信号进行处理,
					//可以是0,表示使用sa_handler,也可以是SA_SIGINFO,表示使用sa_sigaction
	void (*sa_restorer)(void);	//被废弃了,NULL
}

示例:

#include<sys/time.h>
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
//过3秒,每隔2秒定一次时

//回调函数
void myalarm(int num)
{
    printf("捕捉到信号,编号:%d\n",num);
    printf("xxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
}

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;

    //设置延迟的时间,3秒后开始第一次定时
    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");
        exit(0);
    }
    //getchar();
    while(1){}
    return 0;
}

在这里插入图片描述

SIGCHLD信号

在这里插入图片描述
使用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的资源
    //while (1)
    //{
    //    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;
    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("chlid process pid:%d\n",getpid());
    }
}

共享内存

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

//需要头文件
#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:共享内存的大小
        -shmflg:属性
            -访问权限
            -附加属性:创建/判断共享内存是不是存在
                -创建:IPC_CREAT
                -判断共享内存是否存在:IPC_EXCL,需要和IPC_CREAT一起使用
                    IPC_EXCL | IPC_CREAT |0664
    返回值:
        失败:-1,并设置errno
        成功:>0,返回共享内存的引用的ID,后面操作共享内存都是通过这个值
*/

//需要头文件
#include <sys/types.h>
#include <sys/shm.h>
//函数原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
/*
    功能:和当前的进程进行关联
    参数:
        -shmid:共享内存的标识(ID),由shmget返回值获取
        -shmadrr:申请的共享内存的起始地址,可设为NULL,由内核来指定
        -shmflg:对共享内存的操作
            -读:SHM_RDONLY,必须要有读权限
            -读写:0
    返回值:
        成功:返回共享内存的首(起始)地址
        失败:(void*)-1
*/

//需要头文件
#include <sys/types.h>
#include <sys/shm.h>
//函数原型
int shmdt(const void *shmaddr);
/*
    功能:解除当前进程和共享内存的关联
    参数:
        shmaddr:共享内存的首地址
    返回值:
        成功:0
        失败:-1
*/

//需要头文件
#include <sys/types.h>
#include <sys/shm.h>
//函数原型
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
*/

//需要头文件
#include <sys/types.h>
#include <sys/ipc.h>
//函数原型
key_t ftok(const char *pathname, int proj_id);
/*
    功能:根据指定的路径名和int值,生成一个共享内存的key
    参数:
        -pathname:指定一个存在的路径
        -proj_id:int类型的值,但是这个系统调用只会使用其中的1个字节
            范围:0-255 一般指定一个字符 'a'
*/

问题1:操作系统如何知道一块共享内存被多少个进程关联?
  -共享内存维护了一个结构体struct shmid_ds ,这个结构体中有一个成员shm_nattach
  -shm_nattach 记录了关联的进程个数
在这里插入图片描述
问题2:可不可以对共享内存进行多次删除 shmctl
  -可以
  -因为shmctl只是标记删除,不是真删除
    -当和共享内存关联的进程数为0的时候,就真删除了
  -共享内存的key为0的时候
    -共享内存被标记删除了
  -如果一个进程和共享内存取消关联,这个进程就不能继续操作这个共享内存,也不能再次关联

共享内存和内存映射的区别
  1.共享内存可以直接创建,内存映射需要磁盘文件(匿名映射除外)
  2.共享内存效率更高
  3.内存
    所有的进程操作的是同一块共享内存
    内存映射,每个进程在自己的虚拟地址空间中有一个独立的内存
  4.数据安全
    -进程突然退出
      共享内存还存在
      内存映射区消失
    -运行进程的电脑死机,宕机
      数据存在在共享内存中,没有了
      内存映射区的数据,由于磁盘文件中的数据还在,所有内存映射区的数据还存在
  5.生命周期
    -内存映射区:进程退出,内存映射区销毁
    -共享内存:进程退出,共享内存还在,手动标记删除(所有关联进程数为0),或者关机
      如果一个进程退出,会自动和共享内存进行取消关联

守护进程

在这里插入图片描述
查看当前终端:
tty
查看当前终端的进程号:
echo $$
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

写一个守护进程,每隔2s获取一下系统时间,将这个时间写入到磁盘文件中
示例:

/*
写一个守护进程,每隔2s获取一下系统时间,将这个时间写入到磁盘文件中
*/
#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<unistd.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);
    struct tm *loc = localtime(&tm);
    //char buf[1024];
    //sprintf(buf,"%d-%d-%d %d:%d:%d\n",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/jeon/");

    //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;
    val.it_value.tv_sec = 2;
    val.it_value.tv_usec = 0;
    val.it_interval.tv_sec = 2;
    val.it_interval.tv_usec = 0;
    setitimer(ITIMER_REAL,&val,NULL);
    //不让进程结束
    while(1)
    {
        sleep(10);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值