Linux系统编程Day06

1.进程状态

1.1 孤儿进程
	父进程运行结束,但子进程还在运行(未运行结束)的子进程就称为孤儿进程
	每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为 init ,而 init 
进程会循环地 wait() 它的已经退出的子进程。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>

int main()
{
        pid_t pid = -1;

        //创建一个子进程
        pid = fork();
        if(-1 == pid)
        {
                perror("fork");
                return 1;
        }
        //父进程
        if(pid > 0)
        {
                printf("父进程休息1s后退出\n");
                sleep(1);
                printf("父进程退出\n");
                exit(0);
        }

        //子进程
        while(1)
        {
                printf("子进程工作,ppid = %d\n",getppid());
                sleep(1);
        }

        return 0;
}

1.2 僵尸进程
	进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸进
程。
	这样就会导致一个问题,如果进程不调用wait() 或 waitpid() 的话, 那么保
留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有
限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进
程,此即为僵尸进程的危害,应当避免。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>

//创建僵尸进程
int main()
{
        pid_t pid = -1;

        //创建一个子进程
        pid = fork();
        if(-1 == pid)
        {
                perror("fork");
                return 1;
        }

        //子进程
        if(pid == 0)
        {
                for(int i = 0;i<5;i++)
                {
                        printf("子进程运行:%d\n",i);
                        sleep(1);
                }

                printf("子进程结束\n");
                exit(0);
        }

        sleep(100);
        printf("父进程睡醒了,结束\n");

        return 0;
}

1.3 进程替换
	exec 函数族的作用是根据指定的文件名或目录名找到可执行文件,并用它来取代调
用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。
	#include <unistd.h>
	extern char **environ;
	​
	int execl(const char *path, const char *arg, .../* (char  *) NULL */);
	int execlp(const char *file, const char *arg, ... /* (char  *) NULL */);
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main()
{

        printf("helloworld!\n");
        //1.execlp
        //arg0 arg1 ... argn
        //arg0一般是可执行文件名 argn必须是NULL
        //等价于执行ls -l /home
        //execlp("ls","ls","-l","/home",NULL);

        //2.execl
        //第一个参数一般是可执行文件的绝对路径
        //第二个参数一般是可执行文件的名字
        //中间参数是可执行文件的参数
        //最后一个参数是NULL
        execl("/bin/ls","ls","-l","/home",NULL);


        printf("hello execlp\n");

        return 0;
}

	int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);
	int execv(const char *path, char *const argv[]);
	int execvp(const char *file, char *const argv[]);
	int execvpe(const char *file, char *const argv[], char *const envp[]);
	​
	int execve(const char *filename, char *const argv[], char *const envp[]);
#define _GNU_SOURCE
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>

//进程替换
int main()
{
        //ls -l /home
        char* argvs[] = {"ls","-l","/home",NULL};
        char* envp[] = {"ADDR = XIAAN",NULL};

        printf("hello wolrd\n!");
        //1.execvp
        //第一个参数是可执行文件
        //第二个参数是指针数组 最后以NULL结尾
        //execvp("ls",argvs);

        //2.execv
        //第一个参数是可执行文件的绝对路径
        //第二个参数是指针数组 最后以NULL结尾
        //execv("/bin/ls",argvs);

        //3.execle
        //最后一个参数是环境变量指针数组,以NULL结尾
        //execle("/bin/ls","ls","-l","/home",NULL,envp);

        //4.execvpe
        //需要定义宏变量 _GNU_SOURCE
        //第一个参数是可执行文件
        //第二个参数是参数列表 指针数组
        //第三个参数是环境变量 指针数组
        execvpe("ls",argvs,envp);


        printf("hello execle!\n");
        return 0;
}

	其中只有 execve() 是真正意义上的系统调用,其它都是在此基础上经过包装的
库函数。
	进程调用一种 exec 函数时,该进程完全由新程序替换,而新程序则从其 main
函数开始执行。因为调用 exec 并不创建新进程,所以前后的进程 ID (当然还有父
进程号、进程组号、当前工作目录……)并未改变。exec 只是用另一个新程序替换
了当前进程的正文、数据、堆和栈段(进程替换)。

补充说明:
	l(list)			参数地址列表,以空指针结尾
	v(vector)		存有各参数地址的指针数组的地址
	p(path)			按 PATH 环境变量指定的目录搜索可执行文件
	e(environment)	存有环境变量字符串地址的指针数组的地址
	exec 函数族与一般的函数不同,exec 函数族中的函数执行成功后不会返回,而
