Linux进程与进程间通信学习/复习

Linux进程与进程间通信

1.进程的概念

​ 程序:二进制文件,占用的磁盘空间
​ 进程:启动的程序
​ 所有数据都在内存中
​ 需要占用更多的系统资源
​ cpu,物理内存

1.1并行和并发

​ 并发:在操作系统中,是指一个时间段中有几个程序都处于已启动到运行完毕之间,且这几个程序都是在同一个处理机上进行。(并发不是真正意义上同时进行,而是CPU分时执行各个程序)

​ 并行:当系统有一个以上CPU时,一个CPU执行一个进程时,其他CPU也能执行其他进程,两个进程互不抢占CPU资源,可以同时进行,这种方式就成为并行。(决定并行的因素不是CPU数量,而是CPU的核心数量,比如一个CPU多个核也可以并行)

​ 并行核并发的区别:1.并发是在同一时间段上内同时发生,并行是在同一时间点上同时进行。
​ 2.并发的多个任务是互相抢占资源的。并行多个任务之间是不互相抢占资源的。
​ 3.只有在多CPU或一个CPU多核的情况中,才会发生并行。

1.2进程控制块(PCB)

​ 每个进程在内核中都有一个进程控制块(PCB)来维护进程相关信息,linux内核的进程控制块是task_struct结构体。

//struct task_struct结构体内部成员有很多,重点掌握以下部分即可。
->进程id
->进程的状态
->进程切换时需要保护核恢复一些CPU寄存器
->描述虚拟地址空间的信息
->描述控制终端的信息
->当前工作目录
->umask掩码
->文件描述符,包含跟多指向file结构体的指针
->和信号相关的信息
->用户id和组id,stat
->会话和进程组
->进程可以适用的资源上限
1.3进程的状态

​ 进程基本的状态有5种,初始态,就绪态,运行态,挂起态和终止态。

​ 初始态为进程准备阶段,常常与就绪态结合来看。

就绪态:有执行资格没有执行权(等待CPU);
运行态:有执行资格有执行权(获得了CPU);
挂起态:没有执行资格没有执行权,条件满足后能够变成就绪态。

2.进程控制

1.fork函数

​ 一个进程,包括代码、数据和分配给进程的资源。

​ fork()函数通过系统调用 创建一个与原来进程几乎完全相同的进程,也就是两个进程可以作完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。

​ 一个进程调用fork函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的进程中,只有少数值与原来的进程不同。

包含的头文件
	#include <sys/types.h>
	#include <unistd.h>
函数原型
	pid_t fork(void);
两个返回值:
    =0:当前进程为子进程
    >0:当前进程为父进程
    -1:出错。

基本使用示例:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
    pid_t pid;
    pid = fork();
    if(pid>0)//father process
        printf("this is father process %d\n",getpid());
    else if(pid == 0)
        printf("this is chiled process %d,ppid is %d\n",getpid(),getppid());
	for(int i=0;i<4;i++){
        printf("%d\n",i);
    }
    return 0;
}
/*
this is father process 47399
0
1
2
3
this is chiled process 47400,ppid is 47399
0
1
2
3
*/

fork()函数的补充

用户数据一样,进程ID不一样。

父子进程的执行顺序不一定

父子进程的应用:很多情况下有服务器和客户端,为了让客户端互不影响,服务器需要创建子进程来管理一个客户端。

2.ps和kill命令
ps	查看进程信息
参数:
a:现实现行终端机下所有程序,包括其他用户的程序
u:以用户为主的格式来显示程序状态
x:显示所有程序,不以终端机来区分

用的较多的命令:ps auxps ajx

kill 向指定的进程发送信号

结束一个进程:kill -9 pid号

3.父子进程间的数据共享

fork之后两个地址空间区数据完全相同

后续各自进行了不同的操作:

父进程:num--
子进程:num++

各个进程的地址空间中的数据是完全独立的。

对于同一个变量,读时共享,而写的时候分别在物理地址上拷贝一份变量进行单独读写。

那么父子进程之间可不可通过全局变量通信?答案是不能,因为两个进程内存不共享。

4.exec函数族

让父子进程来执行不相干的操作;

能够替换进程地址空间的代码.text段;

执行另外的程序,不需要创建额外的地址空间;

当前程序中调用另外一个应用程序。

