进程基本概念:
1、进程与程序
程序就是储存在磁盘上的可执行文件,程序被加载到内存中开始运行进程。
一个程序被多次加载时就生成了多个进程
进程就是处于活动状态的计算机程序
2、进程的分类
进程一般分为三种类型:交互进程、批处理进程、守护进程
守护进程一般处于活跃状态,运行在后台,由系统在开机时通过启动脚本来自动创建
3、 查看进程
简单方式 ps 显示当前用户有控制终端的进程信息
列表方式 ps auxw 显示所有进程详细信息
a 所有用户的有控制终端的进程
x 无控制终端的进程
u 显示进程的详细信息
w 以更大的列宽显示
USER 进程的属主
PID 进程的编号
%CPU CPU利用率
%MEM 内核使用率
VSZ 虚拟内存使用字节数
RSS 物理内存使用字节数
TYY 终端设备号 ? 表示无终端控制设备
STAT 进程的状态
O 就绪,等待被调度
R 运行,Linux系统没有0,就绪也用R表示
S 可被唤醒的睡眠,例如系统中断、获得资源、收到信号都可以唤醒它进入运行态
D 不可唤醒的睡眠,只能被系统唤醒
T 暂停态 收到SIGSTOP信号进入暂停态,收到SIGCONT信号转入运行态
W 等待内存分页 (2.6内核后被废弃了)
Z 僵尸状态
X 死亡状态
< 高优先级
N 低优先级
l 多线程的进程
s 进程的领导者
L 有内存被锁进内存
+ 处于后台的进程组
START 进程启动的时间
TIME
COMMAND 启动进程的命令
4、父进程、子进程、孤儿进程与僵尸进程
一个进程可以被另一个进程创建,创建者叫父进程,被创建者叫做子进程,子进程被父进程启动后会在操作系统的调用下同时运行
当子进程先于父进程结束时,子进程会向父进程发送SIGCHLD信号,此时父进程应该去回收子进程的相关资源,如果没有回收,那么子进程就会变成僵尸进程
僵尸进程:该进程已经死亡,但是它的父进程没有立即回收它的相关资源,该进程就进入了僵尸状态
孤儿进程:父进程先于子进程结束,子进程就变成了孤儿进程,此时孤儿进程会被孤儿院(init守护进程)领养,init就是该孤儿进程的父进程
5、进程标识符
每个进程都有一个非负整数表示的唯一表示,即是它的进程ID/PID
进程ID在任何时候都是唯一的,但可以重用,进程一旦结束后,它的PID会被系统 回收,过一段时间才可以重新分配给其他新创建的进程使用(延迟重用)
pid_t getpid(void);
功能:获取当前进程的进程ID
pid_t getppid(void);
功能:获取当前进程的父进程的ID
init的进程ID永远是1
创建进程:
int system(const char *command);
功能:执行一个可执行文件,这样就创建了一个子进程
返回值:子进程结束后才返回
该函数的实现调用了fork和waitpid函数,其实相当于创建了一个子进程,该子进程` 加载了可执行文件command
pid_t fork(void);
功能:创建子进程
返回值:一次调用两次返回,子进程返回0,父进程返回子进程的ID,当进程的数量 超过系统限制就会创建进程失败,返回-1
通过fork创建的子进程会拷贝父进程(数据段、bss段、堆、栈、IO缓冲区)等数据 区,与父进程共享代码段,子进程会继承父进程的信息处理方式
该函数调用后,父子进程各自独立运行,谁先返回并不确定,但是可以通过睡眠确 定哪个进程先执行
for(;😉
{
fork(); //会资源耗尽
}
可以根据返回值的不同来让父进程进入不同的分支,执行不同的代码
通过fork创建子进程可以共享父进程的文件描述符
不同的普通进程不能共享同一个值的文件描述符
pid_t vfork(void)
功能:以加载可执行文件方式来创建子进程
返回值:子进程返回0,父进程返回子进程的PID
子进程先返回,此时子进程并没有创建成功,需要加载一个可执行文件来替换当前子进 程的所有资源,完成替换的子进程才算创建成功,此时父进程才能返回
#include <stdio.h>
#include <unistd.h>
int main(int argc,const char* argv[])
{
printf(".%d\n",getpid());
pid_t pid = fork();
if(0 == pid)
{
printf("我是子进程%u 我的父进程是 %u\n",getpid(),getppid());
pause();
}
else
{
printf("我是父进程 %u,我的子进程是 %u\n",getpid(),pid);
pause();
}
}
使用exec系统函数加载可执行文件:
int execl(const char *path, const char *arg, ...);
path:可执行文件的路径
arg:命令行参数,一般第一个就是可执行文件的名字,至少有一个,以NULL结尾
int execlp(const char *file, const char *arg, ...);
file:可执行文件名字,会根据环境变量PATH的路径来查找可执行文件
int execle(const char *path, const char *arg,
..., char * const envp[]);
envp:环境变量表,父进程可以在加载子进程时把环境变量表传递给子进程,这样父子进程就共用一个环境变量表
int execv(const char *path, char *const argv[]);
argv:指针数组,由传递给子进程的字符串组成,以NULL结尾
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
char *const envp[]);
注意:exec系列函数正常是不会返回的,当加载子进程加载可执行文件失败时会返回-1
以exec系列函数创建的子进程不会继承父进程的信号处理函数,但是能继承父进程的信号屏蔽
进程的正常退出:
1、在main函数中执行return n,该返回值可以被其父进程接收
2、调用了exit函数,该函数是标准库函数
void exit(int status);
功能:在任意时候调用了此函数都可以结束进程
status:结束状态码,与return函数中的返回值效果一样
注意:该函数不会返回
进程退出前
int atexit(void (*function)(void));
功能:注册一个进程结束前要执行的函数
int on_exit(void (*function)(int , void *), void *arg);
功能:注册一个进程结束前要执行的函数
int:return的值或者exit函数的参数
arg:会在进程结束时自动传给function函数
调用exit会进行的事情:
1、先调用事先通过atexit/on_exit注册过的函数,如果都注册了,执行顺序与注册顺序相反
2、冲刷并关闭打开状态下的标准IO流
3、该函数的实现调用了_exit/_Exit
3、调用 _exit / _Exit函数
void _exit(int status);
功能:结束进程,由系统提供
void _Exit(int status);
功能:结束进程,由标准库提供
1、它们的参数会被父进程获取到
2、进程结束前会关闭所有打开状态下的文件描述符(open)
3、给父进程发送SIGCHLD信号
4、该函数也不会返回
4、进程的最后一个线程执行返回语句
5、进程的最后一个线程执行pthread_exit函数
进程的异常终止:
1、进程调用了abort函数,产生了SIGABRT信号
2、进程接受某些信号,可以是其他进程发送,也可以是自己发送的,还可能是自己错误导致
3、进程的最后一个线程接收到了"取消"操作,并对此做出响应
这三种结束方式导致父进程无法获取结束状态码,所有叫做异常终止
注意:不管进程是如何结束都会执行同一段代码,关闭所有打开状态下的文件描述符,释放所有的内存
无论
子进程的回收:
对于任何结束方式,都希望父进程能够知道,通过wait、waitpid函数可以知道子进程是如何结束的以及结束状态码
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
功能:等待子进程的结束,并获取结束状态码
返回值:子进程PID
1、如果所有的子进程都在运行,该函数会阻塞
2、如果有任意一个子进程结束,立即返回该子进程结束状态码和PID
3、如果没有子进程则立即返回-1
WIFEXITED(status) 判断子进程是否正常结束,如果是则返回真
WEXITSTATUS(status) 如果子进程是正常结束的,可以获取正确的结束状态码,只 能获取低八位
WIFSIGNALED(status) 判断子进程是否正常结束,如果是则返回真
WTERMSIG(status) 判断子进程是异常终止,可以获取导致异常终止的信号编码
注意:
由于wait函数可能会阻塞,因此不适合放在父进程的业务逻辑中来调用,因此,可以用SIGCHID信号处理函数,在处理函数中来调用wait函数,注意既可以获取结束的子进程的状态,也不会影响父进程的正常业务逻辑
pid_t waitpid(pid_t pid, int *status, int options);
功能:指定回收某个或某些进程
pid:
< -1 等待属于abs(pid)的编号的进程组中的进程结束
-1 等待任意子进程的结束,功能等同于wait
0 等待同组的任意进程结束
>0 等待pid该进程结束
status:进程结束状态码,与wait的等价
options:
0 阻塞模式,等于wait
WNOHANG 非阻塞模式,如果没有子进程结束,立刻返回
WUNTRACED 如果有进程处于暂停状态时,返回该进程的状态码
WCONTINUED 如果有进程从暂停状态转为继续运行,返回进程的状态码
WIFSTOPPED(status) 判断进程是否处于暂停状态,是则返回真
WSTOPSIG(status) 获取导致进程暂停的信号
WIFCONTINUED(STATUS)判断进程是否由暂停态转为运行态,是则返回真
练习:利用今天的知识,实现一个system函数
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int _system(const char* command)
{
pid_t pid = vfork();
if(0 == pid)
{
return execlp(command,command,NULL);
}
else
{
int status = 0;
waitpid(pid,&status,0);
return status;
}
}
int main(int argc,const char* argv[],char** environ)
{
_system("clear");
printf("-----------");
}
进程间通信:
基本概念:
什么是进程间通信:
是指两个或者多个进程之间交互数据交互数据的过程,因为进程之间是相互独立的,为了能够让多个进程协同工作,必须互相交互数据
进程间通信的分类:(必考)
简单的进程间通信: 信号、文件、环境变量、命令行参数
传统的进程间通信方式:管道文件
XSI进程间通信: 共享内存、消息队列、信号量
网络进程间通信: Socket套接字 本地-socket文件 网络-TCP/IP
传统进程间通信:
管道是UNIX最古老的进程间通信方式,古老意味着所有系统都支持,早期管道都是半双工,现在有些系统的管道是全双工(但是也要假定管道是半双工来实现)
管道就是一种特殊的文件,它的数据在文件中是流动的,读取之后会就会消失,如果文件中没有数据可以读取则会阻塞
有名管道:
基于有文件名的管道文件的通信
编程模型
进程A 进程B
创建管道 …
打开管道 打开管道
写数据 读数据
关闭管道 关闭管道
删除管道 …
创建管道文件:
1、命令 mkfifo file
2、函数:
int mkfifo(const char *pathname, mode_t mode);
功能:创建管道文件
pathname:管道文件路径
mode:管道文件权限
3、标准库函数
FILE* popen(const char *command, const char *type);
功能:打开或创建管道文件
command:可执行文件名
type:
r 文件指针链接到可执行文件的标准输出
w 文件指针链接到可执行文件的标准输入
int pclose(FILE *stream);
功能:关闭管道文件
注意:关闭管道文件的专用函数,一定不能与fclose混用
// A
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc,const char* argv[])
{
int fd[2] ={};
// 获取匿名管道的fd数组
if(pipe(fd))
{
perror("pipe");
return -1;
}
// 创建子进程
if(fork())
{
// 父进程 负责写 关闭读
close(fd[0]);
for(;;)
{
char buf[256] = {};
printf(">>>");
scanf("%s",buf);
write(fd[1],buf,strlen(buf));
if(0 == strcmp("quit",buf))
{
printf("通信结束\n");
usleep(5000);
break;
}
usleep(5000);
}
close(fd[1]);
}
else
{
// 子进程 负责读 关闭写
close(fd[1]);
for(;;)
{
char buf[256] = {};
read(fd[0],buf,sizeof(buf));
printf("read:%s\n",buf);
if(0 == strcmp(buf,"quit"))
{
printf("通信结束\n");
break;
}
}
close(fd[0]);
}
}
// B
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main(int argc,const char* argv[])
{
//打开管道文件
int fd = open("fifo",O_RDONLY);
if(0 > fd)
{
perror("open");
return -1;
}
//读数据
for(;;)
{
char buf[256] = {};
read(fd,buf,sizeof(buf));
printf("read:%s\n",buf);
if(0 == strcmp(buf,"quit"))
{
printf("通信结束!\n");
break;
}
}
//关闭管道文件
close(fd);
}
匿名管道:
注意:只适合通过fork函数创建的父子进程之间使用
编程模型
父进程 子进程
创建获取一对fd
创建子进程 拷贝一对fd
关闭读 关闭写
写数据 读数据
关闭写 关闭读
int pipe(int pipefd[2]);
功能:创建一个匿名管道文件,
pipefd:用于存储管道文件的fd的数组
pipefd[0]:用于读
pipefd[1]:用于写
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc,const char* argv[])
{
int fd[2] ={};
// 获取匿名管道的fd数组
if(pipe(fd))
{
perror("pipe");
return -1;
}
// 创建子进程
if(fork())
{
// 父进程 负责写 关闭读
close(fd[0]);
for(;;)
{
char buf[256] = {};
printf(">>>");
scanf("%s",buf);
write(fd[1],buf,strlen(buf));
if(0 == strcmp("quit",buf))
{
printf("通信结束\n");
usleep(5000);
break;
}
usleep(5000);
}
close(fd[1]);
}
else
{
// 子进程 负责读 关闭写
close(fd[1]);
for(;;)
{
char buf[256] = {};
read(fd[0],buf,sizeof(buf));
printf("read:%s\n",buf);
if(0 == strcmp(buf,"quit"))
{
printf("通信结束\n");
break;
}
}
close(fd[0]);
}
}
XSI进程间通信:
X/open 公司执行的用于进程间通信的系统接口
XSI进程间通信都需要借助系统内核,需要创建内核对象,内核对象以整数形式返回给用户,相当于文 件描述符,也叫做IPC标识符
文件的创建、打开都需要文件名,IPC内核对象的创建也需要借助IPC键值(整数),必须确保IPC键值独一 无二
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
功能:计算出一个独一无二的IPC键值
pathname:项目路径,不是依赖字符计算的,而是依赖路径的位置,如果提供的是假的路径,可能会计算出系统的IPC键值
proj_id:项目编号
返回值:计算出来的IPC键值
共享内存:
基本特点:
两个或者多个进程之间共享一块由内核负责管理维护的内存,该内存可以与进程的虚拟内存空间进行映射
优点:不需要复制信息,是最快的IPC机制
缺点:需要考虑同步访问问题
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
功能:创建\获取共享内存
key:由进程提供的独一无二的IPC键值
size:共享内存的大小,获取共享内存时,此参数无意义,一般设置为0
shmflg:
IPC_CREAT 创建共享内存
IPC_EXCL 共享内存已经存在时返回错误
获取时赋值 0
mode_flags:创建共享内存时需要提供权限
返回值:IPC标识符,错误返回-1
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:虚拟内存与共享内存进行映射
shmid:IPC标识符,shmget的返回值
shmaddr:想要映射的虚拟内存地址,给NULL系统会自动操作
shmflg:
SHM_RDONLY:以只读方式访问共享内存
返回值:与共享内存映射后的虚拟内存的首地址,失败返回(void *) -1
int sgmdt(const void *shmaddr);
功能:取消映射
shmaddr:映射过的虚拟内存地址
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:删除/控制共享内存
shmid:IPC标识符
cmd:
IPC_STAT 获取共享内存属性 则buf为输出型参数
IPC_SET 设置共享内存属性 则buf为输入型参数
IPC_RMID 删除共享内存 NULL
struct shmid_ds {
struct ipc_perm shm_perm; //所有者的相关信息
size_t shm_segsz; //共享内存字节数
time_t shm_atime; //最后映射时间
time_t shm_dtime; //最后取消映射时间
time_t shm_ctime; //最后改变时间
pid_t shm_cpid; //创建者进程号
pid_t shm_lpid; //最后映射\取消映射的进程号
shmatt_t shm_nattch; //当前映射次数
...
};
struct ipc_perm {
key_t __key; //创建共享内存的PIC键值
uid_t uid; //当前使用共享内存的用户ID
gid_t gid; //当前使用共享内存的用户组ID
uid_t cuid; //创建共享内存的用户ID
gid_t cgid; //创建共享内存的用户组ID
unsigned short mode; //共享内存权限
unsigned short __seq; //共享内存的序列号
};
编程模型
进程A 进程B
创建共享内存 获取共享内存
映射共享内存 映射共享内存
写数据后通知读 接收读通知后读数据
接收读通知后读数据 写数据后通知读
取消映射 取消映射
删除共享内存
// 进程A
#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <string.h>
int shmid;
char* shm;
//接收到读消息信号处理函数,负责从共享内存中读数据
void sigrtmin(int num)
{
// 接收读消息并读取数据
printf("read:%s\n",shm);
if(0 == strcmp("quit",shm))
{
printf("通信结束\n");
//取消映射
if(shmdt(shm))
{
perror("shmdt");
exit(-1);
}
usleep(1000);//等待对面进程取消映射
//删除共享内存
if(shmctl(shmid,IPC_RMID,NULL))
{
perror("shmctl");
exit(-1);
}
}
}
int main(int argc,const char* argv[])
{
// 注册信号处理函数
signal(SIGRTMIN,sigrtmin);
printf("我是进程%u\n",getpid());
pid_t pid = 0;
printf("请输入与我通信的进程PID:");
scanf("%u",&pid);
// 创建共享内存
shmid = shmget(ftok(".",110),4096,IPC_CREAT|0644);
if(0 > shmid)
{
perror("shmget");
return -1;
}
// 映射共享内存
shm = shmat(shmid,NULL,0);
if(shm == (void*)-1)
{
perror("shmat");
return -1;
}
// 写入数据并通知其他进程
for(;;)
{
printf(">>>");
scanf("%s",shm);
kill(pid,SIGRTMIN);
if(0 == strcmp(shm,"quit"))
{
printf("通信结束\n");
break;
}
}
// 取消映射
if(shmdt(shm))
{
perror("shmdt");
return -1;
}
usleep(1000);//等待对面进程取消映射
//删除共享内存
if(shmctl(shmid,IPC_RMID,NULL))
{
perror("shmctl");
return -1;
}
}
// 进程B
#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <string.h>
char* shm;
//接收到读消息信号处理函数,负责从共享内存中读数据
void sigrtmin(int num)
{
// 接收读消息并读取数据
printf("read:%s\n",shm);
if(0 == strcmp("quit",shm))
{
printf("通信结束\n");
//取消映射
if(shmdt(shm))
{
perror("shmdt");
exit(-1);
}
exit(0);//已经接收到quti,需要结束整个程序
}
}
int main(int argc,const char* argv[])
{
// 注册信号处理函数
signal(SIGRTMIN,sigrtmin);
printf("我是进程%u\n",getpid());
pid_t pid = 0;
printf("请输入与我通信的进程PID:");
scanf("%u",&pid);
// 获取共享内存
int shmid = shmget(ftok(".",110),0,0);
if(0 > shmid)
{
perror("shmget");
return -1;
}
// 映射共享内存
shm = shmat(shmid,NULL,0);
if(shm == (void*)-1)
{
perror("shmat");
return -1;
}
// 写入数据并通知其他进程
for(;;)
{
printf(">>>");
scanf("%s",shm);
kill(pid,SIGRTMIN);
if(0 == strcmp(shm,"quit"))
{
printf("通信结束\n");
break;
}
}
// 取消映射
if(shmdt(shm))
{
perror("shmdt");
return -1;
}
}
消息队列:
基本特点:是由内核管理维护的数据链表,通过消息类型来收发数据。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
功能:创建\获取消息队列
key:IPC键值
msgflg:
IPC_CREAT 创建消息队列
IPC_EXCL 如果消息队列已存在则会返回错误
如果获取消息队列 直接给0
如果是创建消息队列,记得提供权限mode
返回值:成功返回IPC标识符,错误返回-1
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:向消息列表发送数据
msqid:IPC标识符
msgp:要发送的消息的首地址
struct msgbuf {
long mtype; // 消息类型
char mtext[n]; // 数据
};
msgsz:数据的字节数,不包含消息类型
msgfl:
阻塞一般写0
IPC_NOWAIT 当消息队列满时,不等待立即返回
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
功能:从消息队列中读取数据
msqid:IPC标识符
msgp:存储数据的首地址
msgsz:结构体中数据的最大字节数
msgtyp:消息类型
0 读取消息队列第一条消息
>0 读取消息队列中消息类型等于msgtyp的消息
<0 读取消息类型小于abs(msgtyp)消息,如果多个则读取最小的消息
msgflg:
IPC_NOWAIT 当消息队列中没有匹配的消息类型时,立即返回
MSG_EXCEPT 如果msgtyp>0,则读取第一个消息类型不是msgtyp的消息
MSG_NOERROR 如果包含此标志,则读取msgsz个字节,如果不包含此标志,消息实际长度>msgsz,则会返回错误而不读取数据。
返回值:成功读取到的字节数
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:删除\获取消息队列的属性
msqid:IPC标识符
cmd:
IPC_STAT 获取信息队列的属性
IPC_SET 设置消息队列的属性
IPC_RMID 删除消息队列
buf:
struct msqid_ds {
struct ipc_perm msg_perm; // 属主信息
time_t msg_stime; // 最后发送时间
time_t msg_rtime; // 最后接收时间
time_t msg_ctime; // 最后修改时间
unsigned long __msg_cbytes; // 当前消息队列的字节数
msgqnum_t msg_qnum; // 当前消息队列的消息数
msglen_t msg_qbytes; // 队列中最大消息的字节数
pid_t msg_lspid; // 最后发送者的PID
pid_t msg_lrpid; // 最后接收者的PID
};
编程模型
进程A 进程B
创建消息队列 获取消息队列
发送消息 接收消息
接收消息 发送消息
删除消息队列
// 进程A
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include "messgeage.h"
int main(int argc,const char* argv[])
{
// 创建消息队列
int msgid = msgget(ftok(".",120),IPC_CREAT|0644);
if(0 > msgid)
{
perror("msgget");
return -1;
}
Msg msg = {};
for(;;)
{
msg.type = 5;
printf(">>>");
scanf("%s",msg.data);
//发送消息
if(msgsnd(msgid,&msg,strlen(msg.data)+1,0))
{
perror("msgsnd");
break;
}
if(0 == strcmp("quit",msg.data)) break;
//接收消息
if(0 == msgrcv(msgid,&msg,MESMAX,6,0))
{
perror("msgrcv");
break;
}
printf("recv:%s\n",msg.data);
if(0 == strcmp("quit",msg.data)) break;
}
printf("通信结束!\n");
usleep(1000);
// 删除消息队列
if(msgctl(msgid,IPC_RMID,NULL))
{
perror("msgctl");
return -1;
}
return 0;
}
// 进程B
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include "messgeage.h"
int main(int argc,const char* argv[])
{
// 获取消息队列
int msgid = msgget(ftok(".",120),0);
if(0 > msgid)
{
perror("msgget");
return -1;
}
Msg msg = {};
for(;;)
{
//接收消息
if(0 == msgrcv(msgid,&msg,MESMAX,5,0))
{
perror("msgrcv");
break;
}
printf("recv:%s\n",msg.data);
if(0 == strcmp("quit",msg.data)) break;
msg.type = 6;
printf(">>>");
scanf("%s",msg.data);
//发送消息
if(msgsnd(msgid,&msg,strlen(msg.data)+1,0))
{
perror("msgsnd");
break;
}
if(0 == strcmp("quit",msg.data)) break;
}
printf("通信结束!\n");
return 0;
}
信息量:
基本特点:由内核共享维护的一个"全局变量",用于记录共享资源的数量,限制进程对资源的访问
1、如果变量的值大于0,说明可以使用资源,此时需要将变量值-1,然后才能使用资源
2、如果变量的值等于0 ,说明可以资源可以使用,此时进程会进入休眠,直到变量大于0,进程会被唤醒,执行步骤 1
3、当资源使用完毕时,变量的值+1,正在休眠的进程可以被唤醒了
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
功能:创建或获取信息量
key:IPC键值
nsems:信号量的数量 一般写1即可
semflg:
IPC_CREAT 创建信号量
IPC_EXCL 存在则返回-1
mode: 权限
返回值:IPC标识符 失败返回-1
int semop(int semid, struct sembuf *sops, unsigned nsops);
功能:对信号量进行加减操作
semid:IPC标识符
sops:
struct sembuf
{
unsigned short sem_num; // 信号量下标
short sem_op;
1 信号量+1
0 等待信号量的值为0
>0 信号量-1,如果不能-1,则阻塞
short sem_flg;
IPC_NOWAIT 不阻塞
SEM_UNDO 如果进程终止还没还原信号量,系统将自动还原
}
nsops:表示sops指针指向多少个结构体,一般写1
int semctl(int semid, int semnum, int cmd, ...);
功能:删除/控制信号量
semid:IPC标识符
semnum:信号量下标
cmd:
IPC_STAT 获取共享内存属性
IPC_SET 设置共享内存属性
IPC_RMID 删除共享内存
GETALL 获取所有信号量的值
GETVAL 获取某个信号量的值
GETNCNT 获取所有等待减信号的进程数量
SETALL 设置所有信号量的值
SETVAL 设置某个信号量的值
union semun {
int val; // 用于设置信号量的值
struct semid_ds *buf; // 信号量的属性
unsigned short *array; // 批量获取/设置信号量的值
};
骑小毛驴
#include <stdio.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>
int main(int argc,const char* argv[])
{
// 创建信号量
int semid = semget(ftok(".",119),1,IPC_CREAT|0644);
if(0 > semid)
{
perror("semget");
return -1;
}
// 设置信号量的值
if(semctl(semid,0,SETVAL,5))
{
perror("semctl");
return -1;
}
// printf("%d\n",semctl(semid,0,GETVAL));
printf("我是进程%u,我有%d头小毛驴\n",getpid(),semctl(semid,0,GETVAL));
// 十个进程争夺五个资源
for(int i=0; i<10; i++)
{
pid_t pid = fork();
if(0 == pid)
{
struct sembuf buf = {0,-1,0};
semop(semid,&buf,1);
printf("我是子进程%u,我骑了一头小毛驴,还有%d头小毛驴\n",getpid(),semctl(semid,0,GETVAL));
sleep(i);
buf.sem_op = 1;
semop(semid,&buf,1);
printf("我是子进程%u,我还了一头小毛驴,还有%d头小毛驴\n",getpid(),semctl(semid,0,GETVAL));
return 0;
}
}
while(-1 !=wait(NULL));
printf("我是进程%u,我有%d头小毛驴\n",getpid(),semctl(semid,0,GETVAL));
if(semctl(semid,0,IPC_RMID))
{
perror("semctl");
return -1;
}
}