且,exec 函数族下面的代码执行不到。只有调用失败了,它们才会返回 -1,失败后
从原程序的调用点接着往下执行

2.进程间通信概念

	进程是一个独立的资源分配单元,不同进程(这里所说的进程通常指的是用户进程)
之间的资源是独立的,没有关联,不能在一个进程中直接访问另一个进程的资源。
	但是,进程不是孤立的,不同的进程需要进行信息的交互和状态的传递等,因此需要
进程间通信
进程间通信的目的:
	1.数据传输:一个进程需要将它的数据发送给另一个进程。
	2.通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某
	种事件(如进程终止时要通知父进程)。		
	3.资源共享:多个进程之间共享同样的资源。为了做到这一点,需要内核提供互斥和
	同步机制。		
	4.进程控制:有些进程希望完全控制另一个进程的执行(如 Debug 进程),此时控
	制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
	
2.1 无名管道
	2.1.1 概述
	管道也叫无名管道,它是是 UNIX 系统 IPC(进程间通信) 的最古老形式,所有的 
	UNIX 系统都支持这种通信机制。

	管道有如下特点:		
	1) 半双工,数据在同一时刻只能在一个方向上流动。		
	2) 数据只能从管道的一端写入,从另一端读出。		
	3) 写入管道中的数据遵循先入先出的规则。		
	4) 管道所传送的数据是无格式的,这要求管道的读出方与写入方必须事先约定好数据的格式,如多少字节算一个消息等。		
	5) 管道不是普通的文件,不属于某个文件系统,其只存在于内存中。		
	6) 管道在内存中对应一个缓冲区。不同的系统其大小不一定相同。		
	7) 从管道读数据是一次性操作,数据一旦被读走,它就从管道中被抛弃,释放空间以便写更多的数据。		
	8) 管道没有名字,只能在具有公共祖先的进程(父进程与子进程,或者两个兄弟进程,具有亲缘关系)之间使用。

	管道是一种特殊类型的文件,在应用层体现为两个打开的文件描述符。

	2.1.2 pipe函数
	#include <unistd.h>
	​
	int pipe(int pipefd[2]);
	功能:创建无名管道。
	​
	参数:
	    pipefd : 为 int 型数组的首地址,其存放了管道的文件描述符 pipefd[0]、pipefd[1]。
	    
	    当一个管道建立时,它会创建两个文件描述符 fd[0] 和 fd[1]。其中 fd[0] 固定用于读管道,而 fd[1] 固定用于写管道。一般文件 I/O的函数都可以用来操作管道(lseek() 除外)。
	​
	返回值:
	    成功:0
	    失败:-1

	**可以使用ulimit -a 命令来查看当前系统中创建管道文件所对应的内核缓冲区大小。**

	2.1.3 查看管道缓冲区函数
	#include <unistd.h>
	​
	long fpathconf(int fd, int name);
	功能:该函数可以通过name参数查看不同的属性值
	参数:
	    fd:文件描述符
	    name:
	        _PC_PIPE_BUF,查看管道缓冲区大小
	        _PC_NAME_MAX,文件名字字节数的上限
	返回值:
	    成功:根据name返回的值的意义也不同。
	    失败: -1
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
  
int main()
{
        int fds[2];
        int ret = -1;

        //创建一个无名管道
        ret = pipe(fds);
        if(-1 == ret)
        {
                perror("pipe");
                return 1;
        }

        //fds[0]用于读 fds[1]用于写
        printf("fds[0]:%d fds[1]:%d\n",fds[0],fds[1]);

        //查看管道的缓冲区大小
        printf("pipe read size:%ld\n",fpathconf(fds[0],_PC_PIPE_BUF));
        printf("pipe write size:%ld\n",fpathconf(fds[1],_PC_PIPE_BUF));

        //关闭文件描述符
        close(fds[0]);
        close(fds[1]);


        return 0;
}

	2.1.4 管道的读写特点
	读管道:
		管道中有数据,read返回实际读到的字节数。
		管道中无数据:
		管道写端被全部关闭,read返回0 (相当于读到文件结尾)
		写端没有全部被关闭,read阻塞等待(不久的将来可能有数据递达,此时会让出cpu)

	写管道:
		管道读端全部被关闭, 进程异常终止(也可使用捕捉SIGPIPE信号,使进程终止)
		管道读端没有全部关闭:
		管道已满,write阻塞。
		管道未满,write将数据写入,并返回实际写入的字节数。
		

	2.1.5 设置为非阻塞的方法
	设置方法:
		//获取原来的flags
		int flags = fcntl(fd[0], F_GETFL);
		// 设置新的flags
		flag |= O_NONBLOCK;
		// flags = flags | O_NONBLOCK;
		fcntl(fd[0], F_SETFL, flags);
		
	结论: 如果写端没有关闭,读端设置为非阻塞, 如果没有数据,直接返回-1。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>