//1.指定执行目录下的程序
int execl(const char *path, const char *arg, ...
                       /* (char  *) NULL */);
/*
path:要执行程序的路径,最好是绝对路径
arg:要执行程序需要的参数
第一位arg:占位
后面的arg:命令的参数
参数写完之后:null
一般执行自己写的程序
*/
//2.执行PATH环境变量能够搜索到的程序
int execlp(const char *path, const char *arg, ...
                       /* (char  *) NULL */);


返回值:

如果函数运行成功不返回

#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
	pid_t pid;
	pid = fork();
	//father process
	if(pid>0){
		printf("this is father process %d\n",getpid());
	}
	//chiled process
	else if(pid == 0){
		execl("/bin/ls","ls","-l",NULL);
		perror("execl");
        printf("this is chiled process %d\n",getpid());
	}
    for(int j=0;j<3;j++){
        printf("--j=%d\n",j);
    }
    return 0;
}
/*out:
this is father process 47957
i = 600
-- j = 0
-- j = 1
-- j = 2
total 16
-rwxrwxr-x 1 book book 8544 Jul 18 05:12 a.out
-rw-rw-r-- 1 book book  563 Jul 18 05:12 process.c
*/

​ 从这段程序不难看出,子进程在执行了execl函数后,后面的代码段都没有再运行,而是执行了execl指定的程序。

5.孤儿进程和僵尸进程

**孤儿进程:**一个父进程退出,而他的一个或多个子进程还在运行,那么这些子进程就成为了孤儿线程。孤儿线程将被init进程(pid号为1)所收养,并由init进程时它们完成状态收集工作。

为什么会被init收养,为了释放子进程的占用的系统资源:

​ 进程结束之后,能够释放用户区空间
​ 释放不了PCB,必须由父进程释放

**僵尸进程:**当进程退出父进程(使用wait()系统调用)没有读取到子进程退出的返回代码时,就会产生僵尸进程。僵尸进程会在以终止状态保持在进程表中,并且会一直等待父进程读取退出状态代码。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
    pid_t pid;
    pid = fork();
    if(pid == 0){
        //chiled process
        printf("chiled process pid %d ppid %d\n",getpid(),getppid());
    }
    else if(pid>0){
        //father process
        while(1){
            sleep(1);
            printf("father process pid %d ppid %d\n",getpid(),getppid());
        }
    }
    return 0;
}
//这里父进程没有对子进程进行资源回收,子进程就会变成一个僵尸进程。

僵尸进程是不能被 kill -9 pid杀死的,因为已经是一个已经死掉了的进程,只是资源没有被回收。

通过ps aux 也是可以找到,如果进程末尾有defunct那么就是僵尸进程。

6.进程回收

wait阻塞函数

1.阻塞并等待子进程退出
2.回收子进程残留资源
3.获取子进程结束状态(退出原因)

pid_t wait(int *wstatus);
返回值:
	-1:回收失败,已经没有子进程了
	>0:回收子进程对应的pid
参数:
	status判断子进程如何退出状态
	1.WIFEXITED:为非0,进程正常结束
		WEXITSTATUS(status);
		如上宏为真,使用此宏,获取进程退出状态的参数,也就是return的值
	2.WIFSIGNALED:为非0,进程一场退出
		WTERMSIG(status);
		如上宏为真,取得使进程种植的那个信号的编号

示例:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
    pid_t pid;
    pid = fork();
    if(pid == 0){//chiled process
        while(1)
        {
            sleep(1);
            printf("chiled pid is %d,ppid is %d\n",getpid(),getppid());
        }
        printf("chiled process pid %d,ppid is %d\n",getpid(),getppid());
    }
    else if(pid > 0){//father process
        int status;
        pid_t wpid;
        wpid = wait(&status);
        printf("wpid is %d\n",wpid);
        if(WIFEXITED(status)){
            printf("exit value is %d\n",WEXITSTATUS(status));
        }
        if(WIFSIGNALED(status)){
            printf("exit by signal is %d\n",WTERMSIG(status));
        }
        printf("father process pid %d,ppid %d\n",getpid(),getppid());
    }
    return 0;
}

waitpid函数

函数作用同wait函数

