a:进程的概念:
a.1 进程的内容
a.2 "进程控制块、CPU寄存器值、堆栈"
a.3 进程类型:
a.4 进程状态
b: 进程相关操作:
b.1 查看进程信息
ps 查看系统进程快照
top 查看进程动态信息
/proc 查看进程详细信息
nice 按用户指定的优先级运行进程
renice 改变正在运行进程的优先级
jobs 查看后台进程
bg 将挂起的进程在后台运行
fg 把后台运行的进程放到前台运行
c: 进程创建/结束
c.1 创建子进程函数 "fork"
c.2 父子进程
c.3 进程结束函数:
c.4 进程函数调用 "exec函数族"
c.5 进程回收函数: "wait" //阻塞进程
c.6 创建守护进程:
e: 线程总结:
e.0:同步互斥:
e.1:进程概念:
e.2 线程创建/回收/结束函数
"pthread_create" "pthread_join" "pthread_exit"
e.3 线程间通信概念 ----同步机制---信号量(P/V操作)---信号量函数---
e.4 线程----信号量p/v操作程序示例:
e.5 线程通信 – 互斥 --pthread_mutex_init--pthread_mutex_lock--pthread_mutex_unlock
f.进程间通信
f.0进程间通信:对于进程:重点学习的是进程间的通信机制
f.1 无名管道概念:
f.2 无名管道特点:
f.3 无名管道创建 – pipe
f.4 无名管道读写特性
f.5 有名管道:
f.6 信号机制
1.//信号概念:
2.//常用信号1
3.信号发送 、定时器、 信号捕捉
kill/raise alarm/pause signal
函数1:信号发送 – kill / raise
函数2:alarm 定时器
函数3:让进程睡眠直到有信号到来 pause(void);
函数4: 设置信号响应方式 – signal
f.7 IPC对象
1.IPC对象操作
2.IPC操作流程
3.ftok KEY生成函数
4.IPC对象共享内存
( 打开 shmget、
内存映射 shmat、
撤销映射 shmdt、
删除共享内存 shmctl)
5.IPC对象消息队列
a.消息队列概要
b.消息队列机制
消息的格式
1.创建打开 msgget
2.发送消息 msgsnd
3.读取消息 msgrcv
4.控制消息 msgctl
6.IPC对象信号灯集:
//a.信号灯概念:
//b.信号灯使用步骤
1.创建一个键值用于标识信号灯集
key, ftok()返回值
2.打开/创建信号灯 semget
3.信号灯初始化 semctl .. init_sem
4.P/V操作 semop sembuf
5.删除信号灯 semctl
/*******************************************************************/
1.进程总结:
---------------------------------------------------------------------
a.进程的概念:
"程序": 存放在磁盘上的指令和数据的有序集合(文件)
静态的
"进程": 执行一个程序所分配的资源的总称
进程是程序的一次执行过程
动态的,包括创建、调度、执行和消亡
进程和程序的区别:
程序是静态的,它是一些保存在磁盘上的指令的有序集合,没有任何执行的概念
进程是一个动态的概念,它是程序执行的过程,包括创建、调度和消亡
程序主要构成:正文段(只读,共享)、数据段(虚拟地址)
进程主要构成:正文段、数据段、堆栈段、PCB(描述进程信息的一个结构体)(实际分配物理地址)
/************************************************/
//a.1 进程的内容
<--- 正文段 ------>
程序
进程 <--- 用户数据---->
<--- 系统数据段
//a.2 "进程控制块、CPU寄存器值、堆栈"
进程控制块(pcb):
"进程标识PID"
"进程用户"
"进程状态、优先级"
"文件描述符表"
//a.3 进程类型:
交互进程:在shell下启动。以在前台运行,也可以在后台运行
批处理进程:和在终端无关,被提交到一个作业队列中以便顺序执行
守护进程:和终端无关,一直在后台运行
//a.4 进程状态
运行态:进程正在运行,或者准备运行
等待态:进程在等待一个事件的发生或某种系统资源
"可中断"
"不可中断"
停止态:进程被中止,收到信号后可继续运行
死亡态:已终止的进程,但pcb没有被释放
-----------------------------------------------------------------------
b: 进程相关操作
// b.1 查看进程信息
"ps 查看系统进程快照 " : ps -ef
(1) ps -ef|more
UID PID PPID C STME
创建者 进程号 副进程号 开始时间、
(2)ps -ef|grep test1
linux 28948 28780 99 00.00:25 ./test1
进程号 副进程号 CPU占用率 执行时间
(3)ps aux :能显示进程当前状态
"top 查看进程动态信息"
"/proc 查看进程详细信息"
(4) ps -ef|grep test1 查看进程号
cd /proc 再ls 查看所有的
cd 进程号
ls
cat status 查看该进程详细信息
"nice 按用户指定的优先级运行进程"
(5)nice -n 2 ./test1 0 19
"renice 改变正在运行进程的优先级"
(6)renice -n 2 28070(进程号)
"jobs 查看后台进程"
(7) ./test1 & //进入后台
"bg 将挂起的进程在后台运行"
"fg 把后台运行的进程放到前台运行"
c: 进程相关函数:
c.1 创建子进程函数 "fork"
//头文件: #include <unistd.h>
//函数原型:pid_t fork(void);
//返回值:
创建新的进程,失败时返回-1
成功时父进程返回子进程的进程号,子进程返回0
通过fork的返回值区分父进程(大于0)和子进程(0)
//函数示例:
pid_t pid;
if ((pid = fork()) < 0) {
//调用fork创建进程
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());
}
---------------------------------------------------------------------------------
c.2 父子进程
首先fork用于已经存在的进程中,创建一个新进程,即子进程。
-->其复制父进程,除了进程号,资源使用,计时器等。
然后,父子进程有独立的地址空间。
-->若父进程先结束,则子进程变成孤儿进程,被init收养,同时变为后台进程。
-->反之,父进程没有及时回收则子进程变为僵尸进程。
最后,子进程从fork下一语句开始执行。
----------------------------------------------------------------------------------
c.3 进程结束函数:
// 头文件:
#include <stdlib.h>
#include <unistd.h>
//函数原型:
void exit(int status);
void _exit(int status);
//返回值:
结束当前的进程并将status返回
exit结束进程时会刷新(流)缓冲区
//函数示例:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
printf(“this process will exit”);
exit(0);
printf(“never be displayed”);
}
./a.out
this process will be exit
#include <stdio.h>
#include <stdlib.h>
int main(void) {
printf(“using exit…\n”);
printf(“This is the end”);
exit(0);
}
./a.out
using exit…
This is the end
----------------------------------------------------------------------------------
c.4进程函数调用 exec函数族
进程调用exec函数族执行某个程序
进程当前内容被指定的程序替换
实现让父子进程执行不同的程序
-->父进程创建子进程
-->子进程调用exec函数族
-->父进程不受影响
函数1: // execl/execlp:
//头文件:
#include <unistd.h>
//函数原型:
int execl(const char *path, const char *arg, …);
int execlp(const char *file, const char *arg, …);
//参数:
path 执行的程序名称,包含路径
arg… 传递给执行的程序的参数列表
file 执行的程序的名称,在PATH中查找
//返回值:
成功时执行指定的程序;失败时返回EOF
//函数示例:
"执行ls命令,显示/etc目录下所有文件的详细信息"
if (execl(“/bin/ls”, “ls”, “-a”, “-l”, “/etc”, NULL) < 0) {
perror(“execl”);
}
if (execlp(“ls”, “ls”, “-a”, “-l”, “/etc”, NULL) < 0) {
perror(“execlp”);
}
---------------------------------------------------------------------------------------------
函数2 // execv / execvp
// 头文件:
#include <unistd.h>
// 函数原型:
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
//返回值:
成功时执行指定的程序;失败时返回EOF
arg… 封装成指针数组的形式
//函数示例:
" 执行ls命令,显示/etc目录下所有文件的详细信息"
char *arg[] = {“ls”, “-a”, “-l”, “/etc”, NULL};
if (execv(“/bin/ls”, arg) < 0) {
perror(“execv”);
}
if (execvp(“ls”, arg) < 0) {
perror(“execvp”);
}
函数3: system
#include <stdlib.h>
int system(const char *command);
成功时返回命令command的返回值;失败时返回EOF
当前进程等待command执行结束后才继续执行
c.5 进程回收函数: wait //阻塞进程
//用于使父进程阻塞,直到下一个子进程结束或者接到指定信号
// 头文件:
#include <sys/types.h>
#include <sys/wait.h>
// 函数原型:
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int option);
// 参数:
status 指定保存子进程返回值和结束方式的地址
status 为NULL表示直接释放子进程PCB,不接收返回值
pid可用于指定回收哪个子进程或任意子进程
option指定回收方式,0 (阻塞方式回收)或 WNOHANG(非阻塞方式回收)
// 返回值:
成功时返回回收的子进程的进程号;失败时返回EOF
若子进程没有结束,父进程一直阻塞
若有多个子进程,哪个先结束就先回收、
成功时返回回收的子进程的pid或0;失败时返回EOF
// 父进程调用wait(&status) 回收
WIFEXITED(status) 判断子进程是否正常结束
WEXITSTATUS(status) 获取子进程返回值
WIFSIGNALED(status) 判断子进程是否被信号结束
WTERMSIG(status) 获取结束子进程的信号类型
waitpid(pid, &status, 0);
waitpid(pid, &status, WNOHANG);
waitpid(-1, &status, 0);
waitpid(-1, &status, WNOHANG);
//函数示例:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
int status;
pid_t pid;
if ((pid = fork()) < 0) { //创建子进程
perror(“fork”);
exit(-1);
}
else if (pid == 0) { //子进程
sleep(1);
exit(2);
}
else { //父进程
wait(&status); //父进程阻塞,等待子进程结束
printf(“%x\n”, status); //子进程返回值或结束方式地址
}
return 0;
}
--------------------------------------------------------------------------------------------
c.6 创建守护进程:
Linux系统中大量使用,很多服务程序以守护进程形式运行
始终在后台运行
独立于任何终端
周期性的执行某种任务或等待处理特定事件
1. 创建子进程,父进程退出
if (fork() > 0) {
exit(0);
}
---->子进程变成孤儿进程,被init进程收养
---->子进程在后台运行
2.子进程创建新会话
if (setsid() < 0) {
exit(-1);
}
---> 子进程成为新的会话组长
---> 子进程脱离原先的终端
3.更改当前工作目录
chdir(“/”);
chdir(“/tmp”);
---> 守护进程一直在后台运行,其工作目录不能被卸载
---> 重新设定当前工作目录cwd
4.重设文件权限掩码
if (umask(0) < 0) {
exit(-1);
}
----> 文件权限掩码设置为0
----> 只影响当前进程
5. 关闭打开的文件描述符
int i;
for(i=0; i<getdtablesize(); i++) {
close(i);
}
---> 关闭所有从父进程继承的打开文件
---> 已脱离终端,stdin / stdout / stderr无法再使用
开始--- > fork()
pid > 0---->父进程退出 ---exit(1)
pid == 0--->子进程创建新会话 --------孤儿进程 setsid();
改变当前目录为根目录 --------脱离当前终端chdir("/mywork")
重设文件权限掩码 --------不修改当前文件的权限umask(0)
关闭文件描述符 ---------getdtablesize();
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
int main() {
pid_t pid;
FILE *fp;
time_t t;
int i;
if ((pid = fork()) < 0)
{
perror(“fork”);
exit(-1);
}
else if (pid > 0)
{
exit(0); //1.创建子进程,父进程退出
}
setsid(); //2.子进程创建新会话
umask(0); //3.重设文件权限掩码
chdir(“/tmp”); //4.更改当前工作目录
for (i=0; i< getdtablesize(); i++)
{
close(i); //5.关闭打开的文件描述符
}
if ((fp = fopen("time.log", "a")) == NULL)
{
perror(“fopen”);
exit(-1);
}
while ( 1 )
{
time(&t);
fprintf(fp, "%s", ctime(&t));
fflush(fp);
sleep(1);
}
return 0;
}
/***************************************************************************/
e.线程总结:
e.0同步和互斥机制:
这种机制是为了方便大家对临界资源的操作。
本质是对临界资源的配置
同步(synchronization):指的是多个任务(线程)
按照约定的顺序相互配合完成一件事情
互斥机制:
引入互斥(mutual exclusion)锁的目的是用来保证共享数据操作的完整性。
互斥锁主要用来保护临界资源
每个临界资源都由一个互斥锁来保护,任何时刻最多只能有一个线程能访问该资源
线程必须先获得互斥锁才能访问临界资源,访问完资源后释放该锁。
如果无法获得锁,线程会阻塞直到获得锁为止
同步互斥机制实现函数:
pthread_mutex_init / destroy
pthread_mutex_trylock/lock/unlock
pthread_cond_init/destroy
pthread_cond_wait/timewait
×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
安装POSIX包
No manual entry for pthread_mutex_init
man pthread_mutex_init
No manual entryfor pthread_mutex_init
解决方案:
$sodo apt-get install manpages-posix manpages-posix-dev
×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
e.1:进程概念:
> 进程有独立的地址空间
> Linux为每个进程创建task_struct
> 每个进程都参与内核调度,互不影响
> 进程在切换时系统开销大
> 很多操作系统引入了轻量级进程LWP
> 同一进程中的线程共享相同地址空间
> Linux不区分进程、线程
> 通常线程指的是共享相同地址
空间的多个任务
> 一个进程中的多个线程共享以下资源
可执行的指令
静态数据
进程中打开的文件描述符
当前工作目录
用户ID
用户组ID
> 每个线程私有的资源包括
线程ID (TID)
PC(程序计数器)和相关寄存器
堆栈
错误号 (errno)
优先级
执行状态和属性
> pthread线程库中提供了如下基本操作
创建线程
回收线程
结束线程
> 同步和互斥机制
信号量
互斥锁
===============================================================================
e.2 线程创建/回收/结束函数 “pthread_create” “pthread_join”
//头文件:
#include <pthread.h>
//函数原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*routine)(void *), void *arg);
//线程创建函数
int pthread_join(pthread_t thread, void **retval);
//线程回收函数
void pthread_exit(void *retval);
//线程结束函数
//参数:
thread 线程对象
attr 线程属性,NULL代表默认属性
routine 线程执行的函数
arg 传递给routine的参数
void **retval 接收线程thread的返回值
void *retval 可被其他线程通过pthread_join获取
//返回值:
成功返回0,失败时返回错误码
调用线程阻塞直到thread结束
线程私有资源被释放
//线程函数示例:
#include <stdio.h>
#include <string.h>
#include <pthread.h>
char message[32] = “Hello World”;
void *thread_func(void *arg);
int main(void) {
pthread_t a_thread;
void *result;
if (pthread_create(&a_thread, NULL, thread_func, NULL) != 0) {
printf(“fail to pthread_create”);
exit(-1);
} //线程创建
pthread_join(a_thread, &result); //线程回收
printf(“result is %s\n”, (char *)result);
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”);
} //结束线程
//IPT:
$ gcc -o test test.c -lpthread
$ ./test
thank you for waiting for me
marked by thread
=============================================================================
e.3 线程间通信概念 ----同步机制—信号量(P/V操作)—信号量函数
1.线程间通信概念:
> 线程共享同一进程的地址空间
> 优点:线程间通信很容易
通过全局变量交换数据
>缺点: 多个线程访问共享数据时需要同步或互斥机制
2.线程通信--同步
同步(synchronization)指的是多个任务按照约定的先后次序相互配合完成一件事情
3.信号量 sem_init sem_wait sem_post
> 信号量代表某一类资源,其值表示系统中该资源的数量
> 信号量是一个受保护的变量,只能通过三种操作来访问
初始化
P操作(申请资源)
V操作(释放资源)
>P(S) 含义如下:
if(信号量的值大于0) {
申请资源的任务继续运行;
信号量的值减一;
}else {
申请资源的任务阻塞;
}
> V(S) 含义如下:
信号量的值加一;
if (有任务在等待资源) {
唤醒等待的任务,让其继续运行
}
4.Posix 信号量
> posix中定义了两类信号量:
> 无名信号量(基于内存的信号量)
> 有名信号量
> pthread库常用的信号量操作函数如下:
> int sem_init(sem_t *sem, int pshared, unsigned int value);
> int sem_wait(sem_t *sem); // P操作
> int sem_post(sem_t *sem); // V操作
5.信号量函数: sem_init sem_wait sem_post
a:信号量初始函数
//头文件:
#include <semaphore.h>
//函数原型:
int sem_init(sem_t *sem, int pshared, unsigned int val);
//参数:
sem 指向要初始化的信号量对象
pshared 0 – 线程间 1 – 进程间
val 信号量初值
//返回值:
成功时返回0,失败时EOF
b:信号量 p/v 操作
//头文件:
#include <semaphore.h>
//函数原型:
int sem_wait(sem_t *sem); P操作
int sem_post(sem_t *sem); V操作
//参数:
sem 指向要操作的信号量对象
//返回值:
成功时返回0,失败时返回EOF
========================================================================================
e.4 线程----信号量p/v操作程序示例:
//程序1: 两个线程同步读写缓冲区
#include <stdio.h>
#include <string.h>
#include <pthread.h>
char buf[32];
sem_t sem; //指向要初始化的信号量对象
void *function(void *arg);
int main(void) {
pthread_t a_thread; //线程对象
if (sem_init(&sem, 0, 0) < 0) {
perror("sem_ini"); exit(-1);
} //信号量初始化
if (pthread_create(&a_thread, NULL, function, NULL) != 0) {
printf("fail to pthread_creat");
exit(-1);
} //创建线程:调用function函数
printf("input 'quit' to exit\n");
do {
fgets(buf, 32, stdin);
sem_post(&sem); //v操作释放资源表示Buf中有数据了
} 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));
}
}
//读写操作时候,都应该进行P/V操作
//如果读操作时间太长,写操作就会将BUf中数据给覆盖。
//程序2 两个线程同步读写缓冲区
#include <stdio.h>
#include <string.h>
#include <pthread.h>
char buf[32];
sem_t sem_r, sem_w;
void *function(void *arg);
int main(void) {
pthread_t a_thread;
//初始化信号量
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”);
exit(-1);
}
printf(“input ‘quit’ to exit\n”);
do {
sem_wait(&sem_w);
fgets(buf, 32, stdin);
sem_post(&sem_r);
}while (strncmp(buf, “quit”, 4) != 0);
return 0;
}
void *function(void *arg) {
while ( 1 ) {
sem_wait(&sem_r);
printf(“you enter %d characters\n”, strlen(buf));
sem_post(&sem_w);
}
}
================================================================================
e.5 线程通信 – 互斥 --pthread_mutex_init–pthread_mutex_lock–pthread_mutex_unlock
//线程间同步互斥
//1.为了保证临界资源的完整性。
//比如全局资源,系统资源。
//创建线程之前所有的资源,都属于全局资源
//当你要对这个全局资源进行操作时,就会变成临界资源。
//线程间同步
//信号量(多个信号量),条件变量与互斥锁
//线程间互斥
//信号量(单个信号量),互斥锁
a.>临界资源
一次只允许一个任务(进程、线程)访问的共享资源
>临界区
访问临界区的代码
>互斥机制
mutex互斥锁
任务访问临界资源前申请锁,访问完后释放锁
b.互斥锁: "mutex"
1.互斥锁初始化 – pthread_mutex_init
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex,
const pthread_mutexattr_t * attr);
成功时返回0,失败时返回错误码
mutex 指向要初始化的互斥锁对象
attr 互斥锁属性,NULL表示缺省属性
2. 申请锁 – pthread_mutex_lock
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
成功时返回0,失败时返回错误码
mutex 指向要初始化的互斥锁对象
如果无法获得锁,任务阻塞
3. 释放锁 – pthread_mutex_unlock,pthread_cond_destroy
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
成功时返回0,失败时返回错误码
mutex 指向要初始化的互斥锁对象
执行完临界区要及时释放锁
int pthread_cond_destroy(pthread_cond_t *cond);
4.1:初始化条件变量pthread_cond_init
int pthread_cond_init(pthread_cond_t *cv,
const pthread_condattr_t *cattr);
返回值:函数成功返回0;任何其他返回值都表示错误
--> 当参数cattr为空指针时,函数创建的是一个缺省的条件变量。
--> 否则条件变量的属性将由cattr中的属性值来决定。
--> 调用 pthread_cond_init函数时,参数cattr为空指针等价于cattr中的属性为缺省属性,
--> 只是前者不需要cattr所占用的内存开销
这个函数返回时,条件变量被存放在参数cv指向的内存中
4.2:释放条件变量pthread_cond_destroy
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cv);
返回值:函数成功返回0;任何其他返回值都表示错误
释放条件变量。
注意:条件变量占用的空间并未被释放。
5. 阻塞在条件变量上pthread_cond_wait
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cv,
pthread_mutex_t *mutex);
返回值:函数成功返回0;任何其他返回值都表示错误
函数将解锁mutex参数指向的互斥锁,并使当前线程阻塞在cv参数指向的条件变量上。
被阻塞的线程可以被pthread_cond_signal函数,pthread_cond_broadcast函数唤醒,也可能在被信号中断后被唤醒。
pthread_cond_wait函数的返回并不意味着条件的值一定发生了变化,必须重新检查条件的值。
pthread_cond_wait函数返回时,相应的互斥锁将被当前线程锁定,即使是函数出错返回。
6.阻塞直到指定时间pthread_cond_timedwait
#include <pthread.h>
#include <time.h>
int pthread_cond_timedwait(pthread_cond_t *cv,
pthread_mutex_t *mp, const structtimespec * abstime);
返回值:函数成功返回0;任何其他返回值都表示错误
函数到了一定的时间,即使条件未发生也会解除阻塞。这个时间由参数abstime指定。函数返回时,相应的互斥锁往往是锁定的,即使是函数出错返回。
注意:pthread_cond_timedwait函数也是退出点。
超时时间参数是指一天中的某个时刻。使用举例:
pthread_timestruc_t to;
to.tv_sec = time(NULL) + TIMEOUT;
to.tv_nsec = 0;
超时返回的错误码是ETIMEDOUT。
//线程互斥程序
#include <stdio.h>
#include <pthread.h>
unsigned int count, value1, value2;
pthread_mutex_t lock;
void *function(void *arg);
int main(void) {
pthread_t a_thread;
if (pthread_mutex_init(&lock, NULL) != 0) {
printf(“fail to pthread_mutex_init\n”); exit(-1);
}
if (pthread_create(&a_thread, NULL, function, NULL) != 0) {
printf(“fail to pthread_create”); exit(-1);
}
while ( 1 ) {
count++;
#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
……
使用互斥锁
$ gcc –o test test.c –lpthread –D_LOCK_
$ ./test
/***************************************************************************************************/
c.条件锁初始化
//1.初始化条件变量pthread_cond_init
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cv, const pthread_condattr_t *cattr);
//参数:
cond是一个指向结构pthread_cond_t的指针
attr是一个指向结构pthread_condattr_t的指针
结构pthread_condattr_t是条件变量的属性结构,
和互斥锁一样我们可以用它来设置条件变量是进程内可用还是进程间可用,
默认值是PTHREAD_ PROCESS_PRIVATE,即此条件变量被同一进程内的各个线程使用;
如果选择为PTHREAD_PROCESS_SHARED则为多个进程间各线程公用。
注意初始化条件变量只有未被使用时才能重新初始化或被释放
// 返回值:
函数成功返回0;任何其他返回值都表示错误
初始化一个条件变量。当参数cattr为空指针时,函数创建的是一个缺省的条件变量,否则条件变量的属性将由cattr中的属性值来决定。
调用 pthread_cond_init函数时,参数cattr为空指针等价于cattr中的属性为缺省属性,只是前者不需要cattr所占用的内存开销。
这个函数返回时,条件变量被存放在参数cv指向的内存中。
可以用宏PTHREAD_COND_INITIALIZER来初始化静态定义的条件变量,使其具有缺省属性。
这和用pthread_cond_init函数动态分配的效果是一样的。初始化时不进行错误检查。如:
pthread_cond_t cv = PTHREAD_COND_INITIALIZER;
不能由多个线程同时初始化一个条件变量。
当需要重新初始化或释放一个条件变量时,应用程序必须保证这个条件变量未被使用。
//例子 :
1.//先定义互斥锁变量,条件变量:
pthread_mutex_t mymutex;
pthread_cond_t mycond_reverse = PTHREAD_COND_INITIALIZER;
pthread_cond_t mycond_printf = PTHREAD_COND_INITIALIZER;
2.//条件锁,互斥所初始化
pthread_cond_init(&mycond_printf,NULL);
pthread_cond_init(&mycond_reverse,NULL);
pthread_mutex_init(&mymutex,NULL);
3.//创建线程
pthread_create(&tid[0],NULL,reverse_msgbuf,(void *)&msg);
pthread_create(&tid[1],NULL,printf_msgbuf,(void *)&msg);
// 2.阻塞在条件变量上pthread_cond_wait
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cv,pthread_mutex_t *mutex);
//参数:
cv :pthread_cond_t 条件变量:
mutex:互斥锁变量
函数将解锁mutex参数指向的互斥锁,并使当前线程阻塞在cv参数指向的条件变量上。
//唤醒条件:
被阻塞的线程可以被pthread_cond_signal函数,pthread_cond_broadcast函数唤醒,也可能在被信号中断后被唤醒。
//返回值:
函数成功返回0;任何其他返回值都表示错误
pthread_cond_wait函数的返回并不意味着条件的值一定发生了变化,必须重新检查条件的值。
pthread_cond_wait函数返回时,相应的互斥锁将被当前线程锁定,即使是函数出错返回。
一般一个条件表达式都是在一个互斥锁的保护下被检查。
当条件表达式未被满足时,线程将仍然阻塞在这个条件变量上。
当另一个线程改变了条件的值并向条件变量发出信号时,
等待在这个条件变量上的一个线程或所有线程被唤醒,接着都试图再次占有相应的互斥锁。
阻塞在条件变量上的线程被唤醒以后,直到pthread_cond_wait()函数返回之前条件的值都有可能发生变化。
所以函数返回以后,在锁定相应的互斥锁之前,必须重新测试条件值。
最好的测试方法是循环调用pthread_cond_wait函数,并把满足条件的表达式置为循环的终止条件。如:
pthread_mutex_lock();
while (condition_is_false)
pthread_cond_wait();
pthread_mutex_unlock();
阻塞在同一个条件变量上的不同线程被释放的次序是不一定的。
注意:pthread_cond_wait()函数是退出点,如果在调用这个函数时,已有一个挂起的退出请求,
且线程允许退出,这个线程将被终止并开始执行善后处理函数,而这时和条件变量相关的互斥锁仍将处在锁定状态。
//函数示例
void* printf_msgbuf(void* arg){
msg_t *msg = (msg_t *)arg;
while(1){
pthread_mutex_lock(&mymutex);
pthread_cond_wait(&mycond_printf,&mymutex);
printf(“printf_msgbuf 😗**********\n”);
printf(“printf_msgbuf :%s\n”,msg->buf);
pthread_mutex_unlock(&mymutex);
}
}
3.解除在条件变量上的阻塞pthread_cond_signal
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cv);
// 返回值:
函数成功返回0;任何其他返回值都表示错误
函数被用来释放被阻塞在指定条件变量上的一个线程。
必须在互斥锁的保护下使用相应的条件变量。
否则对条件变量的解锁有可能发生在锁定条件变量之前,从而造成死锁。
唤醒阻塞在条件变量上的所有线程的顺序由调度策略决定,
如果线程的调度策略是SCHED_OTHER类型的,系统将根据线程的优先级唤醒线程。
如果没有线程被阻塞在条件变量上,那么调用pthread_cond_signal()将没有作用。
//函数示例
while(1){
pthread_cond_signal (&mycond_printf);
sleep(1);
pthread_cond_signal (&mycond_reverse);
sleep(1);
}
4.阻塞直到指定时间pthread_cond_timedwait
#include <pthread.h>
#include <time.h>
int pthread_cond_timedwait(pthread_cond_t *cv,
pthread_mutex_t *mp, const structtimespec * abstime);
返回值:函数成功返回0;任何其他返回值都表示错误
函数到了一定的时间,即使条件未发生也会解除阻塞。这个时间由参数abstime指定。
函数返回时,相应的互斥锁往往是锁定的,即使是函数出错返回。
注意:pthread_cond_timedwait函数也是退出点。
超时时间参数是指一天中的某个时刻。使用举例:
pthread_timestruc_t to;
to.tv_sec = time(NULL) + TIMEOUT;
to.tv_nsec = 0;
超时返回的错误码是ETIMEDOUT。
5.释放阻塞的所有线程pthread_cond_broadcast
#include <pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cv);
返回值:函数成功返回0;任何其他返回值都表示错误
函数唤醒所有被pthread_cond_wait函数阻塞在某个条件变量上的线程,参数cv被用来指定这个条件变量。
当没有线程阻塞在这个条件变量上时,pthread_cond_broadcast函数无效。
由于pthread_cond_broadcast函数唤醒所有阻塞在某个条件变量上的线程,
这些线程被唤醒后将再次竞争相应的互斥锁,所以必须小心使用pthread_cond_broadcast函数。
pause() ;
6.释放条件变量pthread_cond_destroy
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cv);
返回值:函数成功返回0;任何其他返回值都表示错误
释放条件变量。
注意:条件变量占用的空间并未被释放。
7.唤醒丢失问题
在线程未获得相应的互斥锁时调用pthread_cond_signal或pthread_cond_broadcast函数可能会引起唤醒丢失问题。
唤醒丢失往往会在下面的情况下发生:
一个线程调用pthread_cond_signal或pthread_cond_broadcast函数;
另一个线程正处在测试条件变量和调用pthread_cond_wait函数之间;
没有线程正在处在阻塞等待的状态下
#include <stdio.h>
#include <pthread.h>
unsigned int count,value1,value2;
pthread_mutex_t lock;
pthread_cond_t mycon1 =PTHREAD_COND_INITIALIZER;
pthread_cond_t mycon =PTHREAD_COND_INITIALIZER;
void *function1(void *arg);
void *function(void *arg);
int flag=0;
int main(int argc, const char *argv[])
{
pthread_t a_thread1,a_thread;
pthread_mutex_init(&lock,NULL);
pthread_cond_init(&mycon,NULL);
pthread_create(&a_thread1,NULL,function1,NULL);
pthread_create(&a_thread,NULL,function,NULL);
count = 1;
#if 0
while(1){
pthread_cond_signal (&mycon1);
sleep(1);
}
#endif
pause(); //广播
return 0;
}
void *function1(void*arg){
while(1){
#ifdef LOCK
pthread_mutex_lock(&lock);
while(flag !=0){
#if 0
pthread_cond_wait(&mycon1,&lock);
#endif
pthread_cond_wait(&mycon,&lock); //广播
}
#endif
value1 = count;
count++;
value2 = count;
#ifdef LOCK
flag=1;
#if 0
pthread_cond_signal(&mycon);
#endif
pthread_cond_broadcast(&mycon);//广播
pthread_mutex_unlock(&lock);
#endif
}
return NULL;
}
void *function(void *arg) {
while(1){
#ifdef LOCK
pthread_mutex_lock(&lock);
while(flag != 1){
pthread_cond_wait(&mycon,&lock);
}
#endif
if (value1 != value2) {
printf(“var_rangeslue1 = %u, value2 = %u\n”, value1, value2);
usleep(100000);
}
#ifdef LOCK
flag = 0;
#if 0
// pthread_cond_signal (&mycon1);
#endif
pthread_cond_broadcast(&mycon);//广播
pthread_mutex_unlock(&lock);
#endif
}
return NULL;
}
/***************************************************************************************************/
// f.进程间通信:对于进程:重点学习的是进程间的通信机制
消息队列和FIFO一样,当读走之后数据便不存在消息队列当中了。
注意:消息队列,信号灯集,共享内存创建在内核当中,需要手动释放,否则任然占用内存
1. UNIX进程间通信方式
无名管道(pipe)
有名管道 (fifo)
信号(signal)
2. System V IPC
共享内存(share memory)
消息队列(message queue)
信号灯集(semaphore set)
3. 套接字(socket)
进程间的通信方式总结:
pipe: 具有亲缘关系的进程间,单工,数据在内存中 (无名管道)
fifo: 可用于任意进程间,双工,有文件名,数据在内存 内容保存在内核中,对外提供名称fifo用于通信
signal: 唯一的异步通信方式
msg:常用于cs模式中, 按消息类型访问 ,可有优先级
shm:效率最高(直接访问内存) ,需要同步、互斥机制
sem:配合共享内存使用,用以实现同步和互斥
socket: 网络通信
f.1 无名管道概念:
-----> 进程在用户空间内是相互独立的,无法在用户空间内进行通信,但是他们都可以访问内核,那么他们可以
通过在内核中创建一个对象,然后共同访问同一个对象来进行通信。
进程1 进程2
| |
| |
|---------------------------|
| 内核 |
| |
| |
|---->管道 ---由队列实现<---|
|___________________________|
f.2 无名管道特点:
1、当管道中没有数据的时候,读阻塞;
>无名管道创建时会返回两个文件描述符,分别用于读写管道
2、当写端关闭时,读操作不阻塞,而是直接返回。
当读端关闭时,对管道进行写操作会管道破裂,程序直接退出。
3、管道里面的东西,读完就没有了
4、当管道被写满时,写阻塞(管道大小为65536)
f.3 无名管道创建 – pipe
#include <unistd.h>
int pipe(int pfd[2]);
成功时返回0,失败时返回EOF
pfd 包含两个元素的整形数组,用来保存文件描述符
pfd[0]用于读管道;pfd[1]用于写管道
// 管道函数示例:
子进程1和子进程2分别往管道中写入字符串;父进程读管
道内容并打印;
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main(void) {
pid_t pid1, pid2;
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”);
write(pfd[1], buf, 32);
exit(0);
}
else { // 父进程
if ((pid2 = fork()) < 0) {
perror(“fork”);
exit(-1);pi
}
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);
printf(“%s\n”, buf);
wait(NULL);
read(pfd[0], buf, 32);
printf(“%s\n”, buf);
}
}
return 0;
}
f.4 无名管道读写特性
// a.写端存在
有数据 :read返回实际读取的字节数
无数据 :进程读阻塞
// b.写端不存在
有数据 :read返回实际读取的字节数
无数据 :read返回0
// c.读端存在
有空间 :write返回实际写入的字节数
无空间 :进程写阻塞
// d.读端不存在
有空间
无空间
管道断裂!
// e.如何获取无名管道的大小?
循环写入管道,直到阻塞
统计循环次数
//获取管道大小程序:
#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;
}
// f.如何验证管道断裂(进程被信号结束)?
子进程写管道
父进程回收
//管道断裂程序:
#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); // 回收子进程
printf("%x\n", status); // 打印子进程退出状态
}
return 0;
}
f.5 有名管道:
> 有名管道可以使互不相关的两个进程互相通信。
> 有名管道可以通过路径名来指出,并且在文件系统中可见。
> 本质上是操作文件设备节点
> 打开管道时可指定读写方式
> 通过文件IO操作,内容存放在内存中
1.有名管道创建 – mkfifo
#include <unistd.h>
#include <fcntl.h>
int mkfifo(const char *path, mode_t mode);
path 创建的管道文件路径
mode 管道文件的权限,如0666
成功时返回0,失败时返回EOF
//有名管道函数示例:
进程A:循环从键盘输入并写入有名管道myfifo,输入quit时退出
进程B:循环统计进程A每次写入myfifo的字符串的长度
N1:创建管道程序:
/* create_fifo.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
if(mkfifo(“myfifo”, 0666) < 0) {
perror(“mkfifo”);
exit(-1);
}
return 0;
}
N2:写管道程序:
/* write_fifo.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#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);
if (strcmp(buf, “quit\n”) == 0)
break;
write(pfd, buf, N);
}
close(pfd);
return 0;
}
N3.读管道程序:
/* read_fifo.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define N 32
int main(void) {
char buf[N];
int pfd;
if ((pfd = open(“myfifo”, O_RDONLY)) < 0) {
perror(“open”);
exit(-1);
}
while (read(pfd, buf, N) > 0) {
printf(“the length of string is %d\n”, strlen(buf));
}
close(pfd);
return 0;
}
f.6 信号机制
1.//信号概念:
2.//常用信号1
3.信号发送 、定时器、 信号捕捉
kill/raise alarm/pause signal
函数1:信号发送 – kill / raise
函数2:alarm 定时器
函数3:让进程睡眠直到有信号到来 pause(void);
函数4: 设置信号响应方式 – signal
1.//信号概念:
> 信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式
> linux内核通过信号通知用户进程,不同的信号类型代表不同的事件
> Linux对早期的unix信号机制进行了扩展
> 进程对信号有不同的响应方式
缺省方式
忽略信号
捕捉信号
=============================================================
2.//常用信号1
信号名 含义 默认操作
SIGHUP 该信号在用户终端关闭时产生,
通常是发给和该终端关联的会话内的所有进程 终止
SIGINT 该信号在用户键入INTR字符(Ctrl-C)时产生,内
核发送此信号送到当前终端的所有前台进程 终止
SIGQUIT 该信号和SIGINT类似,但由QUIT字符(通常是 Ctrl-\)来产生 终 止
SIGILL 该信号在一个进程企图执行一条非法指令时产生 终止
SIGSEV 该信号在非法访问内存时产生,如野指针、缓冲区溢出 终止
SIGPIPE 当进程往一个没有读端的管道中写入时产生,代表“管道断裂” 终止
SIGKILL 该信号用来结束进程,并且不能被捕捉和忽略 终止
SIGSTOP 该信号用于暂停进程,并且不能被捕捉和忽略 暂停进程
SIGTSTP 该信号用于暂停进程,用户可键入SUSP字符
(通常是Ctrl-Z)发出这个信号 暂停进程
SIGCONT 该信号让进程进入运行态 继续运行
SIGALRM 该信号用于通知进程定时器时间已到 终止
SIGUSR1/2 该信号保留给用户程序使用 终止
信号相关命令 kill / killall
kill [-signal] pid
默认发送SIGTERM
-sig 可指定信号
pid 指定发送对象
killall [-u user | prog]
prog 指定进程名
user 指定用户名
<1.> 信号的发送 信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式
kill - send a signal to a process or a group of processes
信号的发送:
kill raise alarm
<2.> 信号的接收 int pause(void)
<3.> 信号的处理signal 信号的处理
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
//等价于void (*signal(int signum, void (*handler)(int)))(int);
DESCRIPTION
signal() sets the disposition of the signal signum to handler, which is
either SIG_IGN, SIG_DFL, or the address of a programmer-defined func‐tion (a "signal handler").
功能:自定义对信号的处理,即改变原有信号的响应 --- 自定义
参数:signum:需要设定信号的名字
handler:一个函数指针,指向一个返回值为void,参数为int型的这么一个函数
返回值:返回handler函数的地址
=======================================================================================
3.信号发送 、定时器、 信号捕捉
kill/raise alarm/pause signal
// 函数1:
信号发送 – 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 信号类型
//函数2:alarm 定时器
//函数原型:
int alarm(unsigned int seconds);
//返回值:
成功时返回上个定时器的剩余时间,失败时返回EOF
//参数:
seconds 定时器的时间 0:取消定时器
一个进程中只能设定一个定时器,时间到时产生SIGALRM
//函数3: //让进程睡眠直到有信号到来
int pause(void);
进程一直阻塞,直到被信号中断
被信号中断后返回-1,errno为EINTR
//时钟信号函数程序示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
alarm(3); //3秒以后发送定时器信号
pause(); //为了是进程运行时间达到3秒
//让进程进入阻塞状态,直到被信号打断,结束进程
printf(“I have been waken up!\n”);
return 0;
}
$ ./a.out
Alarm clock
//函数4: 设置信号响应方式 – signal
//头文件:
#include <unistd.h>
#include <signal.h>
//函数原型:
void (*signal(int signo, void (*handler)(int)))(int);
//返回值:<函数指针>
成功时返回原先的信号处理函数,失败时返回SIG_ERR
返回handler函数的地址
//参数:
signo 要设置的信号类型
handler 指定的信号处理函数: SIG_DFL代表缺省方式; SIG_IGN 代表忽略信号;
handler:一个函数指针,指向一个返回值为void,参数为int型的这么一个函数
----------------------------------------------------------------------------
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
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);
//signal仅仅是把信号类型和信号处理函数关联起来。
while ( 1 ) pause();
return 0;
}
f.7 IPC对象
1.IPC对象操作
2.IPC操作流程
3.ftok KEY生成函数
4.IPC对象共享内存
(打开shmget、内存映射shmat、撤销映射shmdt、删除共享内存 shmctl)
5.IPC对象消息队列
a.消息队列概要
b.消息队列机制
消息的格式
1.创建打开 msgget
2.发送消息 msgsnd
3.读取消息 msgrcv
4.控制消息 msgctl
6.IPC对象信号灯集:
//a.信号灯概念:
//b.信号灯使用步骤
1.创建一个键值用于标识信号灯集
key, ftok()返回值
2.打开/创建信号灯 semget
3.信号灯初始化 semctl
4.P/V操作 semop sembuf
5.删除信号灯 semctl
1.IPC对象操作
--> IPC对象:共享内存,消息队列,信号灯集
--> 每个IPC对象有唯一的ID
--> IPC对象创建后一直存在,直到被显式地删除
--> 每个IPC对象有一个关联的KEY
--> ipcs / ipcrm
可通过命令
ipcs -a查看所有的IPC对象使用情况
ipcs -m 查看共享内存
ipcs -q 查看消息队列
ipcs -s 查看信号灯集
ipcrm -m shmid 删除shmid的共享内存
ipcrm -q msgid 删除消息队列
ipcrm -s semid 删除信号灯集
2.IPC操作流程:
-->pathname id-->ftok()-->key_t key----------|
//创建一个不为0---key值 V
Msg_get
Shm_get ---> int id ---> msgsnd msgrecv msgctrl
Sem_get shmctrl shmat shmdt
^ semctrl semop
|
|
Key 为IPC_PRIVATE ----------------------------|
==================================================
3.ftok KEY生成函数
//头文件:
#include <sys/types.h>
#include <sys/ipc.h>
//函数原型:
key_t ftok(const char *path, int proj_id);
// 返回值:
成功时返回合法的key值,失败时返回EOF
//参数:
path 存在且可访问的文件的路径
proj_id 用于生成key的数字,不能为0
#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;
if ((key = ftok(".", ‘a’)) == -1) {
perror(“key”);
exit(-1);
}
4.IPC对象共享内存 (打开shmget、内存映射shmat、撤销映射shmdt、删除共享内存 shmctl
共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝
共享内存在内核空间创建,可被进程映射到用户空间访问,使用灵活
由于多个进程可同时访问共享内存,因此需要同步和互斥机制配合使用
________________ _________________
|_____客户端_____| |_____客户端_____|
| |
| |
|___________共享内存____________|
服务器先启动:共享内存在内核空间
// 共享内存 使用步骤:
1-创建打开共享内存
int shmget(key_t key, size_t size, int shmflg);
|
2-映射共享内存,吧指定的共享内存映射到进程的地址空间进行访问
void *shmat(int shmid, const void *shmaddr, int shmflg);
|
3-对共享内存的空间进行读写操作(一次读写整块共享内存空间,是优势也是劣势)
fgets(tmpbuf,sizeof(tmpbuf),stdin); //写操作
strncpy(shm_node->textbuf,tmpbuf,TEXTSIZE); //读操作
|
4-撤销共享内存映射
int shmdt(const void *shmaddr);
|
5-删除共享内存对象
int shmctl(int shmid, int IPC_RMID, struct shmid_ds *buf);
// 共享内存函数1:
共享内存创建 – shmget
//头文件:
#include <sys/ipc.h>
#include <sys/shm.h>
//函数原型:
int shmget(key_t key, int size, int shmflg);
//返回值:
成功时返回共享内存的id,失败时返回EOF
//参数:
key 和共享内存关联的key,IPC_PRIVATE 或 ftok生成
shmflg 共享内存标志位 IPC_CREAT|0666
/*ftok与shmget函数程序示例:
要求:创建/打开一个和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);
}
//共享内存函数2:
共享内存映射 – 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表示只读
//共享内存读写
通过指针访问共享内存,指针类型取决于共享内存中存放的数据类型
例如:在共享内存中存放键盘输入的字符串
char *addr;
int shmid;
……
if ((addr = (char *)shmat(shmid, NULL, 0)) == (char *)-1) {
perror(“shmat”);
exit(-1);
}
fgets(addr, N, stdin);
//共享内存函数3:
共享内存撤销映射 – shmdt
#include <sys/ipc.h>
#include <sys/shm.h>
int shmdt(void *shmaddr);
成功时返回0,失败时返回EOF
不使用共享内存时应撤销映射
进程结束时自动撤销
//共享内存函数4:
删除共享内存对象
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
成功时返回0,失败时返回EOF
shmid 要操作的共享内存的id
cmd 要执行的操作 IPC_STAT IPC_SET IPC_RMID
buf 保存或设置共享内存属性的地址
// 共享内存 - 注意事项
每块共享内存大小有限制
ipcs -l
cat /proc/sys/kernel/shmmax
共享内存删除的时间点
shmctl(shmid, IPC_RMID, NULL) 添加删除标记
nattach 变成0时真正删除
//共享内存示例程序:
#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;
char *addr;
int shmid;
if ((key = ftok(".", 'a')) == -1)
{ //0-ftok 唯一KEY生成函数
perror("key");
exit(-1);
}
if ((shmid = shmget(key, 1024, IPC_CREAT|0666)) < 0)
{ //1-创建打开共享内存
perror("shmget");
exit(-1);
}
if ((addr = (char *)shmat(shmid, NULL, 0)) == (char *)-1)
{ //2-映射共享内存,吧指定的共享内存映射到进程的地址空间进行访问
perror("shmat");
exit(-1);
}
fgets(addr, N, stdin);
//3-对共享内存的空间进行读写操作(一次读写整块共享内存空间,是优势也是劣势)
shmdt(addr);
//4-撤销共享内存映射
shmctl(shmid, IPC_RMID, NULL);
// 5-删除共享内存对象
return 0;
}
================================================================================================================
5.IPC对象消息队列
a.消息队列概要
b.消息队列机制
消息的格式
1.创建打开 msgget
2.发送消息 msgsnd
3.读取消息 msgrcv
4.控制消息 msgctl
//a.消息队列概要:
消息队列是System V IPC对象的一种
消息队列由消息队列ID来唯一标识
消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等
消息队列可以按照类型来发送/接收消息
//b.消息队列机制
消息队列:
消息的格式
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data 消息正文,自己定义自己的消息类型*/
......
};
size_t msgsz = sizeof(struct msgbuf) - sizeof(long)
1.创建、打开消息队列
int msgget(key_t key, int msgflg);功能:创建或打开一个消息队列
|
2.发送消息
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
|
3.读取消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
|
4.控制消息队列 //IPC_RMID表示删除消息队列
int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );
//
// c.打开/创建消息队列 msgget 向消息队列发送消息 msgsnd
// 从消息队列接收消息 msgrcv 控制消息队列 msgctl
1.//函数1 消息队列创建/打开 – msgget
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
成功时返回消息队列的id,失败时返回EOF
key 和消息队列关联的key IPC_PRIVATE 或 ftok
msgflg 标志位 IPC_CREAT|0666
1.1//消息队列创建/打开 - 示例
……
int main() {
int msgid;
key_t key;
if ((key = ftok(“.”, ‘q’)) == -1) {
perror(“ftok”); exit(-1);
}
if ((msgid = msgget(key, IPC_CREAT|0666)) < 0) {
perror(“msgget”);
exit(-1);
}
……
return 0;
}
2.//函数2 消息队列消息发送 – msgsnd
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msgid, const void *msgp, size_t size,
int msgflg);
成功时返回0,失败时返回-1
msgid 消息队列id
msgp 消息缓冲区地址
size 消息正文长度
msgflg 标志位 0 或 IPC_NOWAIT
2.1 // 消息格式
通信双方首先定义好统一的消息格式
用户根据应用需求定义结构体类型
首成员类型为long,代表消息类型(正整数)
其他成员都属于消息正文
2.2//消息发送示例:
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);
msgsnd(msgid, &buf,LEN, 0);
……
return 0;
}
3.//函数1 消息队列接收消息 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 指定接收的消息类型
msgflg 标志位 0 或 IPC_NOWAIT
3.1//消息接收示例:
typedef struct {
long mtype;
char mtext[64];
} MSG;
#define LEN (sizeof(MSG) – sizeof(long))
int main() {
MSG buf;
……
if (msgrcv(msgid, &buf,LEN, 200, 0) < 0) {
perror(“msgrcv”);
exit(-1);
}
……
}
4.//函数1 消息队列控制消息队列 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 存放消息队列属性的地址
//示例练习:
要求:两个进程通过消息队列轮流将键盘输入的字符串发送给对方,接收并打印对方发送的消息
改进思路:
通过多进程/线程实现同时收发消息
引入服务器端,实现不同通信方式
===========================================================================================
6.IPC对象信号灯集:
//a.信号灯概念:
//b.信号灯使用步骤
1.创建一个键值用于标识信号灯集
key, ftok()返回值
2.打开/创建信号灯 semget
3.信号灯初始化 semctl
4.P/V操作 semop sembuf
5.删除信号灯 semctl
//a.信号灯概念:
==> 信号灯也叫信号量,用于进程/线程同步或互斥的机制
==> 信号灯的类型
Posix 无名信号灯
Posix有名信号灯
System V 信号灯
==> 信号灯的含义
信号灯
System V 信号灯是一个或多个计数信号灯的集合
可同时操作集合中的多个信号灯
申请多个资源时避免死锁
//b.信号灯使用步骤:
1.创建一个键值用于标识信号灯集
key, ftok()返回值
2.打开/创建信号灯 semget
int semget(key_t key, int nsems, int semflg);
=================================================================
// 头文件
#include <sys/ipc.h>
#include <sys/sem.h>
//函数原型
int semget(key_t key, int nsems, int semflg);
//返回值
成功时返回信号灯的id,失败时返回-1
//参数
key 和消息队列关联的key IPC_PRIVATE 或 ftok
nsems 集合中包含的计数信号灯个数
semflg 标志位 IPC_CREAT|0666|IPC_EXCL
if((semid = semget(key,3,IPC_CREAT|0666|IPC_EXCL)) < 0 )
{
if(errno == EEXIST){
semid = semget(key,3,0666)
}
}
else{
//初始化
}
3.信号灯初始化 semctl
功能:信号量集合的控制 ----初始化 ----删除 union semun填充
int semctl(int semid, int semnum, SETVAL, union semun arg...);
=================================================================
// 头文件
#include <sys/ipc.h>
#include <sys/sem.h>
//函数原型
int semctl(int semid, int semnum, int cmd, …);
//返回值
成功时返回0,失败时返回EOF
//参数
semid 要操作的信号灯集id
semnum 要操作的集合中的信号灯编号
cmd 执行的操作 SETVAL IPC_RMID
union semun 取决于cmd
//初始化函数示例:
假设信号灯集合中包含两个信号灯;第一个初始化
为2,第二个初始化为0
union semun myun;
myun.val = 2;
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);
}
==================================================================
4.P/V操作 semop sembuf
=================================================================
// 头文件
#include <sys/ipc.h>
#include <sys/sem.h>
//函数原型
int semop(int semid, struct sembuf *sops, unsigned nsops);
//返回值
成功时返回0,失败时返回-1
//参数
semid 要操作的信号灯集id
sops 描述对信号灯操作的结构体(数组)
nsops 要操作的信号灯的个数
struct sembuf
{
short semnum;
short sem_op;
short sem_flg;
};
semnum 信号灯编号
sem_op -1:P操作 1:V操作
sem_flg 0 (阻塞)/ IPC_NOWAIT(非阻塞)
struct sembuf buf[3];
buf[0].semnum = 0;
buf[0].sem_op = -1;
buf[0].sem_flg = 0;
semop(semid,buf,2);
==================================================================
**对信号量数组semnum编号的信号量做P操作 P操作资源 -1***/
int P(int semid, int semnum){
struct sembuf sopsp={semnum,-1, 0};//SEM_UNDO};
return (semop(semid,&sopsp,1)); }
|
/***对信号量数组semnum编号的信号量做V操作 V操作资源 +1 ***/
int V(int semid, int semnum){
struct sembuf sopsv={semnum, +1,0};// SEM_UNDO};
return (semop(semid,&sopsv,1)); }
5.删除信号灯 semctl
int semctl(int semid, int semnum, IPC_RMID, union semun arg...);
//程序例子:
父子进程通过System V信号灯同步对共享内存的读写
父进程从键盘输入字符串到共享内存
子进程删除字符串中的空格并打印
父进程输入quit后删除共享内存和信号灯集,程序结束
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.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};
pid_t pid;
key_t key;
char *shmaddr;
if((key = ftok(".",'s')) == -1)
{
perror("ftok");
exit(-1);
}
//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("shmat");
goto _error2;
}
//创建子进程
if((pid =fork()) < 0)
{
perror("fork");
goto _error2;
}
else if(pid ==0)
{
while(1)
{
char *p,*q;
pv(semid,READ,-1); //子进程中进行P操作查看是否有数据可以进行读操作
p=q = shmaddr; //指针都指向首地址
while(*q){
if(*q !=' ')
{
*p++ = *q;
}
q++;
}
*p ='\0';
printf("%s\n",shmaddr);
pv(semid,WRITE,1); //子进程进行V操作告诉父进程可以继续进行写操作
}
}else{
while(1){
pv(semid,WRITE,-1); //父进程进程P操作查看是否有空间进行写操作
printf("input>");
fgets(shmaddr,N,stdin);
if(strcmp(shmaddr,"quit\n") == 0)
break;
pv(semid,READ,1); //进程读V操作告知子进程可以进行读操作
}
kill(pid,SIGUSR1); //退出,结束子进程
}
_error2:
semctl(semid,0,IPC_RMID);
_error1:
shmctl(shmid, IPC_RMID, NULL);
}
1、标准IO、文件IO相关函数?
2、标准IO、文件IO相关函数?
3、程序与进程的区别?
4、父进程子进程-----Fork执行过程?
5、守护进程:
6、进程线程区别优劣势?
7、线程间间通信、同步与互斥?
8、进程间通信方式?并对比进程间通信的几种方式?
9、前边六种进程间通信与socket有什么本质区别?
10、共享内存步骤
11、消息队列中怎么优先访问哪一个消息? 设置优先级
12、假设工程项目做完,怎么提交给客户?
13、TCP/UDP区别/TCP 可靠传输
14、TCP模型及各层作用?
15、实现加密在哪一层?
16、SOCK、端口号、IP、字节序
17、MAC地址 (ARP/RARP)
18、TCP/UDP通信流程
19、三次、四次握手
20.请解释网络编程中的“心跳检测”概念和给出实现方法