//父子进程使用无名管道进行通信
//父进程写管道 子进程读管道
//
//四种情况
//      第一种:
//      如果写端没有关闭的话,管道中没有数据,这个时候管道进程去读该管道会阻塞
//      第二种:
//      管道所有写端关闭,读进程会读管道的内容,管道的内容全部读取后最后返回0
//
//      第三种:
//      所有读端没有关闭,如果管道被写满了,写管道进程会被阻塞
//
//      第四种:
//      所有的读端被关闭,写管道会收到信号然后退出
int main()
{
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>

//父子进程使用无名管道进行通信
//父进程写管道 子进i
int main()
{
        int ret = -1;
        int fds[2];
        char buffer[64];
        pid_t pid = -1;

        //1.创建无名管道
        ret = pipe(fds);
        if(-1 == ret)
        {
                perror("pipe");
                return 0;
        }

        //2.创建子进程
        pid = fork();
        if(-1 == pid)
        {
                perror("fork");
                return 1;
        }

        //进入子进程
        if(0 == pid)
        {
                //关闭写通道
                close(fds[1]);
                memset(buffer,0,64);
                printf("子进程读管道内容。。\n");

                //设置管道为非阻塞
                ret = fcntl(fds[0],F_GETFL);

                ret |= O_NONBLOCK;

                fcntl(fds[0],F_SETFL,ret);

                //读管道内容
                ret = read(fds[0],buffer,64);
                if(ret < 0)
                {
                        perror("rear");
                        exit(-1);
                }

                printf("son process :%s\n",buffer);
                //关闭读通道
                close(fds[0]);
                exit(0);
        }

        //进入父进程
        close(fds[0]);
        sleep(1);

        memset(buffer,0,64);
        write(fds[1],"ABCDEFG",7);
        if(ret < 0)
        {
                perror("write");
                return 1;
        }

        printf("Father process:%s\n",buffer);

        close(fds[1]);

        return 0;
}

2.2 有名管道
	2.2.1 概述
		管道,由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了
	命名管道(FIFO),也叫有名管道、FIFO文件。

		命名管道(FIFO)不同于无名管道之处在于它提供了一个路径名与之关联,
	以 FIFO 的文件形式存在于文件系统中,这样,即使与 FIFO 的创建进程不存在
	亲缘关系的进程,只要可以访问该路径,就能够彼此通过 FIFO 相互通信,因此
	,通过 FIFO 不相关的进程也能交换数据。

		命名管道(FIFO)和无名管道(pipe)有一些特点是相同的,不一样的地方在于:
		1) FIFO 在文件系统中作为一个特殊的文件而存在,但 FIFO 中的内容却存放在
		内存中。
		2) 当使用 FIFO 的进程退出后,FIFO 文件将继续保存在文件系统中以便以后使用。
		3) FIFO 有名字,不相关的进程可以通过打开命名管道进行通信。

	2.2.2 有名管道创建
		可以通过命令来创建:mkfifo fifo

		通过函数来创建:
		#include <sys/types.h>
		#include <sys/stat.h>
		​
		int mkfifo(const char *pathname, mode_t mode);
		功能:
		    命名管道的创建。
		参数:
		    pathname : 普通的路径名,也就是创建后 FIFO 的名字。
		    mode : 文件的权限,与打开普通文件的 open() 函数中的 mode 参数相同。(0666)
		返回值:
		    成功:0   状态码
		    失败:如果文件已经存在,则会出错且返回 -1。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<fcntl.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>