pid_t waitpid(pid_t pid,int *status,int options);
参数:
    1.pid:指定回收某个子进程
        pid == -1 回收所有子进程
        	while((wpid=waitpid(-1,status,0))!=-1)
        pid > 0 回收某个pid相等的子进程
        pid == 0 回收当前进程组的任一子进程
        pid < 0 子进程的PID取反
   	2.status:子进程的退出状态,用法同wait函数
    3.options:设置为WNOHANG,函数非阻塞,设置为0函数阻塞
返回值:
    >0:返回清理掉的子进程ID
    -1:回收失败,无子进程
    如果为非阻塞
        =0:参数3为WNOHANG,且子进程正在运行

wpid = waitpid(-1,&status,WNOHANG)

7.vfork创建进程

vfork也可以创建进程,与fork的区别:

1.vfork可以直接使用父进程存储空间,不拷贝
2.vfork可以保证子进程先运行,当子进程调用exit之后,父进程才执行

8.进程退出

1.正常退出

​ 1.main函数调用return
​ 2.进程调用exit(),标准C库
​ 3.进程调用 _exit()或者 _Exit() 属于系统调用

补充:
4.进程最后一个线程返回
5.最后一个线程调用pthread_exit

2.异常退出

​ 1.调用abort函数
​ 2.当进程收到某些信号时,比如ctrl+C
​ 3.最后一个线程时取消(cancellation) 请求做出响应

​ 不管进程如何终止,最后都会执行内核中的同一段代码,这段代码和响应进程关闭所有打开描述符,释放它所使用的存储器等。

3.进程通信

1.IPC

​ IPC是什么?Inter Process Communication

进程间通信常用几种方法:
1、管道通信:有名管道,无名管道
2、信号 - 系统开销小
3、消息队列 - 内核的链表
4、信号量 - 计数器
5、共享内存
6、内核映射
7、套接字 - 稳定

2.无名管道

本质:内核缓冲区、伪文件-不占用磁盘空间

特点:

1.读端(read),写端(write),对应两个文件描述。数据写端流入,读端流出。
2.操作管道的进程被销毁后,管道自动释放。
3.管道默认是阻塞读写的。

管道的原理

内部实现方式:队列
环形队列
特点:先进先出

缓冲区大小:
默认4K
大小根据实际情况进行适当调整

管道的局限性

队列:
数据只可以读取一次,不可以重复读取

半双工:
单工:遥控器
半双工:对讲机,数据传输方向单向
全双工:电话

匿名管道:
适用于有血缘关系的进程

创建匿名管道
int pipe(int fd[2]);
fd-传出参数:
    fd[0]-读端
    fd[1]-写端
返回值:
    0:成功
    -1:创建失败 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
    int ret;
    int fd[2];
    ret = pipe(fd);
    if(ret == -1){
        printf("create pipe failed\n");
        exit(1);
    }
    printf("pipe[0] is %d\n",fd[0]);
    printf("pipe[1] is %d\n",fd[1]);
    close(fd[0]);
    close(fd[1]);
    return 0;
}
父子进程使用管道通信

实现 ps aux | grep “bash”

要使用数据重定向:dup2

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
    int fd[2];
    int ret = pipe(fd);
    if(ret == -1){
        printf("pipe create failed!\n");
        exit(1);
    }
    pid_t pid = fork();
    if(pid == -1){
        printf("fork failed\n");
        exit(1);
    }
    //ps aux
    //father process
    if(pid > 0)
    {
        close(fd[0]);//关闭读端
        dup2(fd[1],STDOUT_FILENO);
        execlp("ps","ps","aux",NULL);
        perror("execlp");
        exit(1);
    }
    //bash
    //child process
    if(pid == 0)
    {
        close(fd[1]);//关闭写端
        dup2(fd[0],STDIN_FILENO);
        execlp("grep","grep","bash",NULL);
    }
    close(fd[0]);
    close(fd[1]);
    return 0;
}
管道的读写行为

读操作:
->有数据
read(fd[1])正常读,返回读出的字节数
->无数据
写端被全部关闭,read返回0,相当于读文件到了尾部
没有全部关闭
read阻塞

写操作:
->读端全部关闭
管道破裂,进程被终止
内核给当前进程发送信号SIGPIPE-13,默认处理动作
->读端没全部关闭
缓冲区写满了
write阻塞
缓冲区没满
write继续写,知道写满,阻塞。

管道缓冲区的大小

命令:ulimit -a 可以看到pipe size

函数:long fpathconf(int fd, int name);

