进程
从Notion拷过来发现好多例图都没了,网盘发pdf
进程的概念
进程的概念 进程是一个独立的可调度的任务 进程是一个抽象实体。当系统在执行某个程序时,分配和释放的各种资源 进程是一个程序的一次执行的过程 进程和程序的区别 程序是静态的,它是一些保存在磁盘上的指令的有序集合,没有任何执行的概念 进程是一个动态的概念,它是程序执行的过程,包括创建、调度和消亡
进程是程序执行和资源管理的最小单位
总结:程序和进程不是一个概念 程序是死的,就是保存在磁盘上的文件 进程是活的,程序就行起来就是进程,进程是在内存中运行的。
每一个进程在开启或者创建的时候,操作系统都会给其分配4G的虚拟内存,这4G的虚拟内 存分为3G的用户空间(主要用于变量等定义)和1G的内核空间(系统调用),其中用户空 间是当前进程所私有的,内核空间是当前系统中所有进程共有的, 4G的虚拟空间最终会映射到物理内存中,实际使用多少字节的空间,就映射多少
所以我们之前讲解的内存分配,实则是虚拟内存中用户空间的内存划分
进程调度机制
时间片轮转,上下文切换
操作系统将整个将整个内存分为N份,每一份就是一份时间片,每个时间片大约(在Linux上 为5ms-800ms) 上下文切换指的是内核(操作系统的核心)在CPU上对进程或者线程进行切换。上下文切 换过程中的信息被保存在进程控制块(PCBProcess Control Block)中,上下文切换的信 息会一直被保存在CPU的内存中,直到被再次使用。
进程号:主要的进程标识 进程号(Process Identity Number,PID) 父进程号(Parent Process ID,PPID) 进程号是操作系统给运行的进程分配的编号,程序每次运行时理论上编号都不一样
特殊的进程号: 0 内核进程,主要用于管理内核 1 init进程,init进程是所有进程的祖先
ps 查看进程
ps -ajx 详细查看进程
top 动态显示进程
htop 更好查看动态进程
进程的状态
运行状态 系统运行, R
等待状态 等待事件发生 S
停止状态 T ctrl+z 会让进程变为停止态
僵尸状态 进程终止或结束 但在进程表中仍有记录 Z
D: 不可中断的静止,等待
R: 正在执行中,运行态
S: 阻塞状态,睡眠态
T: 暂停执行,停止态 (程序运行时,按下ctrl+z)
Z: 不存在但暂时无法消除,僵尸态
W: 没有足够的内存分页可分配
+: 前台进程
<: 高优先级的进程
N: 低优先级的进程
L: 有内存分页分配并锁在内存中,多线程的
s: 会话组组长
进程函数Fork
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
功能:当前进程创建一个子进程
参数:无
返回值:
成功:
>0 在父进程里面返回子进程的进程号
0 在子进程里面返回0
失败:‐1
例子:
pid_t pid = fork();
if(pid == ‐1)
{
perror("fork error");
return ‐1;
}
else if(pid > 0) //父进程的代码区
{
printf("父进程正在运行\n");
sleep(1);
printf("hahahahahaha\n");
}
else //子进程的代码区
{
printf("子进程正在运行\n");
sleep(1);
printf("hehehehehehehe\n");
}
printf("hello world\n");
获取进程号和父进程号 getpid()/getppid()
获取当前进程的进程号
pid_t getpid(void);
获取当前进程的父进程号
pid_t getppid(void);
其它操作
小tips :exit()和_exit()
void exit(int status);
功能:结束一个进程,是一个库函数
参数:
status:当前进程的退出状态,父进程可以通过wait函数获取
返回值:无
#include <unistd.h>
void _exit(int status);
功能:结束一个进程,是一个系统调用
参数:
status:当前进程的退出状态,父进程可以通过wait函数获取
返回值:无
wait()/waitpid()
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
功能:阻塞等待子进程的退出状态
参数:
wstatus:子进程的退出状态,子进程使用exit之后wait才可以接收到
一般情况传NULL即可,表示不接收子进程的退出状态
返回值:
成功:退出的子进程的进程号
失败:‐1
pid_t waitpid(pid_t pid, int *wstatus, int options);
功能:阻塞等待子进程的退出状态
参数:
pid:指定接收的子进程的状态
<‐1 接收这个参数的绝对值的进程组中所有的子进程的退出
‐1 接收所有的子进程的退出(常用)
0 接收进程组号刚好是当前进程的组中子进程的退出
>0 接收指定进程号为当前参数的子进程的退出
status:子进程的退出状态,子进程使用exit之后wait才可以接收到
一般情况传NULL即可,表示不接收子进程的退出状态
options:标志位
0 阻塞
WNOHANG 非阻塞
返回值:
成功:退出的子进程的进程号
失败:‐1
wait(NULL) <==> waitpid(‐1, NULL, 0)
waitpid(‐1, NULL, 0);
处理僵尸进程的方式: 第一种:将父进程结束,父进程退出,僵尸进程会变成孤儿进程,孤儿进程会有init回 收资源第二种:使用wait函数,这个函数是父进程要阻塞等待子进程退出,当子进程退出之后 可以处理僵尸进程
第三种:使用信号,通过异步解决僵尸进程的问题,需要结合wait或者waitpid一起使用。
孤儿进程 orphan 父进程结束,子进程没有结束,此时子进程会变成孤儿进程, 孤儿进程将来退出之后,会由init进程回收资源 孤儿进程的父进程就是1号进程,也就是init进程 由于孤儿进程的父进程是init进程,所以不受当前终端影响
Alarm函数 设定一定的时间,代码继续运行,当时间到达时会产生SIGALRM信号
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
功能:设定一定的时间,代码继续运行,当时间到达时会产生SIGALRM信号,
默认对进程的处理方式是终止进程
参数:
seconds:设定的秒数
返回值:
成功:上一个alarm剩余的时间
失败:0
注意:
alarm的返回值表示上一个alarm剩余的时间
如果一个程序中有很多alarm,后面的alarm会覆盖之前的alarm,之前的都会失效
#include <unistd.h>
int pause(void);
功能:阻塞等待一个信号产生
参数:没有参数
返回值:
成功:信号
失败:‐1
例题
1.求打印了多少个A
int main()
{
for(int i=0;i<2;i++)
{
fork();
printf("A\n");
}
}
2.当printf去掉\n呢
int main()
{
for(int i=0;i<2;i++)
{
fork();
printf("A");
}
}
3.打印了几个A
int main()
{
fork() || fork();
printf("A");
}
答案 :1 打印了6个A
如图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fu3AX5VV-1665491677478)(%E8%BF%9B%E7%A8%8B%206c7341e2eea644dc9cfe511b5f370662/Untitled%203.png)]
printf 遇到、n打印并清空缓冲区,打印六个A
2.打印8个A
如图
而没有、n,printf的缓冲区没有完整清除,到值打印了4个AA
3.打印了3个A
父进程fork的返回值是大于0的,所以第二个fork不执行,而子进程中fork的返回值是0,要执行第二个,fork。输出一个A和一个AA
进程间的通信
传统的进程间通信方式
1.无名管道(pipe)
管道(Pipe):管道是一种半双工的通信方式,数据只能单向流动,而且 只能在具有亲缘关系的进程间使用,允许一个进程和另一个与它有共同祖先的进 程之间进行通信。
只能用于具有亲缘关系的进程之间的通信 半双工的通信模式,具有固定的读端和写端
管道可以看成是一种特殊的文件,对于它的读写可以使用文件IO如read、write函数。 管道是基于文件描述符的通信方式。
当一个管道建立时,它会创建两个文件描述符fd[0]和fd[1]。其中fd[0]固定用于读管道,而 fd[1]固定用于写管道。
int pipe(int pipefd[2]);
功能:创建一个无名管道
参数:
pipefd:开辟内核空间后的两个文件描述符
pipefd[0] 读操作
pipefd[1] 写操作
返回值:
成功:0
失败:‐1
例子:
//使用pipe函数创建无名管道
//当从管道中读取数据时,原本管道中读取的数据会被移除
//如果管道中没有数据,则读操作会阻塞等待
int fd[2]//创建int 数组存储两个文件描述符
if(pipe(fd) == ‐1)
{
perror("pipe error");
exit(1);
}
//向无名管道中写数据
write(fd[1], "hello world", 11); //写入hello world
write(fd[1], "nihao beijing", 13);//写入nihao beijing
注意:无名管道无法修改管道内容的位置
即不能使用lseek等,
//从管道中读取数据
char buf[32] = {0};
read(fd[0], buf, 10); //读
printf("buf = %s\n", buf); //打印
read(fd[0], buf, 10); //读
printf("buf = %s\n", buf);//打印
log:
buf = hello worl
buf = dnihao bei
附:无名管道的读写规律
1 、读写端都存在,只读不写
write(fd[1], "hello world", 11);
//如果读写端都存在,只读不写
//如果管道中有数据,则正常读取,如果管道中没有数据,则读端阻塞等
char buf[32] = {0};
read(fd[0], buf, 32); //阻塞
printf("buf = %s\n", buf);
read(fd[0], buf, 32);
printf("buf = %s\n", buf);
2、 读写段都存在,只写不读
//如果读写端都存在,只写不读
//当管道写满后,写操作就会发生阻塞,测试得到**无名管道64k字节**
int num = 0;
while(1)
{
write(fd[1], "hello", 1024);
num++;
printf("num = %d\n", num);
}
3、只有读端,没有写端
write(fd[1], "hello world", 11);
//只有读端没有写端
//如果管道中有数据,则正常读取,如果没有数据,读端会返回0
close(fd[1])//**将写端关闭的方法**
read(fd[0], buf, 32); 当取完了就返回0
4、只有写端没有读端
close(fd[0]);//**将读端关闭的方法
//只有写端没有读端
//此时当执行写操作时,进程会结束,因为产生一个信号SIGPIPE(管道破裂),
//这个信号默认对进程的处理方式是结束进程**
2.有名管道(fifo) (命名管道)
命名管道(named pipe):也是半双工的通信方式,除具有管道所具有 的功能外,它还允许无亲缘关系进程间的通信。命名管道在文件系统中有对应的 文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建。
有名管道可以使互不相关的两个进程互相通信。
有名管道可以通过路径名来指出,并且在文 件系统中可见 p表示管道文件
进程通过文件IO来操作有名管道
有名管道遵循先进先出规则 不支持如lseek() 操作
创建有名管道的方法:
使用shell命令:
mkfifo 文件名
使用mkfifo函数
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
功能:创建一个有名管道
参数:
pathname:管道文件名
mode:管道文件的权限,一般设置为**0664**
返回值:
成功:0
失败:‐1
例子:
if(mkfifo("myfifo", 0664) == ‐1)
{
//如果是因为管道文件存在所报的错,则无需退出进程
//printf("errno = %d\n", errno);
if(errno != 17)
{
ERRLOG("mkfifo error");
}
}
else
{
printf("fifo file has created successfully\n");
}
int fd;
if((fd = open("myfifo", O_RDWR)) == ‐1) //打开有名管道且可读可写方式
{
ERRLOG("open error");
}
**向有名管道中写入数据
注意:有名管道产生的文件只是起到标识作用,方便不同的进程可以找到,
但是有名管道的读写操作本质还是对内核空间开辟的区域的操作,跟这个文件没有关系
有名管道的操作基本跟无名管道类似,读取到的数据会从管道中移除,
如果管道中没有数据,读操作会阻塞。**
write(fd, "hello world", 11); //阻塞(假设管道没有写入数据)
write(fd, "nihao beijing", 13);
****
附:有名管道读写规律
1、读写端都存在,只读不写
//有名管道读写端都存在,只读不写
//如果管道中有数据,则正常读取,如果没有数据,则阻塞等待
int fd;
if((fd = open("myfifo", O_RDWR)) == ‐1)
{
ERRLOG("open error");
}
write(fd, "hello world", 11);//没有数据则等待
2、读写端都存在,只写不读
有名管道读写端都存在,只写不读
当管道写满后,写操作会发生阻塞,**默认有名管道64K**
与无名管道一样
3、**只有读端没有写端 *******
if((fd = open("myfifo", O_RDONLY)) == ‐1)//**只读达到只有读端的状态//(堵塞)**
{
ERRLOG("open error");
}
**有名管道只有读端没有写端
会在open函数的位置发生阻塞**
4、只有写端没有读端
有名管道之后写端没有读端
会在open函数阻塞
if((fd = open("myfifo", O_WRONLY)) == ‐1) //**只写达到只有写端 // (堵塞)**
{
ERRLOG("open error");
}
例子:*
读写端:
***// recv.c 接收端***
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#define ERRLOG(errmsg) do{perror(errmsg);exit(1);}while(0)
int main(int argc, char const *argv[])
{
if(mkfifo("myfifo", 0664) == ‐1)
{
if(errno != 17)
{
ERRLOG("mkfifo error");
}
}
else
{
printf("fifo file has created successfully\n");
}
**//如果一个进程只读,另一个进程只写
//通信过程中如果关闭写端,读端返回0**
int fd;
if((fd = open("myfifo", O_RDONLY)) == ‐1)
{
ERRLOG("open error");
}
char buf[32] = {0};
ssize_t bytes;
while(1)
{
if((bytes = read(fd, buf, 32)) == ‐1)
{
ERRLOG("read error");
}
printf("bytes = %ld, buf = %s\n", bytes, buf);
memset(buf, 0, sizeof(buf));
}
return 0;
}
// ***send.c 发送端***
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#define ERRLOG(errmsg) do{perror(errmsg);exit(1);}while(0)
int main(int argc, char const *argv[])
{
if(mkfifo("myfifo", 0664) == ‐1)
{
if(errno != 17)
{
ERRLOG("mkfifo error");
}
}
else
{
printf("fifo file has created successfully\n");
}
//**如果一个进程只读,另一个进程只写**
//**通信过程中如果关闭读端,写端会产生SIGPIPE信号,是的当前进程结束**
int fd;
if((fd = open("myfifo", O_WRONLY)) == ‐1)
{
ERRLOG("open error");
}
char buf[32] = {0};
while(1)
{
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) ‐ 1] = '\0';
if(write(fd, buf, sizeof(buf)) == ‐1)
{
ERRLOG("write error");
}
}
return 0;
}
3.信号(signal)
信号(Signal):信号是一种比较复杂的通信方式,用于通知接收进程某 个事件已经发生。是Unix系统中使用的最古老的进程间通信的方法之一。
操作系统通过信号来通知进程系统中发生了某种预先规定好的事件(一组事件中的一 个),它也是用户进程之间通信和同步的一种原始机制。
每一个信号产生之后对进程都有一个默认的处理方式,但是不同的信号对进程的处理是不一 样的,所有的信号对进程的处理方式分为四类
1、让进程结束
2、让进程变为停止态
3、让停止的进程恢复执行(后台进程)
4、忽略,对当前进程不做任何处理
用户可以对产生信号之后的进程作出其他处理方式,
1、缺省,默认方式处理
2、忽略,当信号产生后,对当前进程不做任何处理
3、捕捉,自定义方式处理,一般需要通过编写信号处理函数 注意:SIGKILL和SIGSTOP这两个信号只能按照默认的方式处理
linux 查询当前系统中已经定义的信号
signal()
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:当信号产生后设置进程的处理方式
参数:
signum:信号
handler:信号的处理方式
SIG_DFL 以默认的方式处理
SIG_IGN 以忽略的方式处理
handler 以捕捉的方式,也就是通过信号处理函数执行
void handler(int sig)
{
}
返回值:
成功:当前信号原本默认的处理方式
失败:SIG_ERR
例子:
void handler(int sig)
{
if(sig == SIGINT)
{
printf("SIGINT信号产生了,正在执行信号处理函数\n");
}
if(sig == SIGQUIT)
{
printf("SIGQUIT信号产生了,正在执行信号处理函数\n");
}
if(sig == SIGTSTP)
{
printf("SIGTSTP信号产生了,正在执行信号处理函数\n");
}
}
主函数内
signal(SIGINT, handler);
signal(SIGQUIT, handler);
System VIPC对象
1.共享内存(share memory) *****
共享内存:共享内存就是映射一段能被其他进程所访问的内存,这段共享 内存由一个进程创建,但多个进程都可以访问,是最快的可用IPC形式。是针对 其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使 用,来达到进程间的同步及互斥。
IPC对象就是AT&T公司根据最原始的进程间通信方式改造过来的另外三种进程间通信方 式:消息队列、共享内存、信号灯集 使用ipcs查看当前系统中所有的ipc对象
ipcs 查看所有的ipc对象
ipcs -q 查看消息队列
ipcs -m 查看共享内存
ipcs -s 查看信号量
ipcrm 删除ipc对象
ipcrm –q msqid 删除指定的消息队列
共享内存是一种最为高效的进程间通信方式,进程可以直接读写物理内存,而不的拷贝需要 任何数据 为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射 到自己的私有地址空间 进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。
由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等
共享内存是所有进程间通信方式里面效率最高的,直接对物理内存进行操作。
打开或创建一个共享内存 shmget()
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
功能:**打开或者创建一个共享内存**
参数:
key:键值
size:**共享内存的大小**
shmflg:**权限**
一般设置为 **IPC_CREAT | 0777**
返回值:
成功:**共享内存id**
失败:‐1
控制共享内存 shmctl()
#include <sys/ipc.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:**共享内存信息结构体**
返回值:
成功:0
失败:‐1
例子:
//**使用ftok函数获取键值**
key_t key;
if((key = ftok(".", 100)) == ‐1)
{
perror("ftok error");
exit(1);
}
//**创建一个共享内存**
int shmid;
if((shmid = shmget(key, 500, IPC_CREAT |0777)) == ‐1)
{
perror("shmget error");
exit(1);
}
//**删除共享内存**
if(shmctl(shmid, IPC_RMID, NULL) == ‐1)
{
perror("shmctl error");
exit(1);
}
system("ipcs ‐m");
映射共享内存的地址 shmat()
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:**映射共享内存的地址**
参数:
shmid:共享内存id
shmaddr:映射的地址,一般设置为NULL表示自动映射
shmflg:标志位
0 可读可写
SHM_RDONLY 只读
返回值:
成功:映射后给用户的地址
失败:‐1
取消共享内存映射 shmdt
int shmdt(const void *shmaddr);
功能:取消共享内存映射
参数:
shmaddr:要取消的映射的地址,shmat的返回值
返回值:
成功:0
失败:‐1
例子:
send.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
int main(int argc, char const *argv[])
{
//使用ftok函数获取键值
key_t key;
if((key = ftok(".", 100)) == ‐1)
{
perror("ftok error");
exit(1);
}
//创建一个共享内存
int shmid;
if((shmid = shmget(key, 500, IPC_CREAT |0777)) == ‐1)
{
perror("shmget error");
exit(1); 24
}
printf("shmid = %d\n", shmid);
system("ipcs ‐m");
//映射共享内存
char *buf = shmat(shmid, NULL, 0);
strcpy(buf, "hello world"); //你需要拿着给你的地址,向里面写入你想要的数据
//取消共享内存映射
shmdt(buf);
//删除共享内存
// if(shmctl(shmid, IPC_RMID, NULL) == ‐1)
// {
// perror("shmctl error");
// exit(1);
// }
system("ipcs ‐m");
return 0;
}
recv.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
int main(int argc, char const *argv[])
{
//使用ftok函数获取键值
key_t key;
if((key = ftok(".", 100)) == ‐1)
{
perror("ftok error");
exit(1);
}
//创建一个共享内存
int shmid;
if((shmid = shmget(key, 500, IPC_CREAT |0777)) == ‐1)
{
perror("shmget error");
exit(1);
}
printf("shmid = %d\n", shmid);
system("ipcs ‐m");
//映射共享内存
char *buf = (char *)shmat(shmid, NULL, 0); //读出该地址的数据,转为char*类型,
printf("buf = %s\n", buf); //打印字符串出来
//取消共享内存映射
shmdt(buf);
//删除共享内存
// if(shmctl(shmid, IPC_RMID, NULL) == ‐1)
// {
// perror("shmctl error");
// exit(1);
// }
system("ipcs ‐m");
return 0;
}
2.消息队列(message queue) *****
消息(Message)队列:消息队列是消息的链接表,包括Posix消息队列 system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限 的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能 承载无格式字节流以及缓冲区大小受限等缺。
消息队列是IPC对象的一种 消息队列由消息队列ID来唯一标识 消息队列就是一个消息的列表。
用户可以在消息队列中添加消息、读取消息等。
消息队列可以按照类型来发送/接收消息
创建或打开消息队列 msgget()
msgget() / msgctrl() / ftok()
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
功能:**创建或者打开一个消息队列**
参数:
key:键值,通过唯一的键值可以得到唯一的消息队列
key的设置:
第一种:随意设置
第二种:ftok函数获取键值
msgflg:标志位
通常设置为 IPC_CREAT | **0777**
返回值:
成功:消息队列id
失败:‐1
获取消息队列键值 ftok()
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
功能:获取键值
参数:
pathname:路径
proj_id:任意一个值,一般为0‐127
返回值:
成功:键值
失败:‐1
设置消息队列属性 msgctl()
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:**设置消息队列的属性**
参数:
msqid:指定消息队列的id
cmd:执行命令
IPC_STAT 获取消息队列属性信息
IPC_SET 设置消息队列属性信息
IPC_RMID 删除消息队列
msqid_ds:保存或者设置消息队列属性结构体
返回值:
成功:0
时报:‐1
向消息队列里写内容 msgsnd()
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:向消息队列写内容
参数:
msqid:**消息队列id**
msgp**:要写的内容,必须自己定义一个结构体**
struct msgbuf {
long mtype; //消息类型
char mtext[1]; //消息内容
};
msgsz:消息内容的大小,**不包括消息类型**
msgflg:标志位
0 阻塞
IPC_NOWAIT 非阻塞
返回值:
成功:0
失败:‐1
向消息队列里读内容 msgrcv()
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz,
long msgtyp, int msgflg);
功能:从消息队列中读取数据
参数:
msqid:消息队列id
msgp:保存接收的内容
msgsz:消息内容的大小,**不包括消息类型**
msgtyp:指定读取内容的类型
0 按照顺序读取第一个内容
>0 读取第一个消息类型为当前参数的消息
<0 读取小于等于这个数的绝对值中最小消息类型的消息
例如:‐3 读取<=3中**最小的**
msgflg:标志位
0 阻塞
IPC_NOWAIT 非阻塞
返回值:
成功:接收的消息内容的大小
失败:‐1
例子:
//Send.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
typedef struct{
long type;
int id;
char name[32];
int score;
}MSG;
#define TEXTLEN (sizeof(MSG) ‐ sizeof(long))
int main(int argc, char const *argv[])
{
//获取键值
key_t key;
if((key = ftok(".", 100)) == ‐1)
{
perror("ftok error");
exit(1);
}
//创建或者打开一个消息队列
int msqid;
if((msqid = msgget(key, IPC_CREAT | 0777)) == ‐1)
{
perror("msgget error");
exit(1);
}
system("clear");
printf("msqid = %d\n", msqid);
system("ipcs ‐q");
//使用msgsnd向消息队列发送数据
MSG msg1 = {1, 1001, "张三", 90};
MSG msg2 = {2, 1002, "李四", 100};
MSG msg3 = {3, 1003, "王五", 80};
MSG msg4 = {4, 1004, "赵六", 95};
if(msgsnd(msqid, &msg1, TEXTLEN, 0) == ‐1)
{
perror("msgsnd error");
exit(1);
}
if(msgsnd(msqid, &msg2, TEXTLEN, 0) == ‐1)
{
perror("msgsnd error");
exit(1);
}
if(msgsnd(msqid, &msg3, TEXTLEN, 0) == ‐1)
{
perror("msgsnd error");
exit(1);
}
if(msgsnd(msqid, &msg4, TEXTLEN, 0) == ‐1)
{
perror("msgsnd error");
exit(1);
}
//删除消息队列
#if 0
if(msgctl(msqid, IPC_RMID, NULL) == ‐1)
{
perror("msgctl error");
exit(1);
}
#endif
system("ipcs ‐q");
return 0;
}
//recv.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
typedef struct{
long type;
int id;
char name[32];
int score;
}MSG;
#define TEXTLEN (sizeof(MSG) ‐ sizeof(long))
int main(int argc, char const *argv[])
{
//获取键值
key_t key;
if((key = ftok(".", 100)) == ‐1)
{
perror("ftok error");
exit(1);
}
//创建或者打开一个消息队列
int msqid;
if((msqid = msgget(key, IPC_CREAT | 0777)) == ‐1)
{
perror("msgget error");
exit(1);
}
system("clear");
printf("msqid = %d\n", msqid);
system("ipcs ‐q");
//使用msgsnd向消息队列发送数据
MSG msg;
if(msgrcv(msqid, &msg, TEXTLEN, ‐3, 0) == ‐1)
{
perror("msgrcv error");
exit(1);
}
printf("%d‐%s‐%d\n", msg.id, msg.name, msg.score);
//删除消息队列
#if 0
if(msgctl(msqid, IPC_RMID, NULL) == ‐1)
{
perror("msgctl error");
exit(1);
}
#endif
system("ipcs ‐q");
return 0;
}
如果允许,可以使用结构体数组传输。
MSG msgs[]={{1, 1001, "张三", 90},{2, 1002, "李四", 100},{3, 1003, "王五", 80},{4, 1004, "赵六", 95}};
for (int i = 0; i < 4; i++)
{
msgsnd(msqid, &msgs[i], TEXTLEN, 0);
}
另外需要注意,如果读取之后则从消息队列中消失,未读取的不会消失,依然存在。
3.信号灯(semaphore) (信号量)
信号量(semaphore):信号量是一个计数器,可以用来控制多个进程对 共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他 进程也访问该资源。
因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
信号灯集就是信号量,只不过可以通过一个函数创建多个信号量 具体操作还是PV操作,P操作是减操作,V操作是加操作,当信号量的值为0时,P操作阻塞
但实际中使用多个信号也不会使用信号灯集。
打开或创建一个信号灯集 semget( )
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
功能:打开或者创建一个信号灯集
参数:
key:键值
nsems:信号量的个数,至少1个
semflg:标志位
一般设置为 IPC_CREAT | 0777
返回值:
成功:信号灯集的id
失败:‐1
控制信号灯集 semctl()
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
功能:控制信号灯集
参数:
semid:信号灯集的id
semnum:要操作的信号的编号,从0开始
cmd:执行命令
IPC_RMID 删除信号灯集
SETVAL 设置信号量的初始值
需要通过第四个参数来设置,
需要自己定义公用体
union semun
{
int val;
}
返回值:
成功:0
失败:‐1
执行PV操作 semop()
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);
功能:执行PV操作
参数:
semid:信号灯集的id
sops:结构体指针数组,实现具体PV操作
struct sembuf
{
unsigned short sem_num; 要操作的信号量的编号,从0开始
short sem_op;
PV操作
>0 V操作
<0 P操作
short sem_flg; 标志位
0 阻塞
IPC_NOWAIT 非阻塞
}
nsops:要操作的信号量的个数
返回值:
成功:
失败:
例子:
//获取键值
key_t key;
if((key = ftok(".", 100)) == ‐1)
{
perror("ftok error");
exit(1);
}
//创建信号灯集
int semid;
if((semid = semget(key, 3, IPC_CREAT | 0777)) == ‐1)
{
perror("setget error");
exit(1);
}
//删除信号灯集
if(semctl(semid, 0, IPC_RMID) == ‐1)
{
perror("semctl error");
exit(1);
}
system("ipcs ‐s")//查看
BSD
1.套接字(socket)
套接字(Socket):与其他通信机制不同的是,它可用于不同机器间的进 程通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其 它类Unix系统上:Linux和System V的变种都支持套接字。
进程间的通信——总结:
进程间如何实现通信?
因为任何一个进程创建或者开启的时候,都会有公有的1G的内核空间,所以多个进程 只要能够操作同一个在内核空间开辟的区域,就可以实现通信 所以学习进程间通信方式其实就是学习如何在内核空间开辟区域(除信号和共享内存)。
**前六种进程间通信方式只能实现一台主机的多个进程间通信 只有套接字可以在不同主机的多个进程间实现通信 效率最高的:共享内存,他是直接获取到物理内存的地址,直接对物理内存进行操作。**
总结一些函数和指令等
IO
标准IO
文件指针(FILE *)
fopen() fclose()
fprintf() sprintf()
fgetc() fpuc() fgets() fputs() fread() fwrite()
fseek() rewind() ftell()
perror() errno
文件IO
文件描述符(int)
open() close()
read() write()
lseek()
文件属性
stat
目录操作
opendir()
readdir()
closedir()
动态库和静态库
静态库
gcc ‐c mymath.c ‐o mymath.o
ar crs libmymath.a mymath.o
gcc main.c ‐L. ‐lmymath
动态库
gcc ‐fPIC ‐c mymath.c ‐o mymath.o
gcc ‐shared mymath.o ‐o libmymath.so
gcc main.c ‐lmymath
进程
进程的基本操作
fork()
getpid() getppid()
exit() _exit()
wait() waitpid()
进程间通信方式
原始的:
无名管道
pipe()
有名管道
mkfifo()
信号通信
signal()
kill() raise()
alarm() pause()
IPC对象:
消息队列
ftok()
msgget()
msgctl()
msgsnd()
msgrcv()
共享内存
shmget()
shmctl()
shmat()
shmdt()
信号灯集
semget()
semctl()
semop()
线程
线程的基本操作
pthread_create()
pthread_self()
pthread_exit()
pthread_cancel()
pthread_join()
pthread‐detach()
线程间同步与互斥机制
互斥锁
pthread_mutex_t
pthread_mutex_init()
pthread_mutex_lock()
pthread_mutex_unlock()
pthread_mutex_destroy();
条件变量
pthread_cond_t
pthread_cond_init()
pthread_cond_signal()
pthread_cond_broadcast()
pthread_cond_wait()
pthread_cond_destroy()
信号量
sem_t
sem_int()
sem_post()
sem_wait()
sem_destroy()
点赞获取PDF版本
链接: https://pan.baidu.com/s/1DdnO4k3Y3A9q_kyVUOUJrw?pwd=yyds
提取码: yyds
复制这段内容后打开百度网盘手机App,操作更方便哦