//通过mkfifo创建一个有名管道文件
int main()
{
        int ret = -1;

        //创建一个有名管道
        ret = mkfifo("fifo",0644);
        if(-1 == ret)
        {
                perror("mkfifo");
                return 1;
        }

        printf("创建管道成功。。\n");


        return 0;
}

	2.2.3 有名管道读写操作
		一旦使用mkfifo创建了一个FIFO,就可以使用open打开它,常见的文件I/O函数
	都可用于fifo。如:close、read、write、unlink等。
		FIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是
	从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等
	文件定位操作。
		读操作read.c
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>

//读有名管道
int main()
{
        int fd = -1;
        int ret = -1;

        char buf[128];

        //1.以只读的方式打开一个管道
        fd = open("fifo",O_RDONLY);
        if(-1 == fd)
        {
                perror("open");
                return 1;
        }
        printf("以只读的方式打开一个管道。。\n");

        //2.读管道
        while(1)
        {
                memset(buf,0,128);
                ret = read(fd,buf,128);
                if(ret <= 0)
                {
                        perror("read");
                        break;
                }

                printf("buf:%s\n",buf);
        }

        //3.关闭文件
        close(fd);


        return 0;
}

		写操作write.c
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>

//写有名管道
int main()
{
        int i = 0;
        int fd = -1;
        int ret = -1;

        char buf[128];

        //1.以只写的方式打开一个管道
        fd = open("fifo",O_WRONLY);
        if(-1 == fd)
        {
                perror("open");
                return 1;
        }
        printf("以只写的方式打开一个管道。。\n");

        //2.写管道
        while(1)
        {
                memset(buf,0,128);
                sprintf(buf,"hello world %d",i++);
                ret = write(fd,buf,strlen(buf));
                if(ret <= 0)
                {
                        perror("write");
                        break;
                }

                printf("write fifo:%d\n",ret);
                sleep(1);
        }

        //3.关闭文件
        close(fd);


        return 0;
}

	2.2.4 有名管道注意事项
		1) 一个为只读而打开一个管道的进程会阻塞直到另外一个进程为只写打开该管道
		2)一个为只写而打开一个管道的进程会阻塞直到另外一个进程为只读打开该管道

	读管道:		
	1. 管道中有数据,read返回实际读到的字节数。		
	2. 管道中无数据:		
	3.管道写端被全部关闭,read返回0 (相当于读到文件结尾)		
	4.写端没有全部被关闭,read阻塞等待
	
	写管道:		
	1.管道读端全部被关闭, 进程异常终止(也可使用捕捉SIGPIPE信号,使进程终止)		
	2.管道读端没有全部关闭:		
	3. 管道已满,write阻塞。		
	4. 管道未满,write将数据写入,并返回实际写入的字节数。
	
	2.2.5 案例:不同的进程之间进行通讯
	用双通道进行通讯
	进程A:先读后写
		  以只读的方式打开管道1 以只写的方式打开管道2
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>

//先读后写
//以只读的方式打开管道1 以只写的方式打开管道2
int main()
{
        int fdr = -1;
        int fdw = -1;
        int ret = -1;

        char buffer[128];

        //1.以只读的方式打开管道1
        fdr = open("fifo1",O_RDONLY);
        if(-1 == fdr)
        {
                perror("open");
                return 1;
        }
        printf("以只读的方式打开管道1..\n");

        //以只写的方式打开管道2
        fdw = open("fifo2",O_WRONLY);
        if(-1 == fdw)
        {
                perror("open");
                return 1;
        }
        printf("以只写的方式打开管道2..\n");

        //循环读写
        while(1)
        {
                //读管道1
                memset(buffer,0,128);
                ret = read(fdr,buffer,128);
                if(ret <= 0)
                {
                        perror("read");
                        break;
                }
                printf("read:%d %s\n",ret,buffer);

                //写管道2
                memset(buffer,0,128);
                fgets(buffer,128,stdin);
                //去掉最后一个换行符
                if(0 == buffer[strlen(buffer)-1])
                        buffer[strlen(buffer)-1] = 0;


                ret = write(fdw,buffer,strlen(buffer));
                if(ret <= 0)
                {
                        perror("write");
                        break;
                }
                printf("write:%d\n",ret);
        }


        //关闭文件描述符
        close(fdr);
        close(fdw);



        return 0;
}

	进程B:先写后读
		  以只读的方式打开管道2 以只写的方式打开管道1
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>