3.有名管道
int mkfifo(const char \*filename,mode_t mode);

功能:创建管道文件
参数:管道文件文件名,权限,创建的文件权限仍然和umask有关
返回值:创建成功返回0,创建失败返回-1。

1.特点
  • 有名管道
  • 在磁盘上有这样一个文件 ls -l ->p
  • 也是一个伪文件,磁盘大小永久为0
  • 数据存在内核中有一个对应的缓冲区
  • 半双工通信方式
2.使用场景

​ 没有血缘关系的进程间通信

3.创建方式

​ 命令:mkfifo管道名
​ 函数:mkfifo

4.fifo文件可以使用IO函数操作

​ open/close、read/write,不能执行lseek操作

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
    int ret;
    ret = mkfifo("/home/book/abc/moumou/pid",0777);
    if(ret == -1){
        return -1;
    }
    printf("create fifo succeed\n");
    return 0;
}
//执行这段代码后就会生成一个管道类型的文件

下面就写两个程序,用有名管道实现通信:

//readfifo.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
	int ret;
	int fd;
	char readBuf[50] = {0};
	int nread=0;
	ret = mkfifo("./myfifo",0755);//创建有名管道
	if(ret == -1){
		return -1;
	}
	printf("mkfifo succeed\n");

	fd = open("./myfifo",O_RDONLY);//打开有名管道文件,得到文件描述符
	if(fd < 0){
		return -2;
	}
	printf("open file succeed\n");

	//read fifo
	nread = read(fd,readBuf,50);
	printf("read %d byte from fifo %s",nread,readBuf);
	close(fd);
	return 0;
}
//writefifo.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
	int fd;
	char *str = "hello world";

	fd = open("./myfifo",O_WRONLY);
	if(fd < 0){
		return -1;
	}
	printf("open fifo succeed\n");

	write(fd,str,strlen(str));

	close(fd);
	return 0;
}
4.消息队列

消息队列,是消息的链表,存放在内核中,一个消息队列由一个标识符(队列ID)来标识。

特点
  • 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级
  • 消息队列独立于发送和接收进程,进程终止时,消息队列及其内容仍存在
  • 消息队列可以实现消息的随机查询,消息不一定要先进先出的次序读取,也可以按消息的类型读取。
相关函数
1.#include <sys/msg.h>
    
2.int msgget(key_t key, int msgflg);
//创建或打开消息队列,成功返回队列ID,失败返回-1
参数:
	msgflg是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。msgflg可以与IPC_CREAT做或操作,表示当key命名的消息队列不存在时创建一个消息队列。
返回值:成功返回队列ID,失败返回-1.
3.int msgsnd(int msgid, const void *msgp, size_t msgsz, int msgflg);
//发送消息,成功返回消息数据的长度,失败返回-1
参数:
    msgid:消息队列的ID
    msgp:指向消息的指针,常用结构体msgbuf如下:
    	struct msgbuf{
            long mtype;//消息类型
            char mtext[N];//消息正文
        }
	size:发送的消息正文的字节数
    flag:
        IPC_NOWAIT 消息没有发送完成函数也会立即返回
        0:直到发送完成函数才返回
返回值:
        成功:0,失败:-1
4.ssize_t msgrcv(int msqid, void *msgp,size_t msgsz, long msgtyp, int msgflg);
//从一个消息队列中获取信息
参数:
    msgid:消息队列的ID
    msgp:要接收消息的缓冲区
    size:要接受的消息的字节数
    msgtype:
    	 0 接收消息队列中的第一个消息
		>0 接收消息队列中第一个类型为msgtype的消息
    	<0 接收消息队列中类型值不大于msgtype的绝对值且类型值又最小的消息。
    flag
    	0:若无消息函数一直阻塞
    	IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG。
返回值:
    成功:接收到的消息长度
    出错:-1

5.int msgctl(int msqid,int cmd,struct msqid_ds *buf);
//控制消息队列,成功返回0,失败返回-1
参数:
    	command 是将要采取的动作,它可以取3个值:
    IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列当前关联值夫阿哥imsgid_ds值。
    IPC_SET:如果进程有足够的权限,就把消息队列的当前关联值设置为msgid_ds结构中给出的值
    IPC_RMID:删除消息队列


6.key_t ftok(char * fname,int id);
//系统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
参数:
    fname就是你指定的文件名
    id是子序号,虽然为int但只有8个bit能使用,即0~255
