c程序获取进程pid和ppid
在 Linux 系统中管理进程使用树型管理方式每个进程都需要与其他某一个进程建立
父子关系, 对应的进程则叫做 父进程
Linux 系统会为每个进程分配 id , 这个 id 作为当前进程的唯一标识, 当进程结束, 则会回收
进程的 id 与 父进程的 id 分别通过 getpid() 与 getppid() 来获取
函数头文件
#include <sys/types.h>
#include <unistd.h>
函数原型
pid_t getpid(void);
pid_t getppid(void);
函数功能
获取当前进程 id 与 父进程 id
函数返回值
成功 : getpid() 返回当前进程 id ,getppid() 返回父进程 id
pid_t 的类型 实际为 int , 在系统中采用 typedef 的形式
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main(){
printf("pid=%d,ppid=%d",getpid(),getppid());
return 0;
}
进程相关命令
功能:显示当前进程的状态 (Process Status)
语法
ps [options]
常用语法选项
-A : 列出所有的进程
-e : 与 -A 功能类似
-w : 显示加宽可以显示较多的资讯
-au : 显示较详细的信息
-aux : 显示所有包含其他使用者的进程
示例 : ps -ef 列出所有的进程,相比 ps -aux 信息要少一些
示例 : ps -ef | grep “可执行文件名” 根据名称查找指定名字
top [-] [d delay] [q] [c] [S] [s] [i] [n] [b]
选项
d : 改变显示的更新速度,或是在交谈式指令列 (interactive command) 按 s
q : 没有任何延迟的显示速度,如果使用者是有 superuser 的权限,则 top 将会以最高的优先序执行
c : 切换显示模式,共有两种模式,一是只显示执行档的名称,另一种是显示完整的路径与名称
S : 累积模式,会将己完成或消失的子进程 (dead child process) 的 CPU time 累积起来
s : 安全模式,将交谈式指令取消, 避免潜在的危机
i : 不显示任何闲置 (idle) 或无用 (zombie) 的进程
n : 更新的次数,完成后将会退出 top
b : 批次档模式,搭配 “n” 参数一起使用,可以用来将 top 的结果输出到档案内
top -p <进程 id>
pstree 命令是将所有的进程以树型结构的方式进行展示
功能:kill 命令是用于结束进程的命令或者用于显示相关信号
kill -9 <pid号>
进程的创建
创建进程的函数需要调用 fork() 函数, 则会产生一个新的进程
函数头文件
#include <sys/types.h>
#include <unistd.h>
函数原型
pid_t fork(void);
函数功能:创建一个子进程
函数返回值
成功 : 返回给父进程是子进程 的 pid , 返回给子进程的是 0
失败 : 返回 -1, 并设置 errno
示例 : 创建一个子进程,并打印 HelloWorld
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main(){
pid_t pid;
pid=fork();
if(pid==-1){
perror("[ERROR fork():]");
return -1;
}
printf("hello\n");
return 0;
}
通过 fork() 函数创建子进程之后,有如下特点:
1、父子进程的执行顺序由操作系统算法决定的,不是由程序本身决定
2、子进程会拷贝父进程地址空间的内容, 包括缓冲区、文件描述符等
eg:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main(){
write(1,"hello",5);
fputs("hello",stdout);
pid_t pid;
pid=fork();
if(pid==-1){
perror("[ERROR fork():]");
return -1;
}
return 0;
}
结果打印了三个hello
因为fputs是有缓冲区,fork子进程后会拷贝父进程地址空间的内容
多进程
在创建多个进程时, 最主要的原则为 由父进程统一创建,统一管理,
不能进行递归创建
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
int main(){
pid_t pid;
pid=fork();
if(pid==-1){
perror("[ERROR fork():]");
exit(EXIT_FAILURE);
}
else if(pid==0){
printf("process son A pid=%d\n",getpid());
sleep(2);
exit(EXIT_SUCCESS);
}
else if(pid>0){
pid=fork();
if(pid==-1){
perror("[ERROR fork():]");
exit(EXIT_FAILURE);
}
else if(pid==0){
printf("process son B pid=%d\n",getpid());
sleep(3);
exit(EXIT_SUCCESS);
}
else if(pid>0){
}
}
return 0;
}
进程退出
exit()函数
函数头文件
#include <stdlib.h>
函数原型
void exit(int status);
函数功能:结束进程,并刷新缓冲区
函数参数
status : 退出状态值
在系统中定义了两个状态值 :
EXIT_SUCCESS: 正常退出
EXIT_FAILURE: 异常退出, 具体定义在 stdlib.h 中
#define EXIT_FAILURE 1
#define EXIT_SUCCESS
_exit函数
函数头文件
#include <unistd.h>
函数原型
void _exit(int status);
函数参数
status : 进程退出的状态值
函数功能:结束进程,不会刷新缓冲区
函数实例1 : exit.c
#include<stdio.h>
#include<stdlib.h>
int main()
{
printf("using exit----\n");
printf("This is the content in buffer\n");
exit(0);
}
执行结果为:
using exit----
This is the content in buffer
函数实例2:_exit.c
#include<stdio.h>
#include<stdlib.h>
int main()
{
printf("using _exit--\n");
printf("This is the content in buffer");
_exit(0);
}
执行结果为 :
using _exit--
printf函数就是使用缓冲I/O的方式,该函数在遇到“\n”换行符时自动的从缓冲区中将记录读出。所以exit()将缓冲区的数据写完后才退出,而_exit()函数直接退出
进程的等待
父进程 调用wait()与waitpid()函数等待子进程退出后,释放子进程遗留的资源
wait函数
函数头文件
#include <sys/types.h>
#include <sys/wait.h>
函数原型
pid_t wait(int *wstatus);
函数功能:让函数调用者进程进入到睡眠状态, 等待子进程进入僵死状态后,释放相关资源并返回
函数参数:
wstatus : 保存子进程退出状态值变量的指针,获取具体值需要使用WEXITSTATUS(status)宏定义
函数返回值:
成功 : 返回退出子进程的 pid
失败 :返回 -1
示例:创建一个子进程,延时3s后退出,父进程等待子进程后退出
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<sys/wait.h>
int main(){
pid_t pid;
pid=fork();
if(pid==-1){
perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
else if(pid==0){
printf("the child process pid=%d\n",getpid());
sleep(3);
exit(66);
}
else if(pid>0){
int status=0;
pid_t cpid=wait(&status);
if(cpid==-1){
perror("[ERROR] wait():");
exit(EXIT_FAILURE);
}
printf("the child process pid=%d died status=%d\n",cpid,WEXITSTATUS(status));
}
return 0;
}
打印结果:
the child process pid=17596
the child process pid=17596 died status=66
在 wait 存储在 satus 变量的值, 存储了很多信息, 通过一系列 W 开头的宏来解析获取
WIFEXITED(status) : 进程是否正常结束
WEXITSTATUS(wstatus) : 获取进程退出状态值, exit 函数的参数
WIFSIGNALED(wstatus) : 表示该子进程是否被信号结束的, 返回真,则表示被信号结束的
WTERMSIG(wstatus) : 返回结束该子进程的那个信号的信号值
WCOREDUMP(wstatus) : 表示该子进程被信号唤醒的
WIFSTOPPED(wstatus) : 表示该子进程是否被信号中止 (stop) 的 , 返回真,则表示是被信号中止的
waitpid函数
函数头文件
#include <sys/types.h>
#include <sys/wait.h>
函数原型
pid_t waitpid(pid_t pid, int *wstatus, int options);
函数参数:
pid : 进程 id
-1 : 可以等待任意子进程
>0 : 等待 id 为 pid 的进程
wstatus : 保存子进程退出状态值变量的指针
options : 选项
0:阻塞选项
WNOHANG: 非阻塞选项
函数返回值
成功 :
0 : 退出进程的 pid
= 0 : 在非阻塞模式下,没有进程退出
失败:
-1 并设置 errno
示例 : 创建一个子进程, 子进程运行后 3s 退出, 父进程等待子进程退出
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<sys/wait.h>
int main(){
pid_t pid;
pid=fork();
if(pid==-1){
perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
else if(pid==0){
printf("the child process pid=%d\n",getpid());
sleep(3);
exit(66);
}
else if(pid>0){
int status=0;
//使用阻塞方式等待进程结束=wait(status)
pid_t cpid=waitpid(-1,&status,0);
if(cpid==-1){
perror("[ERROR] wait():");
exit(EXIT_FAILURE);
}
printf("the child process pid=%d died status=%d\n",cpid,WEXITSTATUS(status));
}
return 0;
}
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<sys/wait.h>
int main(){
pid_t pid;
pid=fork();
if(pid==-1){
perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
else if(pid==0){
printf("the child process pid=%d\n",getpid());
sleep(3);
exit(66);
}
else if(pid>0){
int status=0;
pid_t cpid;
//使用非阻塞方式,等待子进程结束后结束循环
while((cpid=waitpid(-1,&status,WNOHANG))==0){}
if(cpid==-1){
perror("[ERROR] wait():");
exit(EXIT_FAILURE);
}
printf("the child process pid=%d died status=%d\n",cpid,WEXITSTATUS(status));
}
return 0;
}
进程的替换
函数原型
int execl(const char *pathname, const char arg, … / (char *) NULL */);
int execlp(const char *file, const char
arg, … /(char *) NULL */);
int execle(const char *pathname, const char
arg, … /, (char *) NULL, char *const envp[] */);
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
函数参数
path:可执行文件的路径名
file : 可执行文件名,可以通过 path 环境变量指定的路径
arg : 参数列表,以 NULL 结尾
argv[] : 参数数组
envp[] : 环境变量数组
函数返回值:
- 成功 : 0
- 失败 : -1
示例 : 通过 execl 函数族执行 ls -l 命令
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(){
int result;
result=execl("/bin/ls","ls","-l",NULL);
if(result==-1){
perror("[ERROR] execl:");
}
return 0;
}
通过execv执行ls -l命令
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(){
int result;
char *argv[]={"ls","-l",NULL};//参数数组
result=execv("/bin/ls",argv);
if(result==-1){
perror("[ERROR] execl:");
}
return 0;
}
通过execvp执行ls -l命令
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(){
int result;
char *argv[]={"ls","-l",NULL};
result=execvp("ls",argv);//只需要指定 可执行文件名,但是只能通过path环境可以找到的
if(result==-1){
perror("[ERROR] execl:");
}
return 0;
}
进程间的通信
管道分为无名管道与有名管道
无名管道用于父子进程之间通讯
有名管道用于任意进程之间通讯
一、无名管道
无名管道的特点:
- 无名管道属于单向通讯
- 无名管道只能用于 父子进程通讯
- 无名管道发送端叫做写端, 接收端叫做读端
- 无名管道读端与写端抽象成两个文件进行操作,在无名管道创建成功之后,则会返回读端与写端的文件描述符
创建无名管道需要调用pipe()函数
函数头文件
#include <unistd.h>
函数原型
int pipe(int pipefd[2]);
函数参数
pipefd : 用于存储无名管道读端与写端的文件描述符的数组
pipefd[0] : 读端文件描述符
pipefd[1] : 写端文件描述符
函数返回值:
成功 : 0
失败 :-1, 设置 errno
代码示例:创建子进程,父进程通过管道向子进程发送 “Hello,pipe”
#include<stdio.h>
#include<unistd.h>
#include<wait.h>
#include<stdlib.h>
int main(){
pid_t pid;
int ret;
int pipefd[2];
ret=pipe(pipefd);//先创建管道,在fork进程
if(ret==-1){
perror("[ERROR] pipe():");
exit(EXIT_FAILURE);
}
pid=fork();
if(pid==-1){
perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
else if(pid==0){
ssize_t rbytes;
char buffer[64]={0};
close(pipefd[1]);//关闭写端
//当管道为空时,读管道会阻塞读进程
rbytes=read(pipefd[0],buffer,sizeof(buffer));
if(rbytes==-1){
perror("[ERROR] read():");
exit(EXIT_FAILURE);
}
printf("%s\n",buffer);
close(pipefd[0]);
}
else if(pid>0){
close(pipefd[0]);//关闭读端
ssize_t wbytes;
char buffer[64]={"hello pipe"};
wbytes=write(pipefd[1],buffer,sizeof(buffer));
if(wbytes==-1){
perror("[ERROR] write():");
wait(NULL);//等待子进程结束
close(pipefd[1]);
exit(EXIT_FAILURE);
}
close(pipefd[1]);
wait(NULL);
}
return 0;
}
当管道为空时,读管道会阻塞读进程
当管道的写端被关闭了,从管道中读取剩余数据后,read 函数返回 0
在写入管道时,确保不超过 PIPE_BUF 字节的操作是原子的
当写入的数据达到 PIPE_BUF 字节时,write() 会在必要的时候阻塞直到管道中的可用空间足以原子地完成操作
当写入的数据大于 PIPE_BUF 字节时,write() 会尽可能多传输数据以充满这个管道
管道的大小是有限的, 不能让父/子进程同时对管道进行读/写操作
当一个进程试图想一个管道中写入数据但没有任何进程拥有该管道的打开着的读取描述符, 内核向写入进程发送一个 SIGPIPE 信号
二、有名管道
- 有名管道是在文件系统中可见的文件, 但是不占用磁盘空间, 仍然在内存中, 可以通过 mkfifo命令创建有名管道
- 有名管道与无名管道一样,在应用层是基于文件接口进行操作
- 有名管道用于任意进程之间的通讯, 当管道为空时, 读进程会阻塞.
函数mkfifo()
函数头文件
#include <sys/types.h>
#include <sys/stat.h>
函数原型:
int mkfifo(const char *pathname, mode_t mode);
函数参数:
pathname : 有名管道路径名
mode : 有名管道文件访问权限
函数返回值:
成功 : 返回 0
失败 : 返回 -1, 并设置 errno
创建两个没有血缘关系的进程,使用有名管道进行进程间通讯
读程序
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#define FIFO_NAME "./fifo"
int main(){
int fd=open(FIFO_NAME,O_RDONLY);
ssize_t rbytes;
char buffer[64]={0};
if(fd==-1){
perror("[ERROR ]open():");
exit(EXIT_FAILURE);
}
rbytes=read(fd,buffer,sizeof(buffer));
if(rbytes==-1){
perror("[ERROR] read():");
exit(EXIT_FAILURE);
}
printf("%s\n",buffer);
close(fd);
return 0 ;
}
写程序
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include<stdlib.h>
#include<fcntl.h>
#include<unistd.h>
#define FIFO_NAME "./fifo"
int main(){
int fd;
ssize_t wbytes;
char buffer[64]={"hello fifo"};
mkfifo(FIFO_NAME,0644);
fd=open(FIFO_NAME,O_WRONLY);
if(fd==-1){
perror("[ERROR] open():");
exit(EXIT_FAILURE);
}
wbytes=write(fd,buffer,sizeof(buffer));
if(wbytes==-1){
perror("[ERROR] write:");
exit(EXIT_FAILURE);
}
close(fd);
return 0;
}
注意:
如果有名管道的一端以只读方式打开,它会阻塞到另一端以写的方式 (只写,读写)
如果有名管道的一端以只写方式打开,它会阻塞到另一端以读的方式 (只读,读写)
有名管道的优缺点:
优点:可以实现任意进程间通信(包括父子进程),操作起来和文件操作一样
缺点:
- 打开的时候需要读写一起进行否则就会阻塞,管道大小是 4096 个字节
- 半双工的工作模式,如果和多个进程通信则需要创建多个管道
三、信号
在 Linux 系统可以通过
kill -l
命令查看, 常用的信号列举如下:
SIGINT
该信号在用户键入 INTR 字符 (通常是 Ctrl-C) 时发出,终端驱动程序发送此信号并送到前台进>程中的每一个进程。
SIGQUIT
该信号和 SIGINT 类似,但由 QUIT 字符 (通常是 Ctrl-) 来控制。
SIGILL
该信号在一个进程企图执行一条非法指令时 (可执行文件本身出现错误,或者试图执行数据段、堆栈溢出时) 发出。
SIGFPE
该信号在发生致命的算术运算错误时发出。这里不仅包括浮点运算错误,还包括溢出及除数 > 为 0 等其它所有的算术的错误。
SIGKILL
该信号用来立即结束程序的运行,并且不能被阻塞、处理和忽略。
SIGALRM
该信号当一个定时器到时的时候发出。
SIGSTOP
该信号用于暂停一个进程,且不能被阻塞、处理或忽略。
SIGTSTP
该信号用于交互停止进程,用户可键入 SUSP 字符时 (通常是 Ctrl-Z) 发出这个信号。
SIGCHLD
子进程改变状态时,父进程会收到这个信号
SIGABRT
进程异常中止
当由进程来发送信号时, 则可以调用kill()函数与raise ()函数
kill函数
函数头文件
#include <sys/types.h>
#include <signal.h>
函数原型
int kill(pid_t pid, int sig);
函数功能:向指定的进程发送一个信号
函数参数
pid : 进程的 id
sig : 信号的 id
函数返回值:
成功: 返回 0
失败: 返回 -1, 并设置 errno
raise函数
函数头文件
#include <sys/types.h>
#include <signal.h>
函数原型
int raise(int sig);
函数参数:
sig : 信号编号
函数返回值
成功 : 返回 0
失败 : 返回 -1, 并设置 errno
示例:创建一个子进程,子进程通过信号暂停,父进程发送 终止信号
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<wait.h>
int main(){
pid_t pid;
pid=fork();
if(pid==-1){
perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
else if(pid==0){
printf("child process is running pid=%d\n",getpid());
raise(SIGSTOP);//给自己发送阻塞信号
printf("child process is stop pid=%d\n",getpid());
}
else if(pid>0){
int ret;
sleep(1);
ret=kill(pid,SIGKILL);//给子进程发送终止信号
if(ret==0){
printf("parent pid=%d kill child pid=%d\n",getpid(),pid);
}
wait(NULL);
exit(EXIT_SUCCESS);
}
return 0;
}
pause() 函数
功能:pause() 函数会让调用它的进程挂起(暂停执行),直到接收到一个信号。这意味着程序会一直暂停,直到有外部事件(如用户输入或者其他进程发送的信号)唤醒它。它没有参数,也不允许指定暂停的时间长度。
头文件:
#include <unistd.h>
用法: pause(); // 程序暂停,等待信号唤醒。
返回值:接收到信号后,pause() 会返回接收到的信号的编号。如果没有信号到来,它就不会返回,也就是说正常情况下这个函数的调用不会返回到调用点
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<wait.h>
int main(){
pid_t pid;
pid=fork();
if(pid==-1){
perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
else if(pid==0){
printf("child process is running pid=%d\n",getpid());
pause();
printf("child process is stop pid=%d\n",getpid());
}
else if(pid>0){
int ret;
sleep(3);
ret=kill(pid,SIGUSR1);//SIGUSR1也是终止信号
printf("parent pid=%d kill child pid=%d\n",getpid(),pid);
wait(NULL);
exit(EXIT_SUCCESS);
}
return 0;
}
自定义信号处理函数
对于每种信号都有相应的默认处理方式
进程退出:
SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM
进程忽略
SIGCHLD,SIGPWR,SIGURG,SIGWINCH
进程暂停
SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU
通过signal函数设置信号处理方式
函数头文件
#include <signal.h>
函数原型
sighandler_t signal(int signum, sighandler_t handler);
函数功能:
设置信号的处理方式, 如果是自定义处理方式,提供函数地址,注册到内核中
函数参数
signum : 信号编号
函数返回值
成功 : 返回信号处理函数地址
失败 : 返回 SIG_ERR , 并设置 errno
示例:创建一个子进程, 父进程给子进程发送 SIGUSR1 信号,并使用自定义的处理方式
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<wait.h>
#include<string.h>
void do_signal_handler(int sig){
printf("Receive signal %s\n",strsignal(sig));
}
int main(){
pid_t pid;
pid=fork();
__sighandler_t h;
h=signal(SIGUSR1,do_signal_handler);
if(h==SIG_ERR){
perror("[ERROR] signal():");
exit(EXIT_FAILURE);
}
if(pid==-1){
perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
else if(pid==0){
printf("child process is running pid=%d\n",getpid());
pause();
printf("child process is stop pid=%d\n",getpid());
}
else if(pid>0){
int ret;
sleep(3);
ret=kill(pid,SIGUSR1);
printf("parent pid=%d kill child pid=%d\n",getpid(),pid);
wait(NULL);
exit(EXIT_SUCCESS);
}
return 0;
}
SIGALARM信号
函数头文件
#include <unistd.h>
函数原型
unsigned int alarm(unsigned int seconds);
函数功能:设置定时器的秒数
函数参数:
seconds : 定时的时间秒数
函数返回值:
返回上一次进程设置定时器剩余的秒数
要点:
定时器的定时任务由内核完成, alarm 函数值负责设置定时时间, 并告诉内核启动定时器
当定时时间超时后,内核会向进程发出 SIGALRM 信号
#include<stdio.h>
#include<string.h>
#include<unistd.h>
int main(){
int ret;
ret=alarm(5);
printf("ret=%d\n",ret);//返回上一次进程设置定时器剩余的秒数
sleep(2);
ret=alarm(4);
printf("ret=%d\n",ret);
return 0;
}
打印结果:
ret=0
ret=3
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
void do_handler_alarm(int sign){
printf("Receice <%s>\n",strsignal(sign));
}
int main(){
int ret;
ret=alarm(5);
__sighandler_t sigret;
sigret=signal(SIGALRM,do_handler_alarm);
pause();//等待计时器结束
return 0;
}
子进程退出信号SIGCHLD
问题:在使用 wait() 函数时,由于阻塞或者非阻塞都非常消耗资源,并且在阻塞情况下,父进程不能执行其他逻辑
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
#include<wait.h>
#include<stdlib.h>
void do_handler_chld(int sign){
printf("Receice <%s>\n",strsignal(sign));
wait(NULL);//不会阻塞,立即释放子进程的资源
}
int main(){
pid_t pid;
__sighandler_t sigret;
sigret=signal(SIGCHLD,do_handler_chld);
pid=fork();
if(pid==-1){
perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
else if(pid==0){
printf("chlid is running pid=%d\n",getpid());
sleep(2);
exit(EXIT_SUCCESS);
}
else if(pid>0){
while(1){}
}
return 0;
}
默认处理方式是忽略的
四、消息队列
System V IPC 对象共有三种
- 消息队列
- 共享内存
- 信号量
每个 IPC 对象都有一个唯一的 ID, 可以通过ftok()函数生成,ftok函数具体说明如下:
函数头文件
#include <sys/types.h>
#include <sys/ipc.h>
函数原型
key_t ftok(const char *pathname, int proj_id);
函数参数
- pathname : 文件路径名
- proj_id : 8 bit 的 id 整数
函数返回值:
成功: 返回合成的 key(key 由 文件的 inode 节点号 与 proj_id 构成,inode 节点号 :每个存在的文件操作系统都会有唯一的编号, 通过 ls - i 命令查看)
失败 : -1, 并设置 errno
ls - i 命令查看inode节点号
创建
函数头文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
函数原型
int msgget(key_t key, int msgflg);
函数参数
key : 由 ftok 函数合成
msgflg : 消息队列标志
- IPC_CREAT : 创建标志
- IPC_EXCL : 如果消息队列存在,则报错, errno 设置为 EEXIST
权限控制标志
函数返回值
成功 : 返回 消息队列 id
失败 : 返回 -1,并设置 errno
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<stdlib.h>
#include<string.h>
#define PATH "."
#define PRO_ID 88//8bit整数
int main(){
key_t key;
int mid;
key=ftok(PATH,PRO_ID);
if(key==-1){
perror("[ERROR] ftok():");
exit(EXIT_FAILURE);
}
mid=msgget(key,IPC_CREAT|0644);
if(mid==-1){
perror("[ERROR] msgget():");
exit(EXIT_FAILURE);
}
printf("message id=%d\n ",mid);
return 0;
}
消息队列可以通过下面的命令查看
ipcs -q
删除消息队列
ipcrm -q [msqid]
删除
删除消息队列需要调用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 : 删除消息队列属性 ,用此命名时,第三个参数为 NULL
buf : 消息队列属性结构体对象指针
消息队列属性结构体定义如下:
struct ipc_perm {
key_t __key; /* Key supplied to msgget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};
在上一个示例的基础上,加上删除队列的代码
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<stdlib.h>
#include<string.h>
#define PATH "."
#define PRO_ID 88//8bit整数
int main(){
key_t key;
int mid;
int ret;
key=ftok(PATH,PRO_ID);
if(key==-1){
perror("[ERROR] ftok():");
exit(EXIT_FAILURE);
}
mid=msgget(key,IPC_CREAT|0644);
if(mid==-1){
perror("[ERROR] msgget():");
exit(EXIT_FAILURE);
}
printf("message id=%d\n ",mid);
ret=msgctl(mid,IPC_RMID,NULL);
if(ret==-1){
perror("[ERROR] msgctl():");
exit(EXIT_FAILURE);
}
return 0;
}
发送
函数头文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
函数原型
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
函数参数:
1.msqid : 消息队列 ID
2.msgp : 消息结构体指针
3.msgsz : 消息内容的长度
4.msgflg : 消息队列标志,默认可以填 0
IPC_NOWAIT: 可以设置非阻塞
函数返回值
成功 : 返回 0
失败 : -1, 并设置 errno
消息结构定义形式如下:
struct msgbuf {//结构体名称自定义即可
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
};
接收
函数头文件
#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 : 消息类型
msgflg : 消息队列标志,默认可以填 0
IPC_NOWAIT: 可以设置非阻塞
函数返回值:
成功 : 返回实际读取消息内容的字节数
失败 : -1, 并设置 errno
示例: 创建两个没有血缘关系的进程, 使用 消息队列进行通讯
write_message.c
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#define PATHNAME "."
#define PRO_ID 10
#define MSG_TYPE 100
#define MSG_SZ 64
struct msgbuf{
long mtype;
char mtext[MSG_SZ];
};
int main(){
key_t key;
key=ftok(PATHNAME,PRO_ID);
int msg_id=msgget(key,IPC_CREAT|0666);
if(msg_id==-1){
perror("[ERROR] msgget():");
exit(EXIT_FAILURE);
}
struct msgbuf message={MSG_TYPE,"hello msg queue"};
int ret=msgsnd(msg_id,&message,strlen(message.mtext),0);
if(ret==-1){
perror("[ERROR] msgsnd():");
exit(EXIT_FAILURE);
}
printf("message send success!");
return 0;
}
read_message.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<errno.h>
#define PATHNAME "."
#define PRO_ID 10
#define MSG_TYPE 100
#define MSG_SZ 64
struct msgbuf{
long mtype;
char mtext[MSG_SZ];
};
int main(){
struct msgbuf message;
ssize_t rbytes;
key_t key;
key=ftok(PATHNAME,PRO_ID);
int msqid=msgget(key,IPC_CREAT|0666);//接收方也需要mesget一次
if(msqid == -1){ perror("ftok(): "); exit(EXIT_FAILURE); }
msgrcv(msqid,&message,MSG_SZ,MSG_TYPE,0);
printf("Receice message %s\n types=%ld\n",message.mtext,message.mtype);
return 0;
}
五、共享内存
共享内存是将分配的物理空间直接映射到进程的用户虚拟地址空间中, 减少数据在内核空间缓存
共享内存是一种效率较高的进程间通讯的方式
在 Linux 系统中通过 ipcs -m
查看所有的共享内存
创建
函数头文件
#include <sys/ipc.h>
#include <sys/shm.h>
函数原型
int shmget(key_t key, size_t size, int shmflg);
函数功能
创建一个共享内存, 并返回 ID
函数参数
key : 由 ftok() 函数返回
size : 共享内存的大小
shmflg : 共享内存标志
- IPC_CREAT : 创建标志
- IPC_EXCL : 如果消息队列存在,则报错, errno 设置为 EEXIST
权限控制标志
函数返回值
成功 : 返回 共享内存 id
失败 : 返回 -1, 并设置 errno
删除
函数头文件
#include <sys/ipc.h>
#include <sys/shm.h>
函数原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
函数功能
1.共享内存控制函数, 功能由具体的功能命令字决定
2.函数参数
-
shmid : 共享内存 id
-
cmd : 控制命令字
IPC_STAT: 获取 消息队列属性
IPC_SET : 设置消息队列属性
IPC_RMID : 删除消息队列属性 ,用此命名时,第三个参数为 NULL
3.buf : 共享内存属性结构体指针
消息队列属性结构体定义如下:
struct ipc_perm {
key_t __key; /* Key supplied to msgget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};
函数返回值
- 成功 : 返回 0 , 特殊命令字除外
- 失败 : 返回 -1
创建一个共享内存后,输出共享内存 id, 删除共享内存
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#define PATHNAME "."
#define PRO_ID 100
#define SZ 256
int main(){
key_t key;
key=ftok(PATHNAME,PRO_ID);
if(key==-1){
perror("[ERROR] ftok:");
exit(EXIT_FAILURE);
}
int shmid=shmget(key,SZ,IPC_CREAT|0666);
if(shmid==-1){
perror("[ERROR] shmid():");
exit(EXIT_FAILURE);
}
printf("shmid=%d\n",shmid);
int ret=shmctl(shmid,IPC_RMID,NULL);
if(ret==-1){
perror("[ERROR] shmctl():");
exit(EXIT_FAILURE);
}
return 0;
}
映射
函数头文件
#include <sys/types.h>
#include <sys/shm.h>
函数原型:
void *shmat(int shmid, const void *shmaddr, int shmflg);
函数功能:将进程地址空间映射到共享内存上
函数参数:
shmid : 共享内存 id
shmaddr : 指定映射的到进程地址空间的起始地址,指定为 NULL 时, 由系统选择映射的地址
shmflg : 共享内存标志, 一般设置为 0
函数返回值:
成功 : 返回映射到进程地址空间的起始地址
失败 : (void *) -1, 并设置 errno
解除映射
函数头文件
#include <sys/types.h>
#include <sys/shm.h>
函数原型
int shmdt(const void *shmaddr);
函数功能:解除进程地址空间与共享内存的映射
函数参数:
shm_addr: 由shmat返回的地址指针
函数返回值:
成功 : 返回 0
失败 : 返回 -1, 并设置 errno
示例:使用共享内存进行进程间通讯
share_write.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#define PATHNAME "."
#define PRO_ID 101
#define SZ 256
int main(){
key_t key;
int shmid,ret;
void *addr=NULL;
key=ftok(PATHNAME,PRO_ID);
if(key==-1){
perror("[ERROR] ftok():");
exit(EXIT_FAILURE);
}
shmid=shmget(key,SZ,IPC_CREAT|0666);
if(shmid==-1){
perror("[ERROR] shmget():");
exit(EXIT_FAILURE);
}
printf("shmid=%d",shmid);
addr=shmat(shmid,NULL,0);
if(addr==(void *)-1){
perror("[ERROR] shmat():");
exit(EXIT_FAILURE);
}
memset(addr,'A',10);//给addr放10个A
shmdt(addr);
return 0;
}
share_read.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#define PATHNAME "."
#define PRO_ID 101
#define SZ 256
int main(){
key_t key;
int shmid,ret;
char buffer[10]={0};
void *addr=NULL;
key=ftok(PATHNAME,PRO_ID);
if(key==-1){
perror("[ERROR] ftok():");
exit(EXIT_FAILURE);
}
shmid=shmget(key,SZ,IPC_CREAT|0666);
if(shmid==-1){
perror("[ERROR] shmget():");
exit(EXIT_FAILURE);
}
addr=shmat(shmid,NULL,0);
if(addr==(void *)-1){
perror("[ERROR] shmat():");
exit(EXIT_FAILURE);
}
memcpy(buffer,addr,10);
printf("Receive %s\n",buffer);
shmdt(addr);
return 0;
}
进程间同步
信号量
互斥: 同一时刻只有一个进程访问临界资源
同步: 在互斥的基础上增加了进程对临界资源的访问顺序
进程主要的同步与互斥手段是信号量
信号量: 由内核维护的整数, 其值被限制为大于或等于 0
信号可以执行如下操作:
将信号量设置成一个具体的值
在信号量当前值的基础上加上一个数值
在信号量当前值的基础上减上一个数值
等待信号量的值为 0
一般信号量分为 二值信号量 与 计数信号量
- 二值信号量: 一般指的是信号量 的值为 1, 可以理解为只对应一个资源
- 计数信号量: 一般指的是值大于等于 2 , 可以理解为对应多个资源
在 Linux 系统中查询信号量使用
ipcs -s
创建信号量集合
函数头文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
函数原型
int semget(key_t key, int nsems, int semflg);
函数功能:创建一个信号量集合
函数参数
key : 由 ftok() 函数生成
nsems : 信号量的数量
semflg : 信号量集合的标志
- IPC_CREAT : 创建标志
- IPC_EXCL : 与 IPC_CREAT 标志一起使用, 如果信号量集合存在就报错
权限标志
函数返回值
成功 : 返回信号量集合的 id
失败 : -1, 并设置 errno
设置信号量的数量
函数头文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
函数原型
int semctl(int semid, int semnum, int cmd, …);
函数功能:信号集合控制函数,根据 cmd 决定当前函数的功能
函数参数
- semid : 信号量集合的 id
- semnum : 信号量的编号,信号量的编号从 0 开始
- cmd : 命令控制字
SETVAL: 设置信号量的值
GETVAL: 获取信号量的值 - …: 后面是属于可变参参数列表, 根据不同的命令有不同的参数
信号量集合调用semctl函数,设置命令为IPC_RMID(删除信号量)时
注意 : 在使用 IPC_RMID 时,第 三个参数会被忽略
具体使用方法如下:
ret = semctl(semid,IPC_RMID,NULL);
函数返回值
成功 : 根据不同的命令有不同的返回值, 可以查看帮助文档关于 RETURN 的说明
GETNCNT the value of semncnt
GETPID the value of sempid
GETVAL the value of semval
GETZCNT the value of semzcnt.
All other cmd values return 0 on success.
失败 : 返回 -1, 并设置 errno
在使用命令时需要使用 union semun 共用体, 具体定义如下:
union semun {
int val; /* Value for SETVAL */
struct semid_ds buf; /*Buffer for IPC_STAT, IPC_SET */
unsigned short array; /*Array for GETALL, SETALL */
struct seminfo__buf; /*Buffer for IPC_INFO
(Linux-specific) */
};
示例:创建一个信号量集合,集合中包含一个信号量, 并设置信号量的值为 1
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#define SEM_PATHNAME "."
#define SEM_PRO_ID 101
union semun{
int val;
};
int main(){
key_t key=ftok(SEM_PATHNAME,SEM_PRO_ID);
int sem_id,ret;
union semun s;
if(key==-1){
perror("[ERROR] ftok():");
exit(EXIT_FAILURE);
}
sem_id=semget(key,1,IPC_CREAT|0666);
if(sem_id==-1){
perror("[ERROR] semget():");
exit(EXIT_FAILURE);
}
s.val=1;
ret=semctl(sem_id,0,SETVAL,s);//初始化信号量的值,设置 第一个信号量的值为1
if(ret==-1){
perror("[ERROR] semctl():");
exit(EXIT_FAILURE);
}
return 0;
}
操作信号量
函数头文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
函数原型
int semop(int semid, struct sembuf *sops, size_t nsops);
函数功能:信号量操作函数,用于占用信号量、释放信号量、设置信号量等待
函数参数:
- semid : 信号量集合 id
- sops : 信号量操作结构体指针
- nsops : 操作的信号量的数量
函数返回值:
成功 : 返回 0
失败 : 返回 -1, 并设置 errno
struct sembuf 结构体
struct sembuf{
unsigned short sem_num;//信号量编号, 从 0 开始
short sem_op;/*信号量操作
-1 : 占用资源
+1 : 释放资源
0 : 等待资源
*/
short sem_flg;
/*
信号量操作标志
IPC_NOWAIT : 非阻塞,在信号量的值为 0 时, 会立即返回
SEM_UNDO : 在进程终止时, 会自动释放信号量
*/
};
示例:使用信号量解决父子进程对终端的竞争
sem.h
#ifndef __SEM_H__
#define __SEM_H__
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<stdlib.h>
//创建信号量 nsems:信号量的个数,对应信号量的值values
extern int sem_create(int nsems,unsigned short values[]);
//占用信号量 semid信号量集合的id ,对应要占用的信号量 eg:0 ,1,2
extern int sem_p(int semid,int semnum);
//释放信号量 semid信号量集合的id ,对应要占用的信号量 eg:0 ,1,2
extern int sem_v(int semid,int semnum);
//删除信号量集合
extern int sem_del(int semid);
#endif
sem.c
#include "sem.h"
#define SEM_PATHNAME "."
#define SEM_PRO_ID 102
union semun{
unsigned short *array;
};
int sem_create(int nsems,unsigned short values[]){
key_t key;
int sem_id,ret;
union semun s;
key=ftok(SEM_PATHNAME,SEM_PRO_ID);//生成key
if(key==-1){
perror("[ERROR] ftok():");
exit(EXIT_FAILURE);
}
sem_id=semget(key,nsems,IPC_CREAT|0666);//创建信号量集合
if(sem_id==-1){
perror("[ERROR] semget():");
exit(EXIT_FAILURE);
}
s.array=values;
ret=semctl(sem_id,0,SETALL,s);//按位初始化(使用SETALL时,第二位参数设置0)
if(ret==-1){
perror("[ERROR] semctl():");
exit(EXIT_FAILURE);
}
return sem_id;
}
int sem_p(int semid,int semnum){
struct sembuf sops;
sops.sem_num=semnum;
sops.sem_op=-1;
sops.sem_flg=SEM_UNDO;
return semop(semid,&sops,1);
}
int sem_v(int semid,int semnum){
struct sembuf sops;
sops.sem_num=semnum;
sops.sem_op=1;
sops.sem_flg=SEM_UNDO;
return semop(semid,&sops,1);
}
int sem_del(int semid){
return semctl(semid,0,IPC_RMID,NULL);
}
main.c
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include"sem.h"
int main(){
pid_t cpid;
int semid;
unsigned short values[]={1};
semid=sem_create(1,values);
cpid=fork();
if(cpid==-1){
perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
else if(cpid==0){
while(1){
sem_p(semid,0);
printf("----------------\n");
printf("C start\n");
sleep(1);
printf("C End \n");
printf("-----------------\n");
sem_v(semid,0);
}
}
else if(cpid>0){
sleep(1);
while(1){
sem_p(semid,0);
printf("----------------\n");
printf("P start\n");
sleep(1);
printf("P End \n");
printf("-----------------\n");
sem_v(semid,0);
}
}
return 0;
}