//先写后读
//以只读的方式打开管道2 以只写的方式打开管道1
int main()
{
        int fdr = -1;
        int fdw = -1;
        int ret = -1;

        char buffer[128];


        //以只写的方式打开管道1
        fdw = open("fifo1",O_WRONLY);
        if(-1 == fdw)
        {
                perror("open");
                return 1;
        }
        printf("以只写的方式打开管道1..\n");


        //1.以只读的方式打开管道2
        fdr = open("fifo2",O_RDONLY);
        if(-1 == fdr)
        {
                perror("open");
                return 1;
        }
        printf("以只读的方式打开管道2..\n");

        //循环读写
        while(1)
        {

                //写管道1
                memset(buffer,0,128);
                fgets(buffer,128,stdin);
                //去掉最后一个换行符
                if('\n' == buffer[strlen(buffer)-1])
                        buffer[strlen(buffer)-1] = '\0';

                ret = write(fdw,buffer,strlen(buffer));
                if(ret <= 0)
                {
                        perror("write");
                        break;
                }
                printf("write:%d\n",ret);

                //读管道2
                memset(buffer,0,128);
                ret = read(fdr,buffer,128);
                if(ret <= 0)
                {
                        perror("read");
                        break;
                }
                printf("read:%d %s\n",ret,buffer);

        }


        //关闭文件描述符
        close(fdr);
        close(fdw);



        return 0;
}

3. 共享存储映射

3.1 概述
	存储映射I/O (Memory-mapped I/O) 使一个磁盘文件与存储空间中的一个缓冲区
相映射。
	于是当从缓冲区中取数据,就相当于读文件中的相应字节。于此类似,将数据存入
缓冲区,则相应的字节就自动写入文件。这样,就可在不适用read和write函数的情况
下,使用地址(指针)完成I/O操作。
	共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式, 因为进程可以
直接读写内存,而不需要任何数据的拷贝。

3.2 存储映射函数
(1) mmap函数

	#include <sys/mman.h>
	​
	void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
	功能:
	    一个文件或者其它对象映射进内存
	参数:
	    addr :  指定映射的起始地址, 通常设为NULL, 由系统指定
	    length:映射到内存的文件长度
	    prot:  映射区的保护方式, 最常用的 :
	        a) 读:PROT_READ
	        b) 写:PROT_WRITE
	        c) 读写:PROT_READ | PROT_WRITE
	    flags:  映射区的特性, 可以是
	        a) MAP_SHARED : 写入映射区的数据会复制回文件, 且允许其他映射该文件的进程共享。
	        b) MAP_PRIVATE : 对映射区的写入操作会产生一个映射区的复制(copy - on - write), 对此区域所做的修改不会写回原文件。
	    fd:由open返回的文件描述符, 代表要映射的文件。
	    offset:以文件开始处的偏移量, 必须是4k的整数倍, 通常为0, 表示从文件头开始映射
	返回值:
	    成功:返回创建的映射区首地址
	    失败:MAP_FAILED宏
	​
	关于mmap函数的使用总结:		
	1) 第一个参数写成NULL		
	2) 第二个参数要映射的文件大小 > 0		
	3) 第三个参数:PROT_READ 、PROT_WRITE		
	4) 第四个参数:MAP_SHARED 或者 MAP_PRIVATE		
	5) 第五个参数:打开的文件对应的文件描述符		
	6) 第六个参数:4k的整数倍,通常为0


	(2) munmap函数
	
	#include <sys/mman.h>		​
	int munmap(void *addr, size_t length);
	功能:
	    释放内存映射区
	参数:
	    addr:使用mmap函数创建的映射区的首地址
	    length:映射区的大小
	返回值:
	    成功:0
	    失败:-1
#include<sys/stat.h>
#include<sys/mman.h>
#include<unistd.h>
#include<fcntl.h>