返回值:
    执行成功,返回一个key_t值,失败返回-1
创建和删除消息队列
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
int main()
{
	int msgid;
	msgid = msgget(IPC_PRIVATE,0755);
	if(megid == -1){
		printf("create message queue failed\n");
		return -1;
	}
	printf("create message queue succeed.msgid = %d\n",msgid);
	system("ipcs -q");

	msgctl(msgid,IPC_RMID,NULL);
	system("ipcs -q");
	return 0;
}

消息队列半双工通信:

//writemsg.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
struct msgbuf
{
	long mtype;
	char mtest[128];
	char ID[4];
};
int main()
{
    struct msgbuf sendbuf;
    int msgid;
    key_t key;
    int readret;
    
    key = ftok(".",1);
    msgid = msgget(key,IPC_CREAT|0755);
    
    sendbuf.mtype = 100;
    while(1)
    {
        memset(sendbuf.mtest,0,128);
        printf("please input to message queue:\n");
        fgets(sendbuf.mtest,128,stdin);
        msgsnd(msgid,(void *)&sendbuf,strlen(sendbuf.mtest),0);
    }
    return 0;
}
//readmsg.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
struct msgbuf
{
	long mtype;
	char mtest[128];
	char ID[4];
};
int main()
{
    struct msgbuf readbuf;
    int msgid;
    key_t key;
    int readret;
    
    key = ftok(".",1);
    msgid = msgget(key,IPC_CREAT|0755);
    
    sendbuf.mtype = 100;
    while(1)
    {
        memset(readbuf.mtest,0,128);
        readret = msgrcv(msgid,(void *)&readbuf,128,100,0);
        printf("message is :%s\n",readbuf.mtest);
        printf("rotal have %d byte\n",readret);
    }
}
消息队列全双工通信
//msg_server.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

struct msgbuf
{
	long mtype;
	char mtest[128];
	char ID[4];
};
int main()
{
	struct msgbuf sendbuf,readbuf;
	int msgid;
	key_t key;
	int readret;
	pid_t pid;
	key = ftok(".",1);//
	msgid = msgget(key,IPC_CREAT|0755);
	if(msgid == -1){
		printf("create message queue failed\n");
		return -1;
	}
	system("ipcs -q");
	printf("create message queue succeed, msgid = %d\n",msgid);
	//init msgbuf
	sendbuf.mtype = 100;
	pid = fork();
	
	if(pid > 0)//father process write 100 msgtype
	{
		while(1)
		{
			memset(sendbuf.mtest,0,128);
			printf("please input to message queue msgtype 100:\n");
			fgets(sendbuf.mtest,128,stdin);
			msgsnd(msgid,(void *)&sendbuf,strlen(sendbuf.mtest),0);
		}
	}
	if(pid == 0)//child process read 200 msgtype
	{
		while(1)
		{
			memset(readbuf.mtest,0,128);
			msgrcv(msgid,(void *)&readbuf,128,200,0);
			printf("receive byte from message queue is :%s\n",readbuf.mtest);
		}
	}
	return 0;
}
//msg_client.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

struct msgbuf
{
	long mtype;
	char mtest[128];
	char ID[4];
};
int main()
{
	struct msgbuf sendbuf,readbuf;
	int msgid;
	key_t key;
	int readret;
	pid_t pid;
	key = ftok(".",1);//
	msgid = msgget(key,IPC_CREAT|0755);
	if(msgid == -1){
		printf("create message queue failed\n");
		return -1;
	}
	system("ipcs -q");
	printf("create message queue succeed, msgid = %d\n",msgid);
	//init msgbuf
	sendbuf.mtype = 200;
	pid = fork();
	
	if(pid > 0)//father process write 200 msgtype
	{
		while(1)
		{
			memset(sendbuf.mtest,0,128);
			printf("please input to message queue msgtype 200:\n");
			fgets(sendbuf.mtest,128,stdin);
			msgsnd(msgid,(void *)&sendbuf,strlen(sendbuf.mtest),0);
		}
	}
	if(pid == 0)//child process read 100 msgtype
	{
		while(1)
		{
			memset(readbuf.mtest,0,128);
			msgrcv(msgid,(void *)&readbuf,128,100,0);
			printf("receive byte from message queue is :%s\n",readbuf.mtest);
		}
	}
	return 0;
}
5.共享内存
概念

