进程编程及守护进程
进程控制块
进程控制块(pcb)
进程标识PID
进程用户
进程状态、优先级
文件描述符表
寄存器 ----- pc:program counter(存放进程下一条指令的地址)
栈:局部变量在栈上创建,在栈上被释放
进程类型:
交互进程:在shell下启动,可以在前台运行,也可以在后台运行
批处理进程:和在终端无关,被提交到一个作业队列中以便顺序执行
守护进程:和终端无关,一直在后台运行
进程状态
运行态:进程正在运行,或者准备运行
等待态:进程在等待一个时间发生或某种系统资源
可中断
不可中断
停止态:进程被中止,收到信号后可继续运行
死亡态:已经终止的进程,但pcb没有被释放
查看进程信息
ps 查看系统进程快照 ps -ef(显示当前所有的进程信息) ps -ef|grep test(根据关键字查看进程)
top 查看进程动态信息
/proc 查看进程的详细信息
进程相关命令
nice 按用户指定的优先级运行进程
renice 改变正在运行进程的优先级
jobs 查看后台进程
bg 将挂起的进程在后台运行
fg 把后台运行的进程放到前台运行
进程创建 - fork
#include<unisted.h>
pid_t fork(void);
创建新的进程,失败时返回-1
成功时父进程返回子进程的进程号,子进程返回0
通过fork的返回值区分父进程和子进程
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 :my pid is %d\n",getpid());
}
父子进程
子进程继承了父进程的内容
父子进程有独立的地址空间,互不影响
若父进程先结束
子进程成为孤儿进程,被init进程收养
子进程变成后台进程
若子进程先结束
父进程如果没有及时回收,子进程变成僵尸进程
进程结束 -exit/_exit
#include<stdlib.h>
#include<unisted.h>
void exit(int status);
void _exit(int status);
结束当前的进程并将status返回
exit结束进程时会刷新(流)缓冲区
/*进程结束-exit-示例1*/
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
printf("this process will exit");//因为printf函数是一个标准的行缓冲,由于没有换行符(\n),只将字符放入标准输出流的缓冲上,不会显示到终端
exit(0);//结束进程,并且刷新流,=此时会将标准输出流上的缓冲输出到终端
printf("never be displayed");//因为进程已经结束,所以会打印该字符串
}
./a.out
this process will be exit
/*进程结束 - exit - 示例2*/
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
printf("using exit...\n");//因为有换行符,该字符被显示到终端上面
printf("this is the end");//因为没有换行符,该字符只会被放入标准输出流的行缓冲中
exit(0);//退出该进程,并且刷新流缓冲区,缓冲区内的字符会被显示到终端上面
}
./a.out
suing exit...
this is the end
如果将exit函数换为_exit函数,则只会结束进程,不会刷新流缓冲区
进程 - exec函数族
进程调用exec函数族执行某个程序
进程当前内容被指定的程序替换
实现让父子进程执行不同的程序
父进程创建子进程
子进程调用exec函数族
父进程不受影响
进程 - execl/execlp
#include<unistd.h>
int execl(const char *path,const char *arg, ...);
int execlp(const char *file,const char *arg, ...);
成功时执行制定的程序;失败时返回EOF
path 执行的程序名称,包含路径
arg... 传递给执行的程序的参数列表
file 执行的程序的名称,在PATH中查找
/*进程创建 - ececl(p) - 示例*/
/*执行ls命令,显示/etc 目录下所有的文件的详细信息*/
if(execl("/bin/ls" , "ls" , "-a" , "-l", "/ect",NULL)<0)
{
perror("execl");
}
if(execlp("ls","ls" , "-a" , "-l", "/ect",NULL)<0)
{
perror("execlp");
}
进程 - execv/execvp
#include <unistd.h>
int execv(const char *path,char *const argv[]);
int execvp(const char *file,char *const argv[]);
成功时执行制定的程序;失败时返回EOF
arg... 封装成指针数组的形式
/*进程创建 -execv(p) - 示例 */
/*执行ls命令,显示/etc目录下所有文件的详细信息*/
char *arg[] = {"ls","-a","-l","/etc",NULL};//字符指针数组
if(execv("/bin/ls",arg)<0)
{
perror("execv");
}
if(execvp("ls",arg)<0)
{
perror("execvp");
}
进程 - system
#include <stdlib.h>
int system (const char *command);
成功时返回命令command的返回值:失败时返回EOF
当前进程等待command执行结束才继续执行
------L5-D1-4
进程回收
子进程结束时由父进程回收
孤儿进程由init进程回收
若没有及时回收会出现僵尸进程
进程回收 - wait
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
成功时返回回收的子进程的进程号;失败时返回EOF
若子进程没有结束,父进程一直阻塞
若有多个子进程,哪个先结束就先回收
status 指定保存子进程返回值和结束方式的地址
status为NULL表示直接释放子进程PCB,不接收返回值
/*进程回收 – wait – 示例*/
int status;
pid_t pid;
/*创建子进程*/
if ((pid = fork()) < 0)
{
perror(“fork”); exit(-1);
}
/*如果该进程为子进程*/
else if (pid == 0)
{
sleep(1); exit(2);
}
/*父进程等待子进程结束,以16进制打印status*/
else {
wait(&status); printf(“%x\n”, status);
}
进程返回值和结束方式
子进程通过020exit / _exit / return 返回某个值(0-255)
父进程调用wait(&status) 回收
WIFEXITED(status) 判断子进程是否正常结束
WEXITSTATUS(status) 获取子进程返回值
WIFSIGNALED(status) 判断子进程是否被信号结束
WTERMSIG(status) 获取结束子进程的信号类型
进程回收 – waitpid
#include <unistd.h>
pid_t waitpid(pid_t pid, int *status, int option);
成功时返回回收的子进程的pid或0;失败时返回EOF
pid可用于指定回收哪个子进程或任意子进程
status指定用于保存子进程返回值和结束方式的地址
option指定回收方式,0 或 WNOHANG
守护进程
守护进程(Daemon)是Linux三种进程类型之一
通常在系统启动时运行,系统关闭时结束 Linux系统中大量使用,
很多服务程序以守护进程形式运行
守护进程特点 ---(前台进程可以在终端输入可以给终端输出,后台进程只能给终端输出)
始终在后台运行
独立于任何终端
周期性的执行某种任务或等待处理特定事件
/*守护进程*/
/*创建守护进程,每隔1秒将系统时间写入文件time.log*/
int main() {
pid_t pid; //保存进程号
FILE *fp; //流指针
time_t t; //变量
int i;
/*创建子进程*/
if ((pid = fork()) < 0) {
perror(“fork”); exit(-1);
}
/*父进程退出,子进程被init收养,在后台运行*/
else if (pid > 0) {
exit(0);
}
setsid(); //子进程创建新的会话,脱离原先的终端
umask(0); //重设权限掩码为0,创建一个新的文件中指定的权限会与权限掩码的反进行与操作生成该进程的文件权限,该权限掩码设为0 ,则不影响当前进程的权限设定
chdir(“/tmp”);//修改当前工作目录,指向根目录下temp
for (i=0; i< getdtablesize(); i++) {
close(i);
}//将从父进程继承来的文件都关闭
if ((fp = fopen(“time.log”, “a”)) == NULL) {
perror(“fopen”); exit(-1); }//打开文件
/*将本地时间写入文件中*/
while ( 1 ) {
time(&t);
fprintf(fp, “%s”, ctime(&t));
fflush(fp);//刷新流
sleep(1);
}
L5-D2-2
专题专题
进程
进程有独立的地址空间
Linux为每个进程创建task_struct
每个进程都参与内核调度,互不影响
线程
进程在切换时系统开销大
很多操作系统引入了轻量级进程LWP
同一进程中的线程共享相同地址空间 Linux不区分进程、线程
线程特点
通常线程指的是共享相同地址
空间的多个任务(使用多线程的好处:大大提高了任务切换的效率 避免了额外的TLB & cache的刷新)
线程创建 – pthread_create
#include <pthread.h>
int pthread_create(pthread_t *thread, const
pthread_attr_t *attr, void *(*routine)(void *), void *arg);
成功返回0,失败时返回错误码
thread 线程对象
attr 线程属性,NULL代表默认属性
routine 线程执行的函数
arg 传递给routine的参数
线程回收 – pthread_join
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
成功返回0,失败时返回错误码
thread 要回收的线程对象
调用线程阻塞直到thread结束
*retval 接收线程thread的返回值
线程结束 – pthread_exit
#include <pthread.h>
void pthread_exit(void *retval);
结束当前线程
retval可被其他线程通过pthread_join获取
线程私有资源被释放
/*线程 - 示例*/
char message[32] = “Hello World”;//全局变量在静态存储区,可以被所有的线程访问
void *thread_func(void *arg);
int main(void) {
pthread_t a_thread;//定义一个线程对象变量
void *result;
/*创建线程a_thread,用的是缺省属性,指定thread_func函数,给函数传0值*/
if (pthread_create(&a_thread, NULL, thread_func, NULL) != 0) {
printf(“fail to pthread_create\n”); exit(-1);
}
pthread_join(a_thread, &result);//等待a_thread线程结束,回收线程,并且接收返回值
printf(“result is %s\n”, result);//回收成功后,将a_thread中的返回值打印
printf(“message is %s\n”, message);//并将数组中的内容也打印出来
return 0;
}
/*线程主函数*/
void *thread_func(void *arg) {
sleep(1);
strcpy(message, “marked by thread”);//将数组中的字符串进行修改
pthread_exit(“thank you for waiting for me”);//结束当前先线程,并返回当前字符串的首地址
}
$ gcc -o test test.c -lpthread
$ ./test
result is thank you for waiting for me
message is marked by thread
线程间通信
线程共享同一进程的地址空间
优点:线程间通信很容易
通过全局变量交换数据
缺点:多个线程访问共享数据时需要同步或互斥机制
线程通信--同步
同步(synchronization)指的是多个任务按照约定的先后次序相互配合完成一件事情
由信号量来决定线程是继续运行还是阻塞、等待
/*线程同步 – 示例1*/
/*两个线程同步读写缓冲区(生产者/消费者问题)*/
// 此处省略若干头文件
char buf[32];
sem_t sem;//定义一个全局的信号量对象
void *function(void *arg);//声明线程要执行的函数
int main(void) {
pthread_t a_thread;
/*信号量初始化,sem为信号量对象的地址,信号量用于进程内部线程通信,第三个参数表示信号量为0*/
if (sem_init(&sem, 0, 0) < 0) {
perror(“sem_init”); exit(-1);
}
/*创建线程,线程地址为a_thread,用缺省的属性,线程的函数为function,传递参数为空*/
if (pthread_create(&a_thread, NULL, function, NULL) != 0) {
printf(“fail to pthread_create\n”); exit(-1);
}
/*写线程*/
printf(“input ‘quit’ to exit\n”);
do {
fgets(buf, 32, stdin);//从标准输入读取一串字符串到buf里面
sem_post(&sem); //执行V操作,释放资源
} while (strncmp(buf, “quit”, 4) != 0);//比较字符串
return 0;
}
/*读线程*/
void *function(void *arg) {
while ( 1 ) {
sem_wait(&sem);//执行P操作,申请资源
printf(“you enter %d characters\n”, strlen(buf));//申请资源成功,显示当前缓冲区内字符长度是多少
}
}
/*线程同步 – 示例2*/
/*两个线程同步读写缓冲区(生产者/消费者问题)*/
char buf[32];
sem_t sem_r, sem_w;//定义全局可写信号量和可读信号量
void *function(void *arg);
int main(void) {
pthread_t a_thread;
/*两个信号量初始化,因为一开始,buf缓冲区为0,可读信号量为0,可写信号量为1*/
if (sem_init(&sem_r, 0, 0) < 0) {
perror(“sem_init”); exit(-1);
}
if (sem_init(&sem_w, 0,1) < 0) {
perror(“sem_init”); exit(-1);
}
/*创建一个新的线程*/
if (pthread_create(&a_thread, NULL, function, NULL) != 0) {
printf(“fail to pthread_create\n”); exit(-1); }
printf(“input ‘quit’ to exit\n”);
do {
sem_wait(&sem_w);//可写信号量P操作,申请资源----判断当前有没有可写的缓冲区
fgets(buf, 32, stdin);---写的动作产生了一个可读的资源
sem_post(&sem_r); //可读信号量V操作,释放资源---唤醒读线程
} while (strncmp(buf, “quit”, 4) != 0);
return 0;
}
void *function(void *arg) {
while ( 1 ) {
sem_wait(&sem_r);//可读信号量P操作,申请资源---检查当前缓冲区有没有可读的资源
printf(“you enter %d characters\n”, strlen(buf));//对缓冲区内容处理后,缓冲区又变为空闲
sem_post(&sem_w);//可写信号量V操作,释放资源---唤醒写线程
}
}
线程通信 – 互斥
临界资源
一次只允许一个任务(进程、线程)访问的共享资源
临界区
访问临界区的代码
互斥机制
mutex互斥锁
任务访问临界资源前申请锁,访问完后释放锁
/*线程互斥 – 示例*/
// 此处省略若干头文件
unsigned int count, value1, value2;
pthread_mutex_t lock;//定义一个全局的互斥锁
void *function(void *arg);
int main(void) {
pthread_t a_thread;
/*对互斥锁进行初始化,锁的地址是lock,锁的属性是NULL,用默认的*/
if (pthread_mutex_init(&lock, NULL) != 0) {
printf(“fail to pthread_mutex_init\n”); exit(-1);
}
/*创建线程,用a_thread对象来创建,默认属性,线程函数名为function,传入空地址的参数*/
if (pthread_create(&a_thread, NULL, function, NULL) != 0) {
printf(“fail to pthread_create”); exit(-1);
}
/*主线程*/
while ( 1 ) {
count++;
/*条件编译,根据条件是否用锁---如果_LOCK_有宏定义,则用互斥锁,然后把count赋值给value1和value2,如果_LOCK_没有宏定义,则不用互斥锁*/
#ifdef _LOCK_
pthread_mutex_lock(&lock);//申请锁
#endif
value1 = count;
value2 = count;
#ifdef _LOCK_
pthread_mutex_unlock(&lock);//释放锁
#endif
}
return 0;
}
/*另外一个线程*/
void *function(void *arg) {
while ( 1 ) {
#ifdef _LOCK_
pthread_mutex_lock(&lock);//申请锁
#endif
if (value1 != value2) {
printf(“value1 = %u, value2 = %u\n”, value1, value2);
usleep(100000);
}
#ifdef _LOCK_
pthread_mutex_unlock(&lock);//释放锁
#endif
}
return NULL; }
/*运行结果*/
不使用互斥锁(程序中不定义宏)
$ gcc –o test test.c -lpthread
$ ./test
value1 = 19821, value2 = 19820
value1 = 14553456, value2 = 14553455
value1 = 29196032, value2 = 29196031
……
使用互斥锁(–D_LOCK_ 向程序中传递_LOCK_,相当于程序中定义了这个宏)
$ gcc –o test test.c –lpthread –D_LOCK_
$ ./test
L5 - D3 - 4
Unix进程间通信
早期UNIX进程间通信方式
无名管道(pipe)
有名管道 (fifo)
信号(signal)
System V IPC
共享内存(share memory)
消息队列(message queue)
信号灯集(semaphore set)
套接字(socket)
无名管道具有如下特点:
只能用于具有亲缘关系的进程之间的通信
单工的通信模式,具有固定的读端和写端
无名管道创建时会返回两个文件描述符,分别用于读写管道
/*无名管道 – 示例*/
/*子进程1和子进程2分别往管道中写入字符串;父进程读管道内容并打印*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void) {
pid_t pid1, pid2;//定义变量,保存fork的返回值
char buf[32];
int pfd[2];//定义数组,数组用来创建管道
/*创建无名管道*/
if (pipe(pfd) < 0) {
perror(“pipe”); exit(-1);
}
/*创建子进程,一定要在无名管道创建后进行子进程创建,否则子进程不会继承父进程的打开的文件及代码*/
if ((pid1 = fork()) < 0) {
perror(“fork”); exit(-1);
}
else if (pid1 == 0) { // 子进程1
strcpy(buf, “I’m process1”);//将字符串写入buf中
write(pfd[1], buf, 32);//将buf中的数据写入到无名管道中
exit(0);
}
else { // 父进程
/*创建第二个子进程*/
if ((pid2 = fork()) < 0) {
perror(“fork”); exit(-1);
}
else if (pid2 == 0) { // 子进程2
sleep(1);
strcpy(buf, “I’m process 2”);
write(pfd[1], buf, 32);
}
else { // 父进程
wait(NULL);//等待回收任意一个子进程
read(pfd[0], buf, 32);将pdf管道中的数据读入到buf中
printf(“%s\n”, buf);
wait(NULL);
read(pfd[0], buf, 32);
printf(“%s\n”, buf);
}
}
return 0;
}
读无名管道
写端存在
有数据 read返回实际读取的字节数
无数据 进程读阻塞
写端不存在
有数据 read返回实际读取的字节数
无数据 read返回0
写无名管道
读端存在
有空间 write返回实际写入的字节数
无空间 进程写阻塞
/*获取无名管道的大小,有读端的情况*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int pfd[2], count = 0;
char buf[1024];
/*创建管道*/
if (pipe(pfd) < 0)
{
perror("pipe");
exit(-1);
}
while ( 1 )
{
write(pfd[1], buf, 1024);
printf("wrote %dk bytes\n", ++count);//打印目前写入字节数
}
return 0;
}
/*验证在无进程时,写进程写入数据,管道断裂(进程被信号结束)*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
int pfd[2];
int status;
pid_t pid;
if (pipe(pfd) < 0) // 创建管道
{
perror("pipe");
exit(-1);
}
close(pfd[0]); // 关闭管道读端
if ((pid = fork()) < 0) // 创建子进程
{
perror("fork");
exit(-1);
}
else if (pid == 0)
{
write(pfd[1], "data", 4); // 写管道
printf("wrote data to pipe\n");
exit(0);
}
else
{
wait(&status); // 回收子进程,将返回值写入status中
printf("%x\n", status); // 打印子进程退出状态
}
return 0;
}
2019.9.7
有名管道特点
对应管道文件,可用于任意进程之间进行通信
打开管道时可指定读写方式
通过文件IO操作,内容存放在内存中
有名管道创建 – mkfifo
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *path, mode_t mode);
成功时返回0,失败时返回EOF
path 创建的管道文件路径
mode 管道文件的权限,如0666
/*有名管道读写 – 示例*/
/*进程A:循环从键盘输入并写入有名管道myfifo,输入quit时退出
进程B:循环统计进程A每次写入myfifo的字符串的长度*/
/* create_fifo.c */
// 省略头文件
int main(void) {
/*创建有名管道名称为myfifo,权限为0666*/
if(mkfifo(“myfifo”, 0666) < 0) {
perror(“mkfifo”);
exit(-1);
}
return 0;
}
/* write_fifo.c */
// 省略头文件
#define N 32
int main(void) {
char buf[N];
int pfd;
/*以只写方式打开管道*/
if ((pfd = open(“myfifo”, O_WRONLY)) < 0) {
perror(“open”); exit(-1);
}
while ( 1 ) {
fgets(buf, N, stdin);//从标准输入读取字符串到buf中
if (strcmp(buf, “quit\n”) == 0) break;
write(pfd, buf, N);把buf中所有的内容写入管道中。
}
close(pfd);//关闭打开的管道
return 0;
}
/* read_fifo.c */
// 省略头文件
#define N 32
int main(void) {
char buf[N];
int pfd;
/*以只读方式打开管道*/
if ((pfd = open(“myfifo”, O_RDONLY)) < 0) {
perror(“open”);
exit(-1);
}
/*从管道中读取字符串到buf中,当管道中没有数据时,读进程阻塞*/
while (read(pfd, buf, N) > 0) {
printf(“the length of string is %d\n”, strlen(buf));
}
close(pfd);
return 0;
}
无论单独打开读端或者写端,程序都会阻塞。
信号机制
信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式
linux内核通过信号通知用户进程,不同的信号类型代表不同的事件
Linux对早期的unix信号机制进行了扩展
进程对信号有不同的响应方式
缺省方式
忽略信号
捕捉信号
信号相关命令 kill / killall
kill [-signal] pid
默认发送SIGTERM
-sig 可指定信号
pid 指定发送对象
killall [-u user | prog]
prog 指定进程名
user 指定用户名
信号发送 – kill / raise
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
int raise(int sig);//只能给自己发信号
成功时返回0,失败时返回EOF
pid 接收进程的进程号;0代表同组进程; -1代表所有进程
sig 信号类型
信号相关函数 – alarm / pause
int alarm(unsigned int seconds);
成功时返回上个定时器的剩余时间,失败时返回EOF
seconds 定时器的时间
一个进程中只能设定一个定时器,时间到时产生SIGALRM
int pause(void);
进程一直阻塞,直到被信号中断
被信号中断后返回-1,errno为EINTR
/*信号函数 alarm / pause – 示例*/
#include <stdio.h>
#include <unistd.h>
int main() {
alarm(3);//创建了一个闹钟,指定3s钟后,时间到
pause();//让进程睡眠,3s钟后,闹钟时间到。内核会向进程发送一个定时器信号,收到信号后,进程会首先响应这个定时器信号sigalarm,默认终止进程,不会执行下面的语句。
printf(“I have been waken up!\n”);
return 0;
}
$ ./a.out
Alarm clock
重要:alarm经常用于实现超时检测
设置信号响应方式 – signal
#include <unistd.h>
#include <signal.h>
void (*signal(int signo, void (*handler)(int)))(int);
成功时返回原先的信号处理函数,失败时返回SIG_ERR
signo 要设置的信号类型
handler 指定的信号处理函数: SIG_DFL代表缺省方式; SIG_IGN 代表忽略信号;
信号函数 signal – 示例
// 头文件省略
/*信号处理函数的接口,根据信号处理函数中形参的值来区分不同的信号*/
void handler (int signo) {
if (signo == SIGINT) {
printf(“I have got SIGINT!\n”); }
if (signo == SIGQUIT) {
printf(“I have got SIGQUIT\n”); }
}
int main() {
/*设置信号的响应方式,不同的信号对应一样的处理函数*/
signal(SIGINT, handler);
signal(SIGQUIT, handler);
while ( 1 ) pause();//阻塞到该处,直到其他信号结束当前进程
return 0;
}
L5 - D5 - 3
System V IPC
IPC 对象包含: 共享内存、消息队列和信号灯集
每个IPC对象有唯一的ID(只有创建ipc对象的进程知道)
IPC对象创建后一直存在,直到被显式地删除
每个IPC对象有一个关联的KEY(指定)
ipcs (显示系统中当前所有的ipc对象)/ ipcrm(删除)
System V IPC – ftok
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *path, int proj_id);
成功时返回合法的key值,失败时返回EOF
path 存在且可访问的文件的路径
proj_id 用于生成key的数字,不能为0
/*System V IPC - ftok – 示例*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
key_t key;//key_t类型等价于一个整形
/*调用ftok生成key,第一个参数点是路径,相当于当前目录,第二个参数是工程号,传的是一个字符常量*/
if ((key = ftok(“.”, ‘a’)) == -1) {
perror(“key”);
exit(-1);
}
……
//每一个进程都得生成相同的key,这样才能通过相同的key找到对应的ipc对象
共享内存
共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝
共享内存在内核空间创建,可被进程映射到用户空间访问,使用灵活
由于多个进程可同时访问共享内存,因此需要同步和互斥机制配合使用
共享内存使用步骤
创建/打开共享内存
映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问
读写共享内存
撤销共享内存映射
删除共享内存对象
/*共享内存创建 - shmget – 示例1*/
/*要求:创建一个私有的共享内存,大小为512字节,权限为0666*/
int shmid;
/*创建私有共享内存,共享内存一定是新建的,因此第三个参数不用加IPC_CREAT*/
if ((shmid = shmget(IPC_PRIVATE, 512, 0666)) < 0) {
perror(“shmget”);
exit(-1);
}
/*共享内存创建 - shmget – 示例2*/
/*要求:创建/打开一个和key关联的共享内存,大小为1024字节,权限为0666*/
key_t key;
int shmid;
if ((key = ftok(“.”, ‘m’)) == -1) {
perror(“ftok”);
exit(-1);
}
if ((shmid = shmget(key, 1024, IPC_CREAT|0666)) < 0) {
perror(“shmget”);
exit(-1);
}
共享内存映射 – shmat
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
成功时返回映射后的地址,失败时返回(void *)-1
shmid 要映射的共享内存id
shmaddr 映射后的地址, NULL表示由系统自动映射
shmflg 标志位 0表示可读写;SHM_RDONLY表示只读
/*通过指针访问共享内存,指针类型取决于共享内存中存放的数据类型*/
/* 例如:在共享内存中存放键盘输入的字符串 */
char *addr;
int shmid;
……
/*进行共享内存的映射,一个参数是id,第二个是映射后的地址,Null表示系统自动映射,第三个参数表示可读写*/
if ((addr = (char *)shmat(shmid, NULL, 0)) == (char *)-1) {
perror(“shmat”);
exit(-1);
}
fgets(addr, N, stdin);//往共享内存存放一个键盘输入的字符串,第一个参数是要存放输入缓冲区内容的首地址
……
消息队列
消息队列是System V IPC对象的一种
消息队列由消息队列ID来唯一标识
消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等
消息队列可以按照类型来发送/接收消息
消息队列使用步骤
打开/创建消息队列 msgget
向消息队列发送消息 msgsnd
从消息队列接收消息 msgrcv
控制消息队列 msgctl
/*消息队列创建/打开 - 示例*/
……
int main() {
int msgid;//定义消息变量的ID
key_t key;
//生成key,第一个参数时当前目录,第二个参数时指定的工程号
if ((key = ftok(“.”, ‘q’)) == -1) {
perror(“ftok”); exit(-1);
}
/*创建消息队列*/
if ((msgid = msgget(key, IPC_CREAT|0666)) < 0) {
perror(“msgget”); exit(-1);
}
……
return 0;
}
消息格式
通信双方首先定义好统一的消息格式
用户根据应用需求定义结构体类型
首成员类型为long,代表消息类型(正整数)
其他成员都属于消息正文
/*消息发送 - 示例*/
/*定义消息格式,第一个成员代表消息的类型(正整数),第二个成员是字符数组用来存放消息的字符串*/
typedef struct {
long mtype;
char mtext[64];
} MSG;
#define LEN (sizeof(MSG) - sizeof(long))//(正文的长度)定义一个宏,结构体大小减去第一个成员的大小
int main() {
MSG buf;
……
buf.mtype = 100; //填充消息的类型为
fgets(buf.mtext, 64, stdin);//将要发送消息的正文从键盘输入到buf的消息成员mtext中
msgsnd(msgid, &buf,LEN, 0);//发送消息到消息队列中,第一个参数是消息队列的id,第二个参数是消息缓冲区的首地址,LEN代表消息正文长度,0表示成功才返回
……
return 0;
}
/*消息接收 - 示例*/
typedef struct {
long mtype;
char mtext[64];
} MSG;
#define LEN (sizeof(MSG) - sizeof(long))
int main() {
MSG buf;
……
/*
消息接收:成功时返回收到的消息长度,失败时返回-1
msgid 消息队列id
buf 消息缓冲区地址
LEN 指定接收的消息长度
200 指定接收的消息类型
msgflg 标志位 0 或 IPC_NOWAIT
*/
if (msgrcv(msgid, &buf,LEN, 200, 0) < 0) {
perror(“msgrcv”);
exit(-1);
}
……
}
/*要求:两个进程通过消息队列轮流将键盘输入的字符串发送给对方,接收并打印对方发送的消息*/
/*clientA.c*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
typedef struct {
long mtype;
char mtext[64];
} MSG;
#define LEN (sizeof(MSG) - sizeof(long))
#define TypeA 100
#define TypeB 200
int main()
{
int msgid;
key_t key;
MSG buf;
/*生成key*/
if ((key = ftok(".", 'q')) == -1)
{
perror("ftok");
exit(-1);
}
/*创建消息队列*/
if ((msgid = msgget(key, IPC_CREAT|0666)) < 0)
{
perror("msgget");
exit(-1);
}
while ( 1 )
{
buf.mtype = TypeB;
printf("input > "); //打印提示信息
fgets(buf.mtext, 64, stdin);//从键盘输入消息到消息队列中
msgsnd(msgid, &buf, LEN, 0);//发送消息
if (strcmp(buf.mtext, "quit\n") == 0) //如果键盘输入quit,则退出当前进程
{
msgctl(msgid, IPC_RMID, NULL);//删除消息队列
exit(0);
}
msgrcv(msgid, &buf, LEN, TypeA, 0);//接收消息队列
if (strcmp(buf.mtext, "quit\n") == 0) break;//如果接收到quit,跳出循环
printf("recv message : %s", buf.mtext);//打印接收消息
}
msgctl(msgid, IPC_RMID, NULL);//删除消息队列
return 0;
}
/*clientB.c*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
typedef struct {
long mtype;
char mtext[64];
} MSG;
#define LEN (sizeof(MSG) - sizeof(long))
#define TypeA 100
#define TypeB 200
int main()
{
int msgid;
key_t key;
MSG buf;
/*生成key*/
if ((key = ftok(".", 'q')) == -1)
{
perror("ftok");
exit(-1);
}
/*创建消息队列*/
if ((msgid = msgget(key, IPC_CREAT|0666)) < 0)
{
perror("msgget");
exit(-1);
}
while ( 1 )
{
msgrcv(msgid, &buf, LEN, TypeB, 0);//接收消息
if (strcmp(buf.mtext, "quit\n") == 0) break;//如果接受到quit,则退出当前进程
printf("recv message : %s", buf.mtext);//打印接收消息
buf.mtype = TypeA;//发送消息类型为TypeA
printf("input > "); //打印提示信息
fgets(buf.mtext, 64, stdin);//从键盘输入消息到消息队列中
msgsnd(msgid, &buf, LEN, 0);//发送消息
if (strcmp(buf.mtext, "quit\n") == 0) exit(0);//如果发送的消息是quit,则退出当前进程
}
msgctl(msgid, IPC_RMID, NULL);//删除当前消息队列
return 0;
}
L5 - D6 - 4
System V IPC - 信号灯
信号灯也叫信号量,用于进程/线程同步或互斥的机制
信号灯的类型
Posix 无名信号灯
Posix有名信号灯
System V 信号灯
信号灯的含义
计数信号灯
System V IPC - 信号灯特点
System V 信号灯是一个或多个计数信号灯的集合
可同时操作集合中的多个信号灯
申请多个资源时避免死锁
System V信号灯使用步骤
打开/创建信号灯 semget
信号灯初始化 semctl
P/V操作 semop
删除信号灯 semctl
/*信号灯集初始化 - 示例*/
/*要求:假设信号灯集合中包含两个信号灯;第一个初始化为2,第二个初始化为0*/
union semun myun;//定义一个共用体变量
myun.val = 2;//初值放入共用体成员val中
/*
成功时返回0,失败时返回EOF
semid 要操作的信号灯集id
0 要操作的集合中的信号灯编号
SETVAL 命令字
myun 存放初始化的值
*/
if (semctl(semid, 0, SETVAL, myun) < 0) {
perror(“semctl”); exit(-1);
}
myun.val = 0;
if (semctl(semid, 1, SETVAL, myun) < 0) {
perror(“semctl”); exit(-1);
}
/*信号灯集/共享内存 - 示例*/
/*
要求:父子进程通过System V信号灯同步对共享内存的读写
父进程从键盘输入字符串到共享内存
子进程删除字符串中的空格并打印
父进程输入quit后删除共享内存和信号灯集,程序结束
*/
#include<stdio.h>
#include<singal.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/sem.h>
#define N 64 // 缓冲区的大小
#define READ 0 //信号灯集合的编号
#define WRITE 1
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *_buf;
}
void init_sem(int semid, int s[], int n)
{
int i;
union semun myun;
for(i=0;i<n,i++)
{
myun.val = s[i];
semctl(semid, i, SETVAL, myun);
}
}
void pv(int semid, int num, int op)
{
struct sembuf buf;
buf.sem_num = num;
buf.sem_op = op;
buf.sem_flg = 0;
semop(semid, &buf, 1);
}
int main()
{
int shmid, semid ,s[]={0,1};//共享内存的ID,信号灯的ID,信号灯的初始化数值
pid_t pid;
key_t key;
char *shmaddr;
/*生成key*/
if((key = ftok(".",'s')) == -1)
{
perror("ftok");
exit(-1);
}
/*创建共享内存*/
if((shmid = shmget(key, N, IPC_CREAT|0666)) < 0)
{
perror("shmget");
exit(-1);
}
/*创建信号灯集合*/
if((semid = semget(key, 2, IPC_CREAT|0666)) < 0)
{
PERROR("semget");
goto _error1;
}
/*信号灯集合的初始化*/
init_sem(semid, s, 2);
/*映射共享内存*/
if ((shmaddr = (char *)shmat(shmid, NULL, 0)) == (char *)-1)
{
perror("shamt");
goto _error2;
}
/创建子进程/
if((pid = fork()) < 0)
{
perror("fork");
goto _error2;
}
elseif (pid == 0)//子进程:删除空格,打印信息
{
char *p, *q;
while(1)
{
pv(semid, READ, -1);
p = q = shmaddr;
while( *q )
{
if(*q!=' ')
{
*p++ = *q;
}
q++;
}
*p = '\0';
printf("%s",shmaddr);
pv(semid, WRITE, 1);
}
}
else//父进程:循环输入
{
while(1)
{
pv(semid, WRITE, -1);
printf("input > ");
fgets(shmaddr, N, stdin);
if(strcmp(shmaddr, "quit\n") == 0) break;
pv(semid, READ, 1);
}
kill(pid, SIGUSR1);
}
_error2:
semctl(semid, 0, IPC_RMID),如果映射共享内存失败,删除创建的信号灯集
_error1:
shmctl(shmid, IPC_RMID, NULL);//如果创建信号灯失败,则删除共享内存
return 0;
}