//存储映射
int main()
{
        int fd = -1;
        int ret = -1;
        void* addr = NULL;

        //1.以读写的方式打开一个文件
        fd = open("11txt",O_RDWR);
        if(-1 == fd)
        {
                perror("open");
                return 1;
        }
        //2.将文件映射到内存
        addr = mmap(NULL,1024,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
        if(addr == MAP_FAILED)
        {
                perror("mmap");
                return 1;
        }

        printf("文件映射完成..\n");

        //3.关闭文件
        close(fd);

        //4.写文件
        memcpy(addr,"1234567890",10);

        //5.断开存储映射
        munmap(addr,1024);


        return 0;
}

3.3 注意事项
1) 创建映射区的过程中,隐含着一次对映射文件的读操作。
2) 当MAP_SHARED时,要求:映射区的权限应 <=文件打开的权限(出于对映射区的保
护)。而MAP_PRIVATE则无所谓,因为mmap中的权限是对内存的限制。	
3) 映射区的释放与文件关闭无关。只要映射建立成功,文件可以立即关闭。	
4) 特别注意,当映射文件大小为0时,不能创建映射区。所以,用于映射的文件必须要
有实际大小。mmap使用时常常会出现总线错误,通常是由于共享文件存储空间大小引
起的。	
5) munmap传入的地址一定是mmap的返回地址。坚决杜绝指针++操作。	
6) 如果文件偏移量必须为4K的整数倍。	
7) mmap创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。

3.4 共享映射的方式操作文件
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/mman.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/wait.h>

//存储映射
int main()
{
        int fd = -1;
        int ret = -1;
        void* addr = NULL;
        pid_t pid = -1;

        //1.以读写的方式打开一个文件
        fd = open("11txt",O_RDWR);
        if(-1 == fd)
        {
                perror("open");
                return 1;
        }
        //2.将文件映射到内存
        addr = mmap(NULL,1024,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
        if(addr == MAP_FAILED)
        {
                perror("mmap");
                return 1;
        }

        printf("文件映射完成..\n");

        //3.关闭文件
        close(fd);

        //4.创建一个子进程
        pid = fork();
        if(-1 == pid)
        {
                perror("fork");
                return 1;
        }

        //子进程
        if(0 == pid)
        {
                //写内容
                memcpy(addr,"ABCDEFGHIJ",10);
        }
        else
        {
                //等待子进程结束
                wait(NULL);

                //f父进程
                printf("addr:%s\n",(char *)addr);
        }

        //5.断开存储映射
        munmap(addr,1024);


        return 0;
}

3.5 共享映射实现父子进程通信
 int fd = open("xxx.txt", O_RDWR);// 打开一个文件
    int len = lseek(fd, 0, SEEK_END);//获取文件大小// 创建内存映射区
    void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (ptr == MAP_FAILED)
    {
        perror("mmap error");
        exit(1);
    }
    close(fd); //关闭文件// 创建子进程
    pid_t pid = fork();
    if (pid == 0) //子进程
    {
        sleep(1); //演示,保证父进程先执行// 读数据
        printf("%s\n", (char*)ptr);
    }
    else if (pid > 0) //父进程
    {
        // 写数据
        strcpy((char*)ptr, "i am u father!!");// 回收子进程资源
        wait(NULL);
    }// 释放内存映射区
    int ret = munmap(ptr, len);
    if (ret == -1)
    {
        perror("munmap error");
        exit(1);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux系统上配置CLion,首先需要安装相应的编译工具包。在Ubuntu中,可以使用以下命令进行安装: ``` sudo apt-get install build-essential ``` 这个命令将安装gcc(C编译器)、g++(C++编译器)和make(连接工具)。 接下来,如果在首次安装CLion时遇到报错,如"CMake Error: CMake was unable to find a build program corresponding to 'Unix Makefiles'.",则需要设置CMake的构建工具。可以通过以下步骤来解决这个问题: 1. 打开CLion,进入File菜单,选择Settings。 2. 在弹出的窗口中,找到Build, Execution, Deployment选项,然后选择CMake。 3. 在CMake的设置中,选择"Unix Makefiles"作为构建工具。 4. 保存设置并重新加载项目。 此外,如果在CLion中使用Cppcheck进行代码静态分析,需要配置Cppcheck的路径。在Linux系统上,可以通过以下步骤来进行配置: 1. 在CLion中,进入File菜单,选择Settings。 2. 在弹出的窗口中,找到Tools选项,然后选择Cppcheck。 3. 在Cppcheck设置中,将cppcheck的路径设置为正确的路径,例如/usr/bin/cppcheck。 4. 保存设置。 通过以上步骤,您可以在Linux系统上成功配置CLion,并使用gcc、g++、make和Cppcheck进行编程和代码分析。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [linux clion 编译环境配置](https://blog.csdn.net/whatday/article/details/131155382)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [clion-cppcheck:CLion的cppcheck插件](https://download.csdn.net/download/weixin_42131414/19111962)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值