​ 共享内存(Shared Memory)就是允许多个进程同时访问同一个内存空间,是在多个进程之间共享和传递数据最高效的方式。操作系统将不同进程之间共享内存安排为同一段物理内存,进程可以将共享内存连接到它们自己的地址空间中,如果某个进程修改了共享内存中的数据,其他的进程读到的数据也将改变。

特点:

  • 共享内存创建之后,一直存在与内核中,知道被删除或系统关闭
  • 共享内存和管道不一样,读取后,内容仍然存在共享内存中。
相关函数
1.int shmget(key_t key, size_t size, int shmflg);
//用来获取或创建共享内存
参数:
	key:IPC_PRIVATE 或 ftok的返回值
	size:共享内存的大小
	shmflg:同open函数的权限位,也可以用8进制表示法
返回值:
	成功:共享内存标识符--ID
	错误:-1
2.void *shmat(int shm_id, const void *shm_addr,int shmflg);
//把共享内存连接映射到当前进程的地址空间
参数:
	shm_id:ID号
	shm_addr:映射到的地址,NULL为系统自动完成的映射
	shmflg:
		SHM_RDONLY共享内存只读
		默认是0,表示共享内存可读写
	返回值:
		成功:映射后的地址
		失败:NULL
3.int shmdt(const void *shmaddr)
//将进程里面的地址映射删除
参数:
	shmid:要操作的共享内存标识符
返回值:
	成功0;失败-1
4.int shmctl(int shm_id, int command, struct shmid_ds *buf);
//删除共享内存对象
参数:
	shmid:要操作的共享内存标识符
	cmd:
		IPC_STAT(获取对象属性)--实现了命令ipcs -m
		IPC_SET(设置对象属性)
		IPC_RMID(删除对象)
	buf:指定IPC_STAT/IPC_SET时用以保存/设置属性
返回值:
	成功0;出错-1。
进程间用共享内存通信

这里只介绍功能实现流程,对于错误提示就先不写进去了

//shm_write
int main()
{
	int shmid;
	int key;
	char *p;

	key = ftok(".",1);					//得到key值
	printf("ftok succeed, key:%x\n",key);
	
	shmid = shmget(key,128,IPC_CREAT|0777);//创建共享内存
	printf("create share memory succeed, shmid=%d\n",shmid);

    //把共享内存映射到当前进程,NULL表示自动映射,0表示可读可写
	p = (char *)shmat(shmid,NULL,0);
    
	printf("please input to share memory:\n");

	//write to share memory
	fgets(p,128,stdin);
	sleep(3);
	shmdt(p);//将进程里面的地址映射删除
	shmctl(shmid,IPC_RMID,0);//删除共享内存对象
	return 0;
}
//shm_read
int main()
{
	int shmid;
	int key;
	char *p;

	key = ftok(".",1);

    //write函数里面已经创建,所以这里最后参数写0就行,表示不用新建共享内存了
	shmid = shmget(key,128,0);

	p = (char *)shmat(shmid,NULL,0);

	printf("share memory data:%s\n",p);

	shmdt(p);
	return 0;
}
6.信号

信号通信,就是内核向用户空间进程发送信号,只有内核才能发信号,用户空间进程不能发送信号。

内核可以发送多少信号呢?

kill -l	//查看所有可以发送的信号
kill -9 pid //例如 终止进程信号给某个进程

信号通信的框架:

  • 信号的发送(发送信号进程):kill raise alarm
  • 信号的接收(接收信号进程):pause() sleep while(1)
  • 信号的处理(接收信号进程):signal
1.信号的发送

kill:

所需头文件:

#include <signal.h>
#include <sys/types.h>
int kill(pid_t pid,int sig);
参数:
	pid:
		正数:要接收信号的进程的进程号
		  0:信号发送到所有的pid进程,在同一进程组中的进程
		 -1:信号发送给所有进程表中的进程(出了进程号最大的进程)
	sig:信号
返回值:
	成功0;错误-1

raise

int raise(int sig);
//发送信号给自己==kill(getpid(),sig)
参数:
	函数传入值:sig:信号
返回值:
	成功0;出错-1

alarm

#include <unistd.h>。
alarm与raise函数比较:
相同点:让内核发送信号给当前进程。
不同点:1.alarm只会发送SIGALARM信号;2.alarm会让内核定时一段时间后发送信号,raise会让内核立刻发送信号。

