C语言基础-进程相关
进程的概念
程序
- 存放在磁盘上的 指令和数据的有序集合(文件)
- 静态的
进程
- 执行一个程序所分配的资源总称
- 进程是程序一次执行过程
- 动态的,包括创建、调度、执行和消亡
进程控制块(pcd)
- 进程标识PID
- 进程用户
- 进程状态、优先级
- 文件描述符表
进程类型
- 交互进程:在shell下启动。以在前台运行,也可以在后台运行
- 批处理进程:和终端无关,被提交到一个作业队列中,以便顺序执行
- 守护进程:和终端无关一直在后台运行
进程状态
- 运行态:
- 正在运行
- 准备运行
- 等待态:进程在等待一个事件的发生或某种资源
- 可终端
- 不可中断
- 停止态:进程被中止,收到信号后可继续运行
- 死亡态:以终止的进程,但pcb没有被释放
查看进程信息
- ps 查看系统进程快照
- top 查看进程动态信息
- /proc 查看进程详细信息
修改进程优先级
- nice 按用户指定的优先级运行进程
- nice -n num ./test
- num范围是 -20~19 越低优先级越高
- 普通用户是没办法指定负数的优先级
- renice 改变正在运行进程的优先级
- renice -n num pid
- 普通用户是没办法提高该优先级,只能进行降低
进程相关
- jobs 查看后台进程
- bg 将挂起的进程在后台运行
- bg id
- id是jobs的后台进程编号
- fg 吧后台运行的程序放到前台来运行
- fg id
- id 是jobs的后台进程编号
- ctrl + z 将前台进程放到后台挂起
C与进程相关函数
父子进程
- 子进程继承了父进程的内容
- 父子进程有独立的地址空间,互不影响
- 若父进程先结束
- 子进程成为孤儿进程,被init进程收养
- 子进程变成后台进程
- 若子进程先结束
- 父进程如果没有及时回收,子进程变成僵尸进程
进程创建 - fork
#include <unistd.h> pid_t fork(void); // 创建新的进程,失败返回-1 // 成功时父进程返回子进程的进程号,子进程返回0
创建完子进程后,从fork后下一条语句开始执行;
父子进程的执行顺序不定
父子进程可以多次调用fork
获取当前进程 - getpid
#include <unistd.h> pid_t getpid(void);
demo:创建子进程
#include <stdio.h> #include <unistd.h> int main(int argc, char *argv[]) { pid_t pid; if ((pid = fork()) < 0) { perror("fork"); return -1; } else if (pid == 0) { printf("child process: my pid is :%d\n",getpid()); }else{ printf("parent process :%d\n",getpid()); } } /* 输出: parent process :7124 child process: my pid is :7125 */
结束进程
#include <stdlib.h>
void exit(int status);
#include <unistd.h>
void _exit(intstatus);
// 引入的头文件不同
//*** exit结束进程时会刷新(流)的缓冲区
// _exit结束时不会刷新缓冲区 可能会造成流文件丢失
exit(0) // 退出当前程序
进程资源回收
- 子进程结束时由父进程回收
- 孤儿进程由init进程回收
- 若没有及时回收会出现僵尸进程
#include <unistd.h>
pid_t wait(int *status);
// status指定保存子进程返回值和结束方式的地址
// status为NULL表示直接释放子进程PCB,不接受返回值
// 成功时返回回收的子进程号
// 失败是返回EOF
// 若子进程没有结束,父进程会一直阻塞
// 若有多个子进程,那个先结束就先回收
Demo:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
pid_t pid;
int status = 0;
if ((pid = fork()) < 0) {
perror("fork");
return -1;
} else if (pid == 0) {
printf("child process: my pid is :%d\n", getpid());
sleep(1);
exit(2);
} else {
wait(&status);
printf("%x\n", status);
printf("parent process :%d\n", getpid());
}
}
进程回收的另一种方式
#include <unistd.h>
pid_t waitpid(pid_t pid,int * status,int option);
// pid可用于指定回收那个子进程或任意子进程
// pid= -1 代表当前进程的任意子进程
// status指定用于保存子进程返回值和结束方式的地址
// option指定回收方式,0或WNOHANG
// 0表示阻塞一直等到进程结束
// WNOHANG表示非阻塞
// 成功时返回子进程的pid或0 0表示当前子进程还未结束
// 失败时返回EOF
进程返回值和结束方式
- 子进程通过exit、_exit/return返回某个值(0~255)
- 父进程调用wait(&status)回收
WIFEXITED(status) // 判断子进程是否正常结束 0为false 1为true
WEXITSTATUS(status) // 获取子进程返回值
WIFSIGNALED(status) // 判断子进程是否被信号结束
WTERMSIG(status) // 获取结束子进程的信号类型
守护进程
- 守护进程(Deamon)是linux三种进程类型之一
- 通常是在系统启动时运行,系统关闭时结束
- linux系统中大量使用,很多服务程序以守护进程方式运行
会话、控制终端
- linux会话(session)、进程组的方式管理进程
- 每个进程属于一个进程组
- 会话是一个或多个进程组的集合。通常用户打开一个终端,系统会创建一个会话。所有通过该终端运行的进程都属于这个会话
- 终端关闭时,所有相关进程会被结束
守护进程特点
- 始终在后台运行
- 独立于任何终端
- 周期性的执行某种任务或等待处理特定事件
守护进程创建
- 创建子进程,父进程退出--子进程变为孤儿进程,被init进程收养;子进程在后台运行(还是会依附于当前终端)
if (fork() > 0) { exit(0); }
- 子进程创建新会话--子进程称为新的会话组长;脱离原先的终端
- 更改当前工作目录--守护进程一直在后台运行,其工作目录不能被卸载一般指向根目录(/)或(tmp)
chdir("/"); // 普通用户指向这个目录可读 chdir("/tmp"); // 所有用户都是最高权限
- 重设文件权限掩码--文件权限掩码设置为0;只影响当前进程
#include <sys/stat.h> if (umask(0) < 0) { exit(-1); }
- 关闭父进程打开的文件描述符--已脱离终端 stdin/stdout/stderr 无法再使用
#include <zconf.h> for (int i = 0; i < getdtablesize(); ++i) { close(i); }
Demo:每隔1秒写入时间到time.log
#include <sys/stat.h> #include <zconf.h> int main(int argc, char *argv[]) { setsid(); umask(0); chdir("/tmp"); for (int i = 0; i < getdtablesize(); ++i) { close(i); } // ... 打开文件 // ... 死循环写入日志 }
exec函数族
- 进程调用exec函数族执行某个程序
- 进程当前内容被指定的程序替换
- 实现让父子进程执行不同程序
- 父进程创建 子进程
- 子进程调用exec函数族
- 父进程不受影响
通过shell命令去执行其他程序
#include <unistd.h> int execl(const char *path,const char*arg,...); / int execlp(const char *file,const char *arg,...); // 成功执行指定的程序 // 失败返回EOF // path找到要执行程序的名称(要包含路径) // arg...传递给执行程序的参数表 // file 执行程序的名称,在$PATH中查找
Demo:
if (execl("/bin/ls", "ls", "-a","/etc") < 0) { perror("excel"); }
if (execlp("ls", "ls", "-a","/etc") < 0) { perror("excel"); }
通过shell命令去执行其他程序 参数为数组
#include <unistd.h> int execv(const char * path,char * const argv[]); int execvp(const char * file,char * const argv[]); // 成功时执行指定的成 // 失败返回EOF // arg... 封装成指针数组 比上面exec更加灵活
char *arg[] = {"ls", "-a", "/etc",NULL}; if (execv("/bin/ls", arg) < 0) { perror("excel"); }
char *arg[] = {"ls", "-a", "/etc",NULL}; if (execvp("ls", arg) < 0) { perror("excel"); }
通过shell命令去执行其他程序 字符串
#include <stdlib.h> int system(const char * command); // 会自动创建一个子进程去执行command // 成功返回command指令的返回值 // 失败返回EOF
进程间通讯介绍
- 早期UNIX进程间通信方式
- 无名管道(pipe)
- 有名管道(fifo)
- 信号(signal)
- System V IPC
- 共享内存(share memory)
- 消息队列(message queue)
- 信号灯集(semaphore set)
无名管道-pip
无名管道特性
无名管道使用范围,只能用于有亲缘关系的进程之间-因为无名管道没有实际的文件所对应,只能通过集成方式打开进行通讯
无名管道是一个单工的,只能单方向传递数据
无名管道的数据是存在在内存中,读取后则无
无名管道创建
#include <unistd.h> int pipe(int pfd[2]); // 成功返回0 // 失败返回EOF // pfd包含两个元素的整型数组,用来保存文件描述符 // pfd[0]用于读管道 // pfd[1]用于写管道
Demo:子进程1和子进程2分别往管道中写入字符串;父进程读取
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <string.h> int main(int argc, char *argv[]) { // 保存fork的返回值 pid_t pid1, pid2; char buf[32]; // 用于保存无名管道 int pfd[2]; // 创建无名进程 if (pipe(pfd) < 0) { perror("pipe"); return 0; } // 创建子进程 if ((pid1 = fork()) < 0) { perror("pip1"); return 0; } else if (pid1 == 0) { // pid1 == 0 子进程1 执行区 strcpy(buf, "process1"); write(pfd[1], buf, 32); } else { // 父进程执行区 if ((pid2 = fork()) < 0) { perror("pip2"); return 0; } else if (pid2 == 0) { // pid2 == 0 子进程2 执行区 sleep(1); strcpy(buf, "process2"); write(pfd[1], buf, 32); } else { // 父进程执行区 // 等待回收任意子进程 wait(NULL); read(pfd[0], buf, 32); printf("%s\n", buf); // 等待回收任意子进程 wait(NULL); read(pfd[0], buf, 32); printf("%s\n", buf); } } return 0; }
读无名管道
写端存在
- 有数据
read函数返回实际读取的字节数
- 无数据
进程读阻塞
写段不存在
- 有数据
read函数返回实际读取的字节数
- 无数据
read函数立即返回0
读端存在
- 有空间
write返回实际写入的字节数
- 无空间
进程写阻塞
读端不存在
- 有空间
系统异常结束(管道断裂)
- 无空间
系统异常结束(管道断裂)
有名管道-fifo
有名管道特点
- 对应管道文件,可用于任意进程之间的通讯
- 打开管道时可指定读写方式
- 通过文件IO操作,内容存放在内存中
只有读端和写端都存在的情况下才能正常运行否则会单方面阻塞
有名管道的创建-mkfifo
#include <unistd.h> #include <fcntl.h> int mkfifo(const char* path,mode_t mode); // 成功时返回0 // 失败时返回EOF // path 创建的管道文件路径,如果没有就是默认在当前路径下 // mode 管道文件的权限,如0666
Demo:
进程A:循环从键盘输入并写入有名管道myfifo,输入quit时推出
进程B:循环统计进程A每次写入myfifo的字符串长度
create.c
#include <stdio.h> #include <sys/stat.h> // create fifo int main(void ){ // 创建管道 if (mkfifo("fifo",0666)<0){ perror("mkfifo"); return 0; } }
write.c
#include <stdio.h> #include <string.h> #include <unistd.h> #include <fcntl.h> int main(int argc, char *argv[]) { char buf[32]; int pfd; // 以只写的方式打开管道 if ((pfd = open("fifo", O_WRONLY)) < 0) { perror("mkfifo"); return 0; } while (1) { // 循环写入 fgets(buf, 32, stdin); // 如果输入quit则跳出 if (strcmp(buf, "quit\n") == 0) { break; } write(pfd, buf, 32); } // 关闭打开的管道 close(pfd); return 0; }
read.c
#include <stdio.h> #include <string.h> #include <unistd.h> #include <fcntl.h> // read fifo int main() { char buf[32]; int pfd; // 以只读方式打开管道 if ((pfd = open("fifo", O_RDONLY)) < 0) { perror("mkfifo"); return 0; } // 循环读 while (read(pfd, buf, 32) > 0) { printf("the length of string is %d\n", strlen(buf)); } // 如果等于0说明该管道已经关闭了 close(pfd); return 0; }
信号机制
- 信号是在软件层面上对中断机制的一种模拟,是一种异步通讯的方式
- Linux内核通过信号通知用户进程,不同的信号类型代表不同的事件
- Linux对早期的unix信号机制进行了扩展
- 进程对信号有不同的响应方式
- 缺省方式
- 忽略信号
- 捕捉信号
常用信号
信号名 | 含义 | 默认操作 |
SIGHUP | 该信号在用户终端关闭时产生,通常是发给该终端关联的会话内的所有进程 | 终止 |
SIGINT | 该信号在用户键入INTR字符(Ctrl-c)时产生,内核发送此信号送到当前终端的所欲前台进程 | 终止 |
SIGQUIT | 该信号和SIGINT类似,但由QUIT字符(通常是Ctrl-\)来产生 | 终止 |
SIGILL | 该信号在一个进程企图执行一条非法执行时产生 | 终止 |
SIGSEV | 该信号在非法访问内存时产生,如野指针,缓冲区溢出 | 终止 |
SIGPIPE | 当进程往一个没有读端的管道中写入时产生,代表'管道断裂' | 终止 |
信号名 | 含义 | 默认操作 |
SIGKILL | 该信号用来结束进程,并且不能被捕捉和胡忽略 | 终止 |
SIGTOP | 该信号用于暂停,并且不能被捕捉和忽略 | 暂停进程 |
SIGTSTP | 该信号用户暂停进程,用户可以键入SUSP(ctrl-z) | 暂停进程 |
SIGCONT | 该信号让进程进入运行态 | 继续运行 |
SIGALRM | 该信号用于通知进程定时器时间已到 | 终止 |
SIGUSR1/2 | 该信号保留给用户程序使用 | 终止 |
Signal.h定义信号的宏
#define SIGHUP 1 /* hangup */ #define SIGINT 2 /* interrupt */ #define SIGQUIT 3 /* quit */ #define SIGILL 4 /* illegal instruction (not reset when caught) */ #define SIGTRAP 5 /* trace trap (not reset when caught) */ #define SIGABRT 6 /* abort() */ #if (defined(_POSIX_C_SOURCE) && !defined(_DARWIN_C_SOURCE)) #define SIGPOLL 7 /* pollable event ([XSR] generated, not supported) */ #else /* (!_POSIX_C_SOURCE || _DARWIN_C_SOURCE) */ #define SIGIOT SIGABRT /* compatibility */ #define SIGEMT 7 /* EMT instruction */ #endif /* (!_POSIX_C_SOURCE || _DARWIN_C_SOURCE) */ #define SIGFPE 8 /* floating point exception */ #define SIGKILL 9 /* kill (cannot be caught or ignored) */ #define SIGBUS 10 /* bus error */ #define SIGSEGV 11 /* segmentation violation */ #define SIGSYS 12 /* bad argument to system call */ #define SIGPIPE 13 /* write on a pipe with no one to read it */ #define SIGALRM 14 /* alarm clock */ #define SIGTERM 15 /* software termination signal from kill */ #define SIGURG 16 /* urgent condition on IO channel */ #define SIGSTOP 17 /* sendable stop signal not from tty */ #define SIGTSTP 18 /* stop signal from tty */ #define SIGCONT 19 /* continue a stopped process */ #define SIGCHLD 20 /* to parent on child stop or exit */ #define SIGTTIN 21 /* to readers pgrp upon background tty read */ #define SIGTTOU 22 /* like TTIN for output if (tp->t_local<OSTOP) */ #if (!defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)) #define SIGIO 23 /* input/output possible signal */ #endif #define SIGXCPU 24 /* exceeded CPU time limit */ #define SIGXFSZ 25 /* exceeded file size limit */ #define SIGVTALRM 26 /* virtual time alarm */ #define SIGPROF 27 /* profiling time alarm */ #if (!defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)) #define SIGWINCH 28 /* window size changes */ #define SIGINFO 29 /* information request */ #endif #define SIGUSR1 30 /* user defined signal 1 */ #define SIGUSR2 31 /* user defined signal 2 */
信号相关命令
$ kill [-signal] pid
- 默认发送SIGTERM
- -sig可指定信号
- pid指定发送对象
$kill -9 1111 // 向pid为1111的进程发送关闭信号 $kill -9 -1111 // 向1111进程组发送关闭信号 $kill -9 -1 // -1表示除了init和当前进程之外所有进程发信号
$ kill [-u user | prog]
- prog 执行进程名
- user 指定用户名
信号发送-kill/raise
#include <unistd.h> #include <signal.h> int kill(pid_t pid,int sig); int raise(int sig); // 成功时返回0 // 失败时返回EOF // pid接受进程的进程号;0代表同组进程;-1代表所有进程 // sig信号类型
信号相关函数-alarm/pause
设置定时器和暂停等待定时器
Ps:alarm经常用于实现超时检测
#include <unistd.h> #include <signal.h> int alarm(unsigned int seconds); // 成功时返回上一个定时器的剩余时间 // 失败返回EOF // seconds 定时器的时间 如果是0则为取消该定时器 // 一个进程中只能设定一个定时间,时间到时产生SIGALRM int pause(void); // 进程一直阻塞,知道被信号中断 // 被信号中断返回-1 // errno为EINTR
Demo:
#include <stdio.h> #include <unistd.h> int main(int argc, char *argv[]) { // 设置定时器 alarm(3); // 阻塞进程 pause(); // 直到定时器结束产生 SIGALRM pause被信号中断 实际上程序已经结束 printf("pause over \n"); // 这句话无法打印 return 0; }
设置信号响应方式-signal
#include <unistd.h> #include <signal.h> void(*signal(int signo,void (*handler)(int)))(int); // signo 要设置的信号类型 // (*handler)(int) 函数指针 传递函数类型;SIG_DFL代表缺省方式(系统的默认处理);SIG_IGN代表忽略该信号;如果传入该函数就是捕捉信号 // 该返回值也是一个函数指针 // 成功时返回原先的信号处理函数 // 失败时返回SIG_ERR
Demo:
#include <stdio.h> #include <unistd.h> #include <sys/param.h> // 信号处理函数 void handle(int signo) { if (signo == SIGINT) { puts("CATCH SIGINT"); } if (signo == SIGQUIT) { puts("CATCH SIGQUIT"); } } int main(int argc, char *argv[]) { // 设置信号响应方式 signal(SIGINT, handle); // `ctrl - c` signal(SIGQUIT, handle); // `ctrl - \` while (1) pause(); return 0; }
System V IPC
- IPC对象包括:对象内存、消息对列、信号灯集
- 每一个IPC对象有唯一的ID
- IPC对象创建后一直存在,直到被显示地删除
- 每一个IPC对象有一个关联的KEY
- ipcs/ipcrm shell命令
IPCS、IPCRM
$ipcs
T ID KEY MODE OWNER GROUP
Message Queues:
T ID KEY MODE OWNER GROUP
Shared Memory:
m 65536 0x5104e14e --rw------- songzhibin staff
T ID KEY MODE OWNER GROUP
Semaphores:
s 65536 0x0b046024 --ra-ra-ra- root wheel
s 65537 0x0b04e111 --ra-ra-ra- root staff
s 65538 0x25048769 --ra-ra-ra- root staff
s 65539 0x5104e149 --ra------- root staff
s 65540 0x5104e14f --ra------- root staff
分别为消息队列、对象内存、信号等集
每一个对象都有唯一的ID
每一个IPC对象都有一个与之对应的KEY值 KEY值0代表是一个私有的,其他进程间不能通过KEY来访问
$ipcrm [-M | -Q | -S key] [-m | -q | -s id]
// 可以通过key或者id来删除 IPC
IPC KEY值生成-ftok
#include <sys/types.h> #include <sys/ipc.h> key_t ftok(const char*path,int proj_id); // 成功返回一个合法的key值 // 失败返回-1 // path 存在且可以访问的文件路径 // proj_id 用于生成key的数字,不能为0
Ps:每个进程低啊用ftok的方式应该相同:path和proj_id参数值应该相同,否则找不到对应的IPC
Demo:
#include <stdio.h> #include <sys/ipc.h> int main(int argc, char *argv[]) { key_t key; // key值生成 // 一般 proj_id 传入字符常量 if ((key = ftok(".", 'a')) == -1) { perror("ftok"); return 0; } }
共享内存
- 共享内存是一种最为高效的进程间通讯方式,进程可以直接读写内存,而不需要任何数据拷贝
- 共享内存在内核空间创建,可以进程映射到用户访问空间,是用灵活
- 由于多个进程可以同时访问共享内存,因此需要同步和互斥机制配合使用
注意事项:
- 每块共享内存的大小有限制
- $ipcs -l
- 修改 cat /proc/sys/kernel/shmmax
- 共享内存删除的时间点
- shmctl(shmid,IPC_RMID,NULL)添加删除标记
- nattach变为0时真正删除 (nattach是映射的数目)
共享内存使用步骤
- 创建/打开共享内存
- 映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问
- 读写共享内存
- 撤销共享内存映射
- 删除共享内存对象
共享内存的创建/打开-shmget
#include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key,int size,int shmflg); // 成功时返回共享内存的id // 失败时返回EOF // key 和共享内存关联的key, IPC_PREIVATE或ftok生成 IPC_PREIVATE是零代表私有共享内存 // size 共享内存的大小 // shmflg 共享内存的标志位 IPC_CREAT|0666
Demo:创建一个私有的共享内存,大小为512字节,权限为0666
#include <stdio.h> #include <sys/ipc.h> #include <sys/shm.h> int main(int argc, char *argv[]) { int shmid; // 如果私有的一定是新建的 不用指定 IPC_CREAT|0666 if ((shmid = shmget(IPC_PRIVATE, 512, 0666)) < 0) { perror("shmget"); return 0; } }
Demo:创建/打开一个和key关联的共享内存,大小为1024字节,权限为0666
#include <stdio.h> #include <sys/ipc.h> #include <sys/shm.h> key_t keyid; int shmid; int main(int argc, char *argv[]) { // 生成共享内存的key if ((keyid = ftok(".", 'a')) == -1) { perror("ftok"); return 0; } // 创建/打开共享内存 // 如果共享内存不存在则创建,如果存在则打开 if ((shmid = shmget(keyid, 1024, IPC_CREAT | 0666))<0) { perror("shmget"); return 0; } }
共享内存映射-shmat
#include <sys/ipc.h> #include <sys/shm.h> void *shmat(int shmid,const void *shmaddr,int shmflg); // 成功时返回映射后的地址 // 失败时返回(void *) -1 // shmid 要映射的共享内存的id // shmaddr 映射后的地址,如果传NULL表示系统自动映射 // shmflg 标志位 0表示可读可写;SHM_RDONLY表示只读
Demo:通过指针访问共享内存,指针类型取决于共享内存中存放的数据类型 下面以字符串为例
#include <stdio.h> #include <sys/ipc.h> #include <sys/shm.h> key_t keyid; int shmid; char *addr; int main(int argc, char *argv[]) { // 生成共享内存的key if ((keyid = ftok(".", 'a')) == -1) { perror("ftok"); return 0; } // 创建/打开共享内存 // 如果共享内存不存在则创建,如果存在则打开 if ((shmid = shmget(keyid, 1024, IPC_CREAT | 0666)) < 0) { perror("shmget"); return 0; } // 映射 // NULL表示由系统分配地址, 0表示可读可写 if ((addr = (char *) shmat(shmid, NULL, 0)) == (void *) -1) { perror("shmat"); return 0; } // 获取标准输入 放到共享内存地址 // 1024则为共享内存创建时的指定大小 防止越界 fgets(addr, 1024, stdin); }
共享内存撤销映射-shmdt
Ps:不使用共享内存时应该撤销映射
进程结束时自动撤销
#include <sys/ipc.h> #include <sys/shm.h> int shmdt(void *shmaddr); // 成功时返回0 // 失败时返回EOF // shmaddr 映射后的首地址
共享内存控制-shmctl
#include <sys/ipc.h> #include <sys/shm.h> int shmctl(int shmid,int cmd,struct shmid_ds *buf); // 成功时返回0 // 失败时返回EOF // shmid 要操作的共享内存id // cmd 要执行的操作 // buf 保存或设置共享内存属性的地址 // cmd宏定义 // IPC_STAT 获取当前共享内存的属性 保存到 buf的结构体指针 // IPC-SET 设置当前共享内存的属性 将buf的属性设置到贡献内存中 // IPC_RMID 删除该共享内存的id, buf参数可以省略为NULL
消息队列
- 消息队列是System V IPC对象的一种
- 消息队列由消息队列ID作为唯一标识
- 消息队列就是一个消息的列表,用户可以在消息队列中添加消息。读取消息等
- 消息队列可以按照类型来发送/接受消息
消息队列在删除时直接删除发送接收会立即出错
消息队列的使用步骤
- 打开/创建消息队列-msgget
- 向消息队列发送消-msgsnd
- 从消息队列接收消息-msgrcv
- 控制消息队列-msgctl
打开/创建消息队列
#include <sys/ipc.h> #include <sys/msg.h> int msgget(key_t,int msgflg); // 成功时返回消息队列的id // 失败时返回EOF // key和消息队列关联的key IPC_PRIVATE或ftok // msgflg 标志位 IPC_CREAT|0666
Demo:创建/打开消息队列
#include <stdio.h> #include <sys/ipc.h> #include <sys/msg.h> int main(int argc, char *argv[]) { int msgid; key_t key; // 生成key if ((key = ftok(".", 'a')) == -1) { perror("ftok"); return 0; } // 打开或创建消息队列 if ((msgid = msgget(key, IPC_CREAT | 0666)) < 0) { perror("msgget"); return 0; } }
消息格式
- 通讯双方首先定义好统一的消息格式
- 用户根据应用需求定义结构体类型
- 首成员类型为long,代表消息类型(正整数)
- 其他成员都属于消息正文
Demo:
typedef struct { // 消息的类型 *第一个成员必须是 long long mType; char mContent[64]; // 消息的正文 } Msg;
消息发送-msgsnd
#include <sys/ipc.h> #include <sys/msg.h> int msgsnd(int msgin,const void *msgp,size_t size,int msgflg); // 成功时返回0 // 失败时返回-1 // msgid 消息队列id // msgp 消息缓冲区的地址 // size 发送消息的长度(正文的长度) // msgflg 标志位 0 或者 IPC_NOWALT , 0表示消息发送成功再返回,IPC_NOWALT不等待结果直接返回
Demo:
#include <stdio.h> #include <sys/ipc.h> #include <sys/msg.h> // 发送消息的格式 typedef struct { // 消息的类型 *第一个成员必须是 long long mType; char mContent[64]; // 消息的正文 } Msg; // 定义宏 消息正文的长度 #define ContentSize (sizeof(Msg)-sizeof(long)) int main(int argc, char *argv[]) { int msgid; key_t key; // 消息buf Msg buf; // 生成key if ((key = ftok(".", 'a')) == -1) { perror("ftok"); return 0; } // 打开或创建消息队列 if ((msgid = msgget(key, IPC_CREAT | 0666)) < 0) { perror("msgget"); return 0; } // 消息发送 buf.mType = 100; // 从用户输入获取 正文 信息 fgets(buf.mContent, 64, stdin); // 发送信息 msgsnd(msgid, &buf, ContentSize, 0); }
消息接受-msgrcv
#include <sys/ipc.h> #include <sys/msg.h> int msgrcv(int msgid,void *msgp,size_t size,long msgtype,int msgflg); // 成功时返回接受到的消息长度 // 失败时返回-1 // msgid 消息队列id // msgp 消息缓冲区的地址 // size 指定接受的消息长度 超过指定消息长度会截断 // msgtype 指定接受的消息类型 如果指定为0 接受最早的一条消息 不是按照类型接受 ,如果指定负数则是优先级接受 // msgflg 标志位 0 或 IPC_NOWAIT 如果0表示会等待消息 IPC_NOWAIT不阻塞直接返回
Demo:
#include <stdio.h> #include <sys/ipc.h> #include <sys/msg.h> // 发送消息的格式 typedef struct { // 消息的类型 *第一个成员必须是 long long mType; char mContent[64]; // 消息的正文 } Msg; // 定义宏 消息正文的长度 #define ContentSize (sizeof(Msg)-sizeof(long)) // receive int main(int argc, char *argv[]) { int msgid; key_t key; // 表示只接受100的消息 long mtype = 100; // 消息buf Msg buf; // 生成key if ((key = ftok(".", 'a')) == -1) { perror("ftok"); return 0; } // 打开或创建消息队列 if ((msgid = msgget(key, IPC_CREAT | 0666)) < 0) { perror("msgget"); return 0; } // 消息接受 if ((msgrcv(msgid, &buf, ContentSize, mtype, 0)) < 0) { perror("msgrcv"); return 0; } printf("%s", buf.mContent); }
控制消息队列-msgctl
#include <sys/ipc.h> #include <sys/msg.h> int msgctl(int msgid,int cmd,struct msqid_ds *buf) // 成功时返回0 // 失败时返回-1 // msgid 消息队列id // cmd 要执行的操作 IPC_STAT(获取属性)/IPC_SET(设置属性)/IPC_RMID(删除) , 前两个依赖第三个buf 最后一个不依赖 可传NULL // buf 存放队列属性的地址
信号灯
- 信号灯也叫信号量,用于进程、线程同步或互斥的机制
- 信号灯的类型
- Posix 无名信号灯
- Posix 有名信号灯
- System V 信号灯
- 信号灯等含义
- 计数信号灯
- System V 信号灯
- System V 信号灯是一个或多个计数信号灯的集合
- 可以同时操作集合中的多个信号灯
- 申请多个资源时避免死锁
System V 信号灯使用步骤
- 打开/创建信号灯-semget
- 信号灯初始化-stmctl
- P/V操作-semop
- 删除信号灯-stmctl
打开/创建信号灯-semget
int semget(key_t key,int nsems,int semflg); // 成功返回信号灯集合的id // 失败时返回-1 // key 和消息对列关联的key IPC_PRIVATE 或 ftok // nsems 集合中包含的计数信号灯个数 // semflg 标记位 IPC_CREAT|0666 IPC_EXCL (IPC_EXCL如果这个信号灯存在则报错 用于检查信号灯是否存在)只能初始化一次 所以需要判断是否已经存在
信号灯初始化-semctl
#include <sys/ipc.h> #include <sys/sem.h> int semctl(int semid,int semnum,int cmd,...); // 成功返回0 // 失败返回EOF // semid 要操作的信号灯集id // semnum 要操作的信号灯集合中的信号灯编号 从0开始到num-1 // cmd 执行的操作 SETVAL(设置信号灯的值) IPC_RMID(删除整个信号灯集合) // ... union semun 取决于cmd SETVAL时需要用到
Demo:假设信号灯集合中包含两个信号灯;第一个初始化为2,第一个初始化为0
#include <stdio.h> #include <sys/ipc.h> #include <sys/sem.h> // 声明共用体类型 union semun mun; int main(int argc, char *argv[]) { // 信号灯id int semid; key_t key; // 生成key if ((key = ftok(".", 'a')) == -1) { perror("ftok"); return 0; } // 负责创建的进程负责初始化 // 创建 有2个信号灯的信号灯集合 if ((semid = semget(key, 2, IPC_CREAT | 0666 | IPC_EXCL)) < 0) { perror("semget"); return 0; } // 指定类型1 mun.val = 2; // 初始化信号灯集合中的0号信号灯的val为2 if (semctl(semid, 0, SETVAL, mun) < 0) { perror("semctl"); return 0; } // 指定类型2 mun.val = 0; // 初始化信号灯集合中的1号的信号灯的val为0 if (semctl(semid, 1, SETVAL, mun) < 0) { perror("semctl"); return 0; } }
信号灯的P/V操作-semop
#include <sys/ipc.h> #include <sys/sem.h> int semop(int semid,struct sembuf * sops,unsigned nsops); // 成功返回0 // 失败返回-1 // semid 要操作信号灯集id // sops 描述对信号灯操作的结构体(数组) 系统中已经定义好的sembuf结构体 // nsops要操作的信号灯的个数 struct sembuf { unsigned short sem_num; // 指定操作信号灯的编号 short sem_op; // 负数:P操作 整数:V操作 short sem_flg; // 0/IPC_NOWAIT 0表示阻塞等待,IPC_NOWAIT立刻返回,不阻塞 };