UNIX环境高级编程——进程管理和通信(总结)
进程管理与通信
进程的管理
进程和程序的区别:
- 进程: 程序的一次执行过程 动态过程,进程的状态属性会发生变化
- 程序:存放在磁盘上的指令、数据的有序集合 是个文件,可直观看到
程序program 静态的概念,本身不会发生变化。指令谁来执行,数据谁来访问?cpu!
但前提是cpu能够接触到,程序执行过程需要cpu、内存、以及相关的资源。
进程是动态的,需要执行时才创建,运行结束要回收,包括创建、调度、执行、消亡的过程。
二者是关系:无程序进程就无意义,是内容与形式的关系。
一个程序的执行,至少创建一个进程。
一个进程的内容 叫进程控制卡,PCB,它是个理论上的东西,不同的系统实现不一样,Linux里用task_struct 来描述
进程的特点:
- 动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的
- 并发性:任何进程都可以同其他进程一起并发执行
- 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
- 异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推。
进程通讯原因:
- 数据传输
- 资源共享
- 通知事件
- 进程控制
- 进程互斥:多个进程同时要访问一个共享资源时,同时只允许一个进程访问,直到访问结束,释放后其他进程才能访问。
- 临界资源:同一时刻只允许一个进程访问的资源,进程中访问临界资源的那段程序代码称为临界资源。
- 进程同步:一组并发的进程按一定的顺序执行,具有同步关系的一组并发进程为合作进程,合作进程间互相发送的信号称为消息或事件。
- 死锁:多个进程竞争一种资源而形成的一种僵局,若无外力作用,这些进程无法继续向前推进。解决方法:主要有预防,使进程访问资源的顺序一致。
进程的类型:
- 交互进程
- 批处理
- 守护进程
进程的状态
- 运行态(正在运行和准备运行的)
- 等待(可中断等待、不可中断等待)
- 僵尸态
- 停止态
进程的执行模式
- 用户模式
- 内核模式
进程的调度
概念:按一定算法从一组待运行的进程中选出一个来占有CPU运行的。
调度方式:
- 抢占式
- 非抢占式
调度算法:
- 先来先服务
- 短进程优先
- 高优先级进程优先
- 时间片轮转法
进程命令
ps –aux
top
kill (向特定PID发送信号)
bg/fg
nice/renice
创建进程:
#include<unistd.h>
#include<sys/types.h>
pid_t fork( void )
返回值:
成功返回两个值,子进程返回0,父进程返回子进程ID;
出错返回-1
函数说明:
一个现有进程可以调用fork函数创建一个新进程。由fork创建的新进程被称为子进程(child process)。fork函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回0值而父进程中返回子进程ID。子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。
注意:
子进程持有的是上述存储空间的“副本”,这意味着父子进程间不共享这些存储空间。linux将复制父进程的地址空间内容给子进程,因此,子进程有了独立的地址空间。
#include<unistd.h>
pid_t vfork( void )
函数说明
vfork创建的子进程与父进程共享数据段,而且由vfork创建的。子进程将先于父进程运行。
返回值:
如果vfork()成功则在父进程会返回新建立的子进程代码(PID),而在新建立的子进程中则返回0。如果vfork失败则直接返回-1,失败原因存于errno中。
错误代码 :
EAGAIN 进程数已达系统规定上限
ENOMEM 内存不足,无法配置核心所需的数据结构空间。
vfork()用法与fork()异同点:
- fork():子进程拷贝父进程的数据段,代码段. vfork():子进程与父进程共享数据段.
- fork():父子进程的执行次序不确定。vfork():保证子进程先运行,在调用exec或exit之前与父进程数据是共享的,在它调用exec,或exit之后父进程才可能被调度运行
- vfork()保证子进程先运行,在她调用exec或exit之后父进程才可能被调度运行。
创建子进程,(写时拷贝)拷贝数据段、代码 哪些不拷贝?(完整拷贝需要与线程区别)
进程的退出
- return 0
- void exit( int status )<stdlib.h>
- void _exit( int status ) <unistd.h>
参数:
status 传递进程结束时的状态,通常0表示正常结束,非零表示出现错误,可以使用wait系统调用来接收子进程的返回值。
exit( )和_exit( )区别
- _exit( ) 直接结束进程,清除其使用的内存空间,销毁其在内核中的数据结构,不刷新缓冲区。
- exit( ) 退出时要清理缓存区
exec函数族
找到可执行文件,用它来取代原调用进程的数据段、代码、栈。进程号除外。
#include <unistd.h>
int execl (
const char *path,
const char *arg,
……
);
int execv (
const char *path,
char * const argv[ ]
);
int execle (
const char *path,
const char *arg,
…… ,
char * const envp[ ]
);
int execve(
const char *path,
char * const arg[ ],
…… ,
char * const envp[ ]
);
int execlp(
const char *file,
const char *arg,
……
);
int execvp (
const char *file,
char *const arg,
……
);
文件查找方式:
p 可以只给出文件名,系统会查找环境变量$PATH所包含的路径。
参数表传递方式:
逐个列举或者将所有参数通过指针数组传递。
l(list) 表示逐个列举。
v(vertor) 表示将所有参数构造成指针数组传递。
环境变量的使用:
e(enviromen) 可以在envp[ ]中传递当前进程所使用的环境变量
使用exec函数族时必须加上错误判断语句
常见的错误原因:
- 找不到文件或路径,errno被设置为ENOENT
- 数组argv和envp忘记用NULL结束,errno被设置为EFAULT
- 没有对应的可执行文件运行权限,errno被设置为EACCESS
进程的回收
<sys/types.h>
<sys/wait.h>
等待回收子进程的退出状态
pid_t wait(
int *status
);
返回值:
成功 >0 回收子进程的pid
失败 -1。
pid_t waitpid (
pid_t pid,
int * status,
int options
);
参数:
pid:
pid>0:只等待进程ID等于pid的子进程,不管已经有其他子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
pid=-1:等待任何一个子进程退出,此时和wait作用一样。
pid=0:等待其组ID等于调用进程的组ID的任一子进程。
pid<-1:等待其组ID等于pid的绝对值的任一子进程。
status
status指向的对象用来保存子进程退出时的状态
若为空,表示忽略子进程退出时的状态
若不为空,表示保存子进程退出时的状态
options
WNOHANG:若由pid指定的子进程并不立即可用,则waitpid不阻塞,此时返回值为0
WUNTRACED:若某实现支持作业控制,则由pid指定的任一子进程状态已暂停,且其状态自暂停以来还未报告过,则返回其状态。
0:同wait,阻塞父进程,等待子进程退出。
三种情况:
- 没有子进程或子进程都退出,立即返回-1
- 没有任何子进程结束,父进程会阻塞,直到有子进程退出。哪个先结束先回收哪个
- 有子进程结束,回收返回
wait/waitpid: waitpid(-1, NULL, 0) == wait(NULL)
最低字节的含义:0表示正常结束;非0表示被信号结束,值为信号的类型
第二字节:正常结束:子进程的返回值;非正常结束:值为0
应用:用父子进程实现文件的复制,每个进程各拷贝一份。
需注意:没打开一个文件,内核空间创建一个struct file结构体,里面有个成员叫f_ops。
./multi_copy <src_file> <dst_file>
步骤如下:
- 检查参数的个数
- 打开源文件、目标文件(注意打开方式和权限)
- 创建子进程
- 子进程中:关源、目标文件, 重新打开后复制后半部分
- 父进程中:复制前半部分
linux守护进程(Daemon进程)
守护进程:是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。
守护进程的特点:
- 是后台服务进程,生存期较长的进程,通常独立于控制终端,周期性的执行某种任务或等待处理某些发生的事件。
- 系统启动时开始运行,在系统关闭时终止
进程查看
ps –axj
父进程ID : PPID
进程ID : PID(标识进程的唯一描述符)
进程组ID : PGID
会话期ID : SID
终端ID : TTY
终端进程组ID : TPGID
状态 : STAT
用户 : UID
运行时间 : TIME
指令: COMMAND
守护进程的创建:
- fork ()创建子进程,父进程退出。
由于守护进程是脱离控制终端的,因此,完成第一步后就会在shell终端里造成一程序已经运行完毕的假象。之后的所有后续工作都在子进程中完成,而用户在shell终端里则可以执行其他的命令,从而在形式上做到了与控制终端的脱离
由于父进程已经先于子进程退出,会造成子进程没有父进程,从而变成一个孤儿进程。在Linux中,每当系统发现一个孤儿进程,就会自动由1号进程收养。原先的子进程就会变成init进程的子进程。
- setsid()子进程中创建新会话
进程组:
一个或多个进程的集合。进程组由进程组ID来唯一标识。
每个进程组都有一个组长进程,进程组ID就是组长进程的进程号。
会话期:一个或多个进程组的集合
setsid函数用于创建一个新的会话,并使得当前进程成为新会话组的组长
setsid函数能够使进程完全独立出来,从而脱离所有其他进程的控制。
#include <unistd.h>
pid_t setsid(void)
- chdir(“/”) 改变工作目录
通常的做法是让“/”或”/tmp”作为守护进程的当前工作目录 。
在进程运行过程中,当前目录所在的文件系统是不能卸载的。
chdir函数可以改变进程当前工作目录
- umask(0) 重设文件权限掩码
文件权限掩码是指文件权限中被屏蔽掉的对应位。把文件权限掩码设置为0,可以增加该守护进程的灵活性。设置文件权限掩码的
通常方法为umask(0)
- close(fd)关闭文件描述符
新建的子进程会从父进程那里继承所有已经打开的文件。
在创建完新的会话后,守护进程已经脱离任何控制终端,应当关闭用不到的文件。
fdtablesize = getdtablesize();
for (fd = 0; fd < fdtablesize; fd++)
close(fd);
这些被打开的文件可能永远不会被守护进程读或写,但它们一样消耗系统资源,而且可能导致所在的文件系统无法卸载
从终端输入的字符不可能达到守护进程,守护进程中用常规的方法(如printf)输出的字符也不可能在终端上显示出来。所以,文件描述符为0、1和2的三个文件(对应标准输入、标准输出和标准错误这三个流)已经失去了存在的意义,也应被关闭。
启动:
- 在Linux系统启动时从启动脚本/etc/rc.d中启动,由作业规划进程crond启动
- 用户终端(通常是 shell)执行。
进程间的通信
- AT&T的贝尔实验室,对Unix早期的进程间通信进行了改进和扩充,形成了“system V IPC”,其通信进程主要局限在单个计算机内
- BSD(加州大学伯克利分校的伯克利软件发布中心),跳过了该限制,形成了基于套接字(socket)的进程间通信机制
传统的进程间通信方式
- 无名管道(pipe)
- 有名管道(fifo)
- 信号(signal)
System V IPC对象
- 共享内存(share memory)
- 消息队列(message queue)
- 信号灯(semaphore)
BSD
- 套接字(socket)
UNIX IPC(InterProcess Communication)
无名管道pipe
特点:
- 只能用在有血缘关系的进程之间的通信、半双工(单方向的数据)。
- 类似于文件IO的读写,只存在于内存中。
创建:
#include <unistd.h>
int pipe(
int fd[2] //
fd:包含两个元素的整型数组(
fd[0]固定用于读管道,而fd[1]固定用于写管道。)
);
返回值
成功:0,
出错:-1.
pipe总结:
读端:
写端存在
- 管道中有数据,返回读取的字节数。
- 无数据,阻塞到有数据。
写端不存在
- 管道中有数据,返回读取的字节数。
- 无数据,返回0,所以,read返回0意味着写端关闭了。
写端:
读端存在
- 空间足,返回写入的字节数。
- 空间不足,有多少写多少,直到写完返回。
读端不存在
- 进程被SIGPIPE信号终止。
有名管道(FIFO)
特点:
- 类似队列、全双工(可读可写)
- 有名管道可以使互不相关的两个进程互相通信。有名管道可以通过路径名来指出,并且在文件系统中可见
- 进程通过文件IO来操作有名管道
- 有名管道遵循先进先出规则
- 不支持如lseek() 操作
创建管道:
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
int mkfifo(
const char *filename,//要创建的管道。
mode_t mode //指定创建的管道的访问权限,一般用8进制数表示.
);
返回值
成功:0
出错:-1,并设置相应的errno.
errno:
EACCESS 参数filename所指定的目录路径无可执行的权限
EEXIST 参数filename所指定的文件已存在
ENAMETOOLONG 参数filename的路径名称太长
ENOENT 参数filename包含的目录不存在
ENOSPC 文件系统的剩余空间不足
EROFS 参数filename指定的文件存在于只读文件系统内
fifo总结:
第一 O_RDONLY O_WRONLY
读端open(O_RDONLY…) 写端 open(O_WRONLY…), 只有读或只有写端时,open阻塞。验证,reader和writer程序,只运行一个。
写端关闭,只剩下读端时,read立即返回0.
读端关闭,只剩下写端时,写端被SIGPIPE信号终止。
在一般情况中(没有说明O_NONBLOCK),只读打开要阻塞到某个其他进程为写打开此FIFO。类似,为写而打开一个FIFO要阻塞到某个其他进程为读而打开它。
第二 O_NONBLOCK
当打开一个FIFO时,非阻塞标志(O_NONBLOCK)产生下列影响:
如果指定了O_NONBLOCK,则只读打开立即返回。但是,如果没有进程已经为读打开一个FIFO,那么,只写打开将出错返回,其errno是ENXIO。
第三 O_RDWR
读端和写端都是O_RDWR方式,两端都运行,若写端写完后退出了(输入quit)。则读端是以O_RDWR方式打开的,相当于自己也是一个写端,写端存在,又没东西读,也会阻塞。
信号
是软件层面对中断的一种模拟,唯一的异步方式。
进程队信号的响应方式:
- 缺省:Linux对每种信号都规定了默认操作(signal(SIGUSR1,SIG_DFL))。
- 忽略:但SIGKILL、SIGSTOP不能被忽略,只能按默认操作去执行(signal(SIGUSER1,SIG_IGN))。
- 捕捉:按指定的操作去执行(signal(SIGUSR1,fun//fun指向用户自定义函数的指针))。
关闭守护进程可发信号,普通用户只能给自己创建的进程发信号。
信号的生存周期
信号的使用场合:
- 后台进程需要使用信号,如xinetd
- 如果两个进程没有亲缘关系,无法使用无名管道
- 如果两个通信进程之一只能使用标准输入和标准输出,则无法使用FIFO
常用信号
信号名:含义(默认操作)
- SIGHUP:该信号在用户终端连接(正常或非正常)结束时发出,通常是在终端的控制进程结束时,通知同一会话内的各个作业与控制终端不再关联。(终止)
- SIGINT:该信号在用户键入INTR字符(通常是Ctrl-C)时发出,终端驱动程序发送此信号并送到前台进程中的每一个进程。(终止)
- SIGQUIT:该信号和SIGINT类似,但由QUIT字符(通常是Ctrl-\)来控制。(终止)
- SIGILL:该信号在一个进程企图执行一条非法指令时(可执行文件本身出现错误,或者试图执行数据段、堆栈溢出时)发出。(终止)
- SIGFPE:该信号在发生致命的算术运算错误时发出。这里不仅包括浮点运算错误,还包括溢出及除数为0等其它所有的算术的错误。(终止)
- SIGKILL:该信号用来立即结束程序的运行,并且不能被阻塞、处理和忽略。(终止)
- SIGALRM: 该信号当一个定时器到时的时候发出。(终止)
- SIGSTOP:该信号用于暂停一个进程,且不能被阻塞、处理或忽略。(暂停进程)
- SIGTSTP:该信号用于暂停交互进程,用户可键入SUSP字符(通常是Ctrl-Z)发出这个信号。(暂停进程)
- SIGCHLD :子进程改变状态时,父进程会收到这个信号。(忽略)
- SIGABORT :该信号用于结束进程。(终止)
SIGUSR1(10) SIGUSR2(12) 保留给用户使用。
SIGKILL及SIGSTOP不能忽略、不能捕捉。
kill –l 命令查看系统支持的信号列表
信号相关的函数
网络的服务器模型里信号捕捉里将进程回收 waitpid(-1, NULL, WNOHANG);
发送信号
#include <signal.h>
#include <sys/types.h>
int kill(
pid_t pid,
int sig
);
int raise(
int sig
);
参数:
pid:
正数:要接收信号的进程的进程号
0:信号被发送到所有和pid进程在同一个进程组的进程
-1:信号发给所有的进程表中的进程(除了进程号最大的进程外)
sig:信号
返回值
成功:0
出错:-1
kill和raise的区别
- 可以发送信号给进程或进程组(实际上,kill系统命令只是kill函数的一个用户接口)。
- raise函数允许进程向自己发送信号。
#include <unistd.h>
unsigned int alarm(
unsigned int seconds //指定秒数
);
返回值
成功:如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。
出错:-1
函数说明:
它在进程中设置一个定时器。当定时器指定的时间到时,内核就向进程发送SIGALARM信号,取消闹钟alarm(0);
int pause(void)
返回值
-1,并且把error值设为EINTR。
函数说明:
pause()
让进程睡眠,将当前进程处于等待态(可中断)直到有信号。
信号的处理
一个进程可以设定对信号的相应方式,
信号处理的主要方法有两种。
- 使用简单的signal()函数
- 使用信号集函数组
信号绑定函数:
#include <signal.h>
void (
*signal(
int signum,
void (*handler)(int))
)(int);
等价于:
typedef void (fun)(int);
fun * signal(signum, fun * handler)
参数
signum:指定信号
handler:
SIG_IGN:忽略该信号。
SIG_DFL:采用系统默认方式处理信号。
自定义的信号处理函数指针
返回值
成功:设置之前的信号处理方式
出错:-1
sigemtpyset(&set)
sigaddset(&set, signum)
sigprocmask(SIG_UNBLOCK|SIG_BLOCK, &set, NULL)
System V IPC对象
IPC对象,都需要key来产生,通过ID来操作对象。
共享内存
共享内存块提供了在任意数量的进程之间进行高效双向通信的机制。每个使用者都可以读取写入数据,但是所有程序之间必须达成并遵守一定的协议,以防止诸如在读取信息之前覆写内存空间等竞争状态的出现。
Linux无法严格保证提供对共享内存块的独占访问,甚至是在您通过使用IPC_PRIVATE创建新的共享内存块的时候也不能保证访问的独占性。 同时,多个使用共享内存块的进程之间必须协调使用同一个键值。
调试:
产生一个key值
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(
const char *pathname,
//就是你指定的文件名(已经存在的文件名),一般使用当前目录'.'。int proj_id
id//id是子序号。(一般填存任意字母或者数字) )
返回值:
成功:一个key值,
失败:-1,并设置errno.
注意:
- P1 创建的IPC对象如何让另一进程知道ID号?ID是系统分配的。但在创建对象时可以指定key,或者可以事先约定好
- 第一个进程用key来创建IPC对象,第二个进程只需要通过key来打开IPC对象
共享内存的特点:
共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝。
共享内存的使用包括如下步骤:
- 创建/打开共享内存 :需要判断是否已经创建过.
- 映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问.
- 撤销共享内存映射.
- 删除共享内存对象.
调试:
查看系统中的对象ipcs
删除系统中的对象ipcrm
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
创建/打开 共享内存
int shmget(
key_t key, //
IPC_PRIVATE或
ftok的返回值,
看到很多key的值是0,是系统中用IPC_PRIVATE创建的。
int size, //
共享内存区大小
int shmflg
);
参数:
shmflg:标志位,
同open函数的权限位,也可以用8进制表示法。
用法:
0666 打开一个共享内存,不能创建。如果与key关联的不存在,会返回-1。
0666|IPC_CREAT 不存在就创建,存在就打开
有时需初始化,创建后要初始化0666|IPC_CREAT|IPC_EXCL 返回-1,并设置相应errno。 errno=EEXIST表示已存在。
例:
int shmid;
if( (shmid = shmget(key, 128, 0666|IPC_CREAT|IPC_EXCL)) < 0)
{ if(errno == EEXIST)
{ shmid = shmget(key, 128, 0666)} //已经存在的情况,打开就可以。
返回值
成功:共享内存段标识符
出错:-1
映射 共享内存
void *shmat(
int shmid, //
要映射的共享内存区标识符
const void *shmaddr, //
将共享内存映射到指定地址(若为NULL,则表示由系统自动完成映射)
int shmflg
);
参数:
shmflg:
SHM_RDONLY:共享内存只读
默认0:共享内存可读写
返回值
成功:映射后的地址;
出错:-1。
撤销共享内存映射
int shmdt(
const void *shmaddr //共享内存映射后的地址
);
返回值:
成功:0,
失败:返回-1
删除共享内存对象
int shmctl(
int shmid, //要操作的共享内存标识符
int cmd,
struct shmid_ds *buf // 指定IPC_STAT/IPC_SET时用以保存/设置属性
);
参数:
cmd :
IPC_STAT (获取对象属性)
IPC_SET (设置对象属性)
IPC_RMID (删除对象)
返回值:
成功:0,
失败:返回-1
消息队列(Message queue)
消息队列的操作包括创建或打开消息队列、添加消息、读取消息和控制消息队列。
- 可看成是多个FIFO的集合,消息类型.
- 消息队列随内核持续,除内核重启或人工删除。
- 消息队列对应唯一的键值
消息结构体
struct msgbuf
{
long mtype; //消息类型
char mtext[N]
//消息正文
};
#define LEN (sizeof(MSG)- sizeof(long))
<sys/types.h>
<sys/ipc.h>
<sys/msg.h>
创建/打开消息队列()
创建的消息队列的数量会受到系统消息队列数量的限制
int msgget(
key_t key, //
和消息队列关联的key值
int flag //消息队列的访问权限
);
返回值:
成功:消息队列的ID
出错:-1
添加消息(
按照类型把消息添加到已打开的消息队列末尾
)
int msgsnd(
int msqid, //消息队列的ID
const void *msgp, //指向消息结构msgbuf.
size_t size, //
发送的消息正文的字节数(LEN)
int flag //IPC_NOWAIT:消息没有发送完成函数也会立即返回;0:直到发送完成函数才返回。
);
返回值:
成功:0
出错:-1
读取消息()
按照类型把消息从消息队列中取走
int msgrcv(
int msgid, //消息队列的ID
void* msgp, //接收消息的缓冲区
size_t size, //要接收的消息的字节数
long msgtype,
int flag
);
参数:
msgtype:
0:队列中最早的消息
>0:指定接收类型
<0:按优先级接收 接收消息队列中类型值不小于msgtyp的绝对值且类型值又最小的消息。
flag:
0:若无消息函数会一直阻塞
IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG。
返回值:
成功:读取的消息的长度
出错:-1
控制消息队列(
可以完成多项功能
)
int msgctl (
int msgqid, //消息队列的队列ID
int cmd,
struct msqid_ds *buf
);
参数:
cmd:
IPC_STAT:读取消息队列的属性,并将其保存在buf指向的缓冲区中。
IPC_SET:设置消息队列的属性。这个值取自buf参数。
IPC_RMID:从系统中删除消息队列。
buf:消息队列缓冲区
返回值:
成功:0
出错:-1
信号灯(semaphore)
信号灯也叫信号量。它是不同进程间或一个给定进程内部不同线程间同步的机制。
信号灯种类:
- posix有名信号灯
- posix基于内存的信号灯(无名信号灯)
- System V信号灯(IPC对象)
- 二值信号灯:值为0或1。与互斥锁类似,资源可用时值为1,不可用时值为0。
- 计数信号灯:值在0到n之间。用来统计资源,其值代表可用资源数。
- System V的信号灯是一个或者多个信号灯的一个集合。其中的每一个都是单独的计数信号灯。System V 信号灯由内核维护
- Posix信号灯指的是单个计数信号灯
信号灯的应用:
等待操作是等待信号灯的值变为大于0,然后将其减1;而释放操作则相反,用来唤醒等待资源的进程或者线程
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
获取信号量标识
int semget(
key_t key, //和信号灯集关联的key值
int nsems, //信号灯集中包含的信号灯数目
int semflg//信号灯集的访问权限,通常为IPC_CREAT | 0666
);
返回值:
成功:则返回信号量集的IPC标识符。
失败,则返回-1,并且设置errno,
EACCES:没有访问该信号量集的权限
EEXIST:信号量集已经存在,无法创建
ENOENT:信号量集不存在,同时没有使用IPC_CREAT
信号操作
int semop(
int semid, //信号灯集ID
struct sembuf *opsptr,
size_t nops //要操作的信号灯的个数
);
参数:
struct sembuf {
short sem_num; //要操作的信号灯的编号
short sem_op; //0:等待,直到信号灯的值变成0
//1:释放资源,V操作
//-1:分配资源,P操作
short sem_flg; //0,IPC_NOWAIT,SEM_UNDO
};
返回值:
成功:0
出错:-1
信号控制
int semctl(
int semid, //信号灯集ID
int semnum, //要修改的信号灯编号
int cmd…/*union semun arg*/
);
参数:
cmd:
GETVAL:获取信号灯的值
SETVAL:设置信号灯的值
IPC_RMID:从系统中删除信号灯集合
返回值:
成功:0
出错:-1
进程间通讯方式比较
- pipe:具有亲缘关系的进程间,单工,数据在内存中
- fifo:用于任意进程间,双工,有文件名,数据在内存
- signal:唯一的异步通信方式
- msg:常用于cs模式中, 按消息类型访问 ,可有优先级
- shm:效率最高(直接访问内存) ,需要同步、互斥机制
- sem:配合共享内存使用,用以实现同步和互斥