unsigned int alarm(unsigned int seconds)
参数:
	seconds:秒数
返回值:
	成功:如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0
	出错-1
2.信号的接收

接收信号的进程,要有什么条件:要想使接收的进程能收到信号,这个进程不能结束:

sleep

pause:进程状态为S

int pause(void);
返回值:成功0,出错-1
3.信号的处理

收到信号的进程,应该怎么样处理?处理的方式:

  • 1.进程的默认处理方式(内核为用户进程设置默认处理方式)A:忽略 B:终止进程 C:暂停
  • 2.自己的处理方式:自己处理信号的方式告诉内核,这样你的进程收到这个信号就会采用自己的处理方式
#include <signal.h>
void (*signal(int signum, void (*handler)(int)))(int);
参数:
	signum:指定信号
	handler:
		SIG_IGN:忽略该信号
		SIG_DFL:采用系统默认方式处理信号
		自定义的信号处理函数指针
返回值:
	成功:设置之前的信号处理方式
	出错:-1
signal函数有两个参数,第一个参数是整型变量(信号值),第二个参数是一个函数指针,是我们自己写的处理函数;这个函数的返回值是一个函数指针。
7.信号灯semaphore

信号灯是信号量的一个集合,信号量的API函数是对单个信号进行操作,信号灯则是对一个信号集合操作。

int semget(key_t key,int nsems,int semflg);
//创建一个新的信号量或获取一个已经存在的信号量的键值
所需头文件:
	#include <sys/types.h>
	#include <sys/ipc.h>
	#include <sys/sem.h>
参数:
	key:和信号灯集关联的key值
	nsems:信号灯集中包含的信号灯数目
	semflg:信号灯集的访问权限
返回值:
	成功:信号灯集ID。失败-1
int semctl(int semid, int semnum, int cmd);
//控制信号量,删除信号量或初始化信号量
所需头文件:
	#include <sys/types.h>
	#include <sys/ipc.h>
	#include <sys/sem.h>
参数:
	semid:信号灯集ID
	semnum:要修改的信号灯编号
	cmd:
		GETVAL:获取信号灯的值
		SETVAL:设置信号灯的值
		IPC_RMID:从系统中删除信号灯集合
函数返回值:
	成功0;出错-1

PV操作

int semop(int semid,struct sembuf *sops,size_t nsops);
//用户改变信号量的值

使用示例:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <unistd.h>


#define SEM_READ 0 
#define SEM_WRITE 0
union semun
{
	int val;
};

void Poperation(int index,int semid)
{
	struct sembuf sop;
	sop.sem_num = index;
	sop.sem_op = -1;
	sop.sem_flg = 0;

	semop(semid,&sop,1);
}

void Voperation(int index,int semid)
{
	struct sembuf sop;
	sop.sem_num = index;
	sop.sem_op = 1;
	sop.sem_flg = 0;

	semop(semid,&sop,1);
}


int main()
{
	key_t key;
	key = ftok(".",123);
	pid_t pid;
	int semid;
	int shmid;
	char *shmaddr;

	semid = semget(key,2,IPC_CREAT|0755);//2 signal
	if(semid<0)
	{
		perror("semget");
		return -1;
	}
	shmid = shmget(key,128,IPC_CREAT|0755);
	if(shmid<0)
	{
		perror("shmget");
		return -2;
	}
	//init semaphore
	union semun myun;
	//init semaphore read
	myun.val = 0;
	semctl(semid,SEM_READ,SETVAL,myun);
	//init semaphore write
	myun.val = 1;
	semctl(semid,SEM_WRITE,SETVAL,myun);
	
	pid = fork();
	if(pid == 0)//child
	{
		while(1)
		{
			shmaddr = (char *)shmat(shmid,NULL,0);
			
			Poperation(SEM_READ,semid);
			printf("get share memory is: %s\n",shmaddr);
			Voperation(SEM_WRITE,semid);
		}
	}
	else if(pid > 0)//father
	{
		while(1)
		{
			shmaddr = (char *)shmat(shmid,NULL,0);
			
			Poperation(SEM_WRITE,semid);
			printf("please input to share memory:\n");
			fgets(shmaddr,32,stdin);
			Voperation(SEM_READ,semid);
		}
	}

	return 0;
}
  • 16
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值