Linux学习总结(五)

来源:互联网 发布:excel 数据统计图 编辑:程序博客网 时间:2024/05/28 15:11

Linux学习总结(五)

进程间通信——FIFO

有名管道概念:有名管道依赖于文件系统,是一个存在的特殊文件,是西安不同进程对文件系统下的某个文件的访问时很方便实现的,有名管道和普通文件一样具有磁盘存放路径、文件权限和其他属性,但是有名管道并没有在磁盘中存放真正的信息,它存储的通信信息在内存中,两个进程结束后自动丢失,拥有一个磁盘路径仅仅是一个接口,其目的是使进程间的编程更简单统一,通信的两个进程结束后,有名管道的路径本身还是仍然存在,这是和无名广岛不一样的地方
有名管道管理及其特殊性
创建有名管道:mkfifo函数用来创建有名管道,他有两个参数,分别用来指定生成的管道和该管道文件的属性

int mkfifo(_const char *_path, _mode_t _mode)

mkfifo()会根据参数建立特殊的有名管道文件,该文件必须不存在,而参数mode为该文件的权限,mkfifo()建立的FIFO文件的其他进程都可以用读写一般文件的方式存取,当使用open()函数打开FIFO文件时,O_NONBLOCK会有影响

读写有名管道

在通过调用write和read来执行读写操作前,需要open函数打开该文件。另外,操作无名管道的阻塞位置为open位置

  • 1、如果希望以写的方式打开管道,则需要另一个进程以读的方式打开管道,如果以某种方式打开有名管道,则系统将阻塞进程,直到有另一个进程以另一种方式打开该管道后才会继续执行
  • 2、两个进程已经完成打开管道操作,阻塞读操作按以下方式执行
    (1)如果管道中没有数据,读操作默认阻塞
    (2)如果管道中有数据,但小于欲读取数据量,读出所有数据返回
    (3)如果管道中有数据,但大于欲读取数据量,读出去往大小数据返回
  • 3、两进程已经完成打开管道操作,阻塞读操作按一下方式执行
    (1)如果管道中没有空间,写操作阻塞
    (2)如果管道中有空间,但空间小于欲写入数据,写满空间后阻塞
    (3)如果管道中有空间,切空间大于欲写入数据,写入数据后返回
  • 4、两进程已经完成打开管道操作,中途一个进程退出
    (1)未退出一端如果是写操作,将SIGPIPE信号
    (2)未退出一端入宫时阻塞读操作,读操作将不会再阻塞直接返回0

Linux异步信号处理机制

Linux常见信号与处理

信号与中断
Linux信号是一种进程间异步的通信机制,在实现上是一种软中断。信号可以导致一个正在运行的进程被异步打断,转而处理一个突发事件。异步事件是不可预见的
简单介绍几种常见的信号处理

  • 1、SIGCHLD 子进程退出时会给父进程发送该信号。父进程可以根据该信号来完成对子进程PCB资源的回收
  • 2、SIGSTOP和SIGKILL不能被屏蔽、安装,用户不能自定义这两个信号的处理
  • 3、SIGSTOP和SIGCONT是配对的。一个进程收到SIGSTOP会暂停执行,进入暂停状态,并屏蔽SIGKILL所有的信号。当该进程收到SIGCONT信号会继续执行
  • 4、信号可以唤醒被终端的进程

信号的基本概念

发送信号:产生信号,有多种发送信号的方式
安装信号:设置信号到来时的不再执行默认操作,而是执行自定义的代码
递送信号:一个信号被操作系统发送到目标进程
捕获信号:被递送的信号在目标进程引起某段处理程序的执行
屏蔽信号:进程告诉操作系统暂时不接受某些信号
忽略信号:进程被递送到目标进程,但目标信号不处理,直接丢弃
未决信号:信号已经产生,但因目标进程暂时屏蔽该信号而不能被目标进程捕获的信号
可靠信号与不可靠信号:编号下于32的信号为不可靠信号,大于32的信号为可靠信号,如果进程在屏蔽信号的时间内,其他进程多次向其发送同一个信号,不可靠信号之哟一次未决信号,当进程解除屏蔽后,该信号只会被捕获一次,而可靠信号操作系统会记录所有的发送,当进程解除屏蔽后,操作系统会捕获对等次数

信号的生命周期

在linux系统下,信号的处理过程如下

  • 1、在目的进程中安装该信号,即设置如果目标进程捕获该信号时执行的操作代码,linux采用signa和sigaction系统调用来完成
  • 2、信号被某个进程产生,同时设置此信号的目的进程,然后又操作系统管理,linux采用看kill、asise、alarm、等系统调用来实现
  • 3、信号在目的进程被注册,操作系统将信号添加到目的进程的pcb相关的数据结构中,每个进程的PCB中有一个未决信号的数据成员,信号在进程中注册指的就是将相应的信号值加入到进程的未决信号中,并且信号所携带的其他信息被保留到信号队列的某个sigqueue结构中,只要信号在进程的未决信号集中,表明进程已经知道这些信号的存在,但还没有来的及处理,或者该信号被进程屏蔽,函数sigpending可取当前进程屏蔽和未决的信号
  • 4、信号在进程中的注销。进程在执行信号相应函数之前,首先要吧信号在进程中注销。在目标进程执行过程中,会检测是否有信号等待处理。进程每次从系统空间返回到用户空间时都做这样的检查。如果存在未决信号等待处理且该信号没有被进程屏蔽,则在运行相应的信号处理函数前,进程会把信号在未决信号链中占有的结构卸掉。对于非实时信号来说,由于在未决信号信息链中最多只占用一个sigqueue结构,因此该结构被释放后,应该把信号在进程未决信号集中删除,而对于实时信号来说,可能在未决信号链中占用多个sigqueue结构,因此如果只占用一个sigqueue结构,则应该把信号在进程的未决信号集中删除。否则,不在进程的未决信号集中删除该信号
  • 5、信号生命终止。进程注销信号后,目的进程根据当前进程对此信号设置的处理方式,暂时终止当前代码的执行,保护上下文、装而执行信号处理函数。即捕获该信号,执行完成后再恢复到被中断的位置继续执行。

发送信号

发送信号是指一个进程想另一个进程发送某个信号值,但实际并不是直接发送,而是由OS转发。产生一个信号有多种情况。信号的来源主要包括:

  • 1、当用户按某些终端键时产生信号,如在终端上按“Ctrl+c”组合键将产生终止信号。
  • 2、硬件异常,例如,对执行一个无效存储访问的进程产生一个SIGSEGV
  • 3、终止进程信号。其他进程调用kill函数可将信号发送给另一个进程或进程组,常用此命令终止一个失控的进程
  • 4、软件异常产生信号。当检测到某种软件条件已经发生,并将其通知有关进程时也会产生信号。如SIGPIPE等

kill发送一个信号到进程

传递一个信号给指定的进程应使用kill函数,传递一个信号给当前进程则使用raise函数,唤醒一个进程和设置定时使用alarm函数

int kill(_pid_t _pid, int _sig);

第一个参数为要传递的进程号,第二个参数为发送的信号值

  • (1)pid>0 将信号发送给进程的PID值为pid的进程
  • (2)pid=0 将信号发送给和当前进程在同一进程组的所有进程
  • (3)pid=-1 将信号发送给系统内所有的进程
  • (4)pid<0 将信号发送给进程组号PGID为pid绝对值的所有进程

rasie自举一个信号

raise函数用来 给当前进程发送一个信号,即唤醒一个进程

int raise(int _sig)

此函数只有一个参数,即要发送的信号值。如果成功返回0

alarm()定时

alarm()函数用来传递定时信号,即在多少时间内产生SIGALRM信号。此函数每调用一次,产生一个信号,并不是循环产生SIGALRM信号

unsigned int alarm(unsigned int _seconds)

此函数只有一个参数,即在多少时间内发送SIGALRM信号给当前进程,默认情况下,当进程接收到alarm信号后将终止执行
* (1)如果sec为0,则取消所有先前发出的报警请求
* (2)如果在调用alarm()函数前没有调用过alarm函数,则执行成功返回0,否则返回-1
* (3)如果在此前调用过alarm()函数,则将重新设置调用进程的闹钟,如果执行成功,将以当前时间为基准,返回值为上次设置的alarm将在多少时间内产生SIGALRM信号

ualarm定时

ualarm将使当前进程在指定时间(us为单位)内残生SIGALRM信号,然后每隔指定时间内重复产生SIGALRM信号

_useconds_t ualarm(_useconds_t _value, _useconds_t _interval);

setitimer定时器应用

函数getitmer()和setitimer()根据逝去时间、在用户空间执行时间、总的执行时间来设置/读出超时定时器信息,定时器将在超时后产生相应的信号

int getitimer(int which,struct itimerval *value)int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);

系统提供了三个定时器,也就是这两个函数的第一个参数可选值

ITMER_REAL:以逝去时间递减,当时钟到来后产生SIGALRM信号ITMER_VIRTUAL:当进程自身代码执行时递减,当时钟超时到来产生SIGVTALRM信号ITIMER_PROF:当进程自身执行或者是系统在执行进程的系统调用时递减,当时钟超时时将产生SIGPROF信号,因此,可联合ITIMER_VIRTUAL用来计算进程在用户空间和系统空间的运行时间

第二个参数的类型声明:

struct itimerval{    struct timerval it_interval;//间隔值    struct timerval it_value;//当前剩余值};struct timeval{    long tv_sec;//s为单位    long tv_usec;//ms为单位}

安装信号与捕获信号

信号处理办法
* (1)忽略此信号。大多数信号都可使用这种方法
* (2)自定义捕捉信号方式,当某信号到来时,执行用户自定义的操作,这要求该进程首先安装该信号,即通知内核在某种信号发生时调用一个特殊函数,
* (3)执行系统默认操作

signal安装信号

若程序要求接收到某信号时执行的特殊操作,就需要安装信号处理函数。安装信号处理函数signal()声明如下:

typedef void(*_sighandler_t)(int);extern _sighandler_t signal(int _sig,_sighandler_t _handler)

此函数有两个参数,第一个参数为sig为接受到的信号,第二个信号为接收到此信号的处理代码入口
如果执行成功,此函数将会返回对此信号的上一次设置

sigaction安装信号

函数signal()只能提供简单的信号安装操作,并逐步被淘汰,因此,linux提供了更强大的函数sigaction()安装信号,此函数可用来检查和更改处理操作,具体声明如下。

int sigaction(int _sig, struct sigaction *_act, struct sigaction *_oact);

此函数的第一个参数为接收到的信号,第二三个参数为信号结构sigaction变量。第二个参数用来制定要设置的信号处理方式,第三个参数将存储执行此函数前对此信号的安装信息
如果参数act为空指针,则信号处理保持不变
结构体struct sigaction详细规定了信号处理函数和信号标志等信息:

struct sigaction{union{_sighandler_t _sa_handler;void (*_sa_sigaction)(int, stuct siginfo *, void *)}_u;sigset_t sa_mask;unsigned long sa_flags;void (*sa_restorer)(void);};

sa_mask是一个信号集合,用于标识在执行信号捕获函数时,添加到进程屏蔽信号集中的信号集。
sa_flags 可用于更改指定信号的行为,
sa_handler或sa_sigaction标识要与指定信号关联的操作。两者只取其一即可,如果选用sa_handler则信号捕获函数类型与signal函数的捕获函数一致,而后者有专门的用途,可以获得更多的信息

安装信号与捕获信号

信号可以被屏蔽,但这与信号忽略不同
信号忽略:系统仍然传递该信号,只是相应进程对该信号不作任何处理
信号屏蔽:即使传递信号给该进程,该进程也不捕获信号,而是该信号处于未决状态,当进程的信号集发生改变时,不再屏蔽该信号,才捕获该信号

设置进程屏蔽信号集

函数sigprocmask()用来设置当前进程屏蔽的信号集合

int sigprocmask(int _how, _const sigset_t *_restrict _set, sigset_t *_restrict _oset);

此函数第一个参数为更改该集的方式

  • (1)SIG_BLOCK:将第二个参数所描述的集合添加到当前进程屏蔽的信号集当中
  • (2)SIG_UNBLOCK:将第2个参数所描述的集合从当前进程屏蔽的信号集中删除
  • (3)SIG_SETMASK:无论屏蔽了哪些信号,设置当前进程屏蔽的集合为第二个参数描述的对象

获取当前的未决的信号

sugpedning()函数返回当前进程所有未决的集合

int sigpending(sigset_t *_set);成功完成后返回0,将未决的信号添加到set中

信号集合操作

1清空信号集

int sigemptyset(sigset_t *_set);

2、填空所有的信号到信号集
int sigfillset(sigset_t *_set);

3、添加信号到信号集中,函数sigaddset()用来将某个信号添加到某信号集中
int sigaddset(sigset_t *_set, int _signo);

第一个参数为删除信号的信号集,第二个参数为添加的信号
4、从信号集中删除某个信号

int sigdelset(sigset_t *_set, int _signo)

5、检测信号是否在信号集中,若在,则返回1.否则返回0

int sigsmember(_const sigset_t *_set, int _signo);

6、检测信号集是否为空信号集

int sigisemptyset(_const sigset_t *_set);

7、按逻辑与方式将两个信号集合并

int sigandset(sigset_t *set, _const sigset_t *_left, _const sigset_t *_right)

8、按逻辑或方式将两个信号集合并

int sigorset(sigset_t *_ser, _const sigset_t *_left, _const sigset_t *_right)

等待信号

pause()函数将使当前进程处于等待状态,直到当前进程屏蔽信号外的任意一个信号出现

int pause(void);

将当前进程屏蔽的信号集替换为其参数所指定的信号集合,知道一个非指定集合中的信号后继续执行

int sigsuspend(_const sigseet_t *_set);

System V 进程间通信

System V提供的IPC机制主要有消息队列、信号量和共享内存3种机制。和文件一样IPC在使用前必须先创建,每种IPC都有特定的生产者、所有者和访问权限

key值和ID值

为了解决通信双方能够获得同一个IPC机制的唯一的ID的问题,IPC在实现时约定使用key值作为参数创建,如果创建时使用相同的key值将得到同一个IPC对象的ID,key值是一个32位的整形数据
linux提供了函数ftok()来创建key值

key_t ftok(_cosnt char*_pathname, int _proj_id);

此函数有两个参数,pathname为文件路径名,可以是特殊文件,也可以是当前目录,第二个参数为一个int型变量

拥有者及权限

一个IPC工具拥有相应的权限,他被定义在struct ipc_perm

消息队列

消息队列模型:消息队列是消息的链式队列
消息队列基本属性:由msqid_ds数据结构

linux消息队列管理

创建消息队列:在使用一个消息队列钱,需要使用msgget函数创建该消息队列

int msgget(key_t _key, int _msgflg);

第一个参数key为由frok创建的key值,第二个参数_msgflg的地位用来确定消息队列的访问权限
消息队列属性控制:创建消息队列后,可以对消息队列的基本属性进行控制,控制消息队列属性的函数为msgctl

int msgctl(int _msqid, int _cmd, struct msqid_ds *_buf);

第一个参数_msqid为消息队列标识符,该值为使用msgget函数创建消息队列的返回值
第二个参数_cmd为执行的控制命令,即要执行的操作
IPC_STAT:读取消息队列属性。取得此队列的msqid _ds结构,并将其存放在buf指向的结构中
IPC_SET:设置消息队列属性。按由buf指向的结构中的值来设置
IPC_RMID:删除消息队列
IPC_INFO:读取消息队列基本情况

发送消息到消息队列

msgsnd()函数将新的消息添加到消息队列尾端

int msgsnd(int _msqid, _const void *_msgp, size_t _msgsz, int _msgflg);

第一个参数为指定的消息队列标识符
第二个参数为指向的用户定义缓冲区
第三个参数为接受信息的大小
第四个参数用来指定在达到系统为消息队列所定的界限时采取的操作

从消息队列接收信息

msgrcv用于从队列中取消息

int msgrcv(int _msqid, void *_msgp, size_t, long int _msgtyp, int _msgflg);

第一个参数为读的对象
第二个参数为一个临时消息数据结构,用来保存读取的信息
第三个参数用于指定mtext的大小
第四个参数用于指定请求的消息类型
msgtyp=0:接受队列中的第一条消息
msgtyp>0:接受第一条msgtyp类型的消息
msgtyp<0:接受第一条最低类型的消息
第五个参数用于指定所需类型消息不在队列上时将要采取的操作

信号量通信机制

信号量IPC原理:信号量通信机制主要用来实现进程间同步,避免并发访问共享资源。信号量可以标识系统可用资源的个数。
最简单的信号量为二元信号量,当资源被占用时,就可以通过设置信号量为0告诉其他进程该资源为不可用,在操作完成后释放资源后,可以设置信号量为1表示资源可用
通常所说的创建一个信号量实际上是创建了一个信号量集合
整个信号量集合由以下部分组成
信号量集合数据结构:在此数据结构中定义了整个信号量集合的基本属性,如访问权限
信号量:信号量集合使用指针指向一个由数组组成的信号量单元,在此信号量单元中存储了个信号量的值
信号量集合数据结构:信号量集合数据解构规定了此信号量的权限、指针、最近修改时间和队列中信号量队列信息,其结构体的名字为struct semid_ds
信号量结构:信号量结构中可能有多个信号量,每个信号量的数据结构中成员变量主要为该信号量的当前值

struct sem{    int semval;//信号量的值    int sempid;//最近操作的一个进程号PID}

Linux信号量管理操作

1、创建信号量集合
创建一个信号量的集合的函数为semget,

int semget(key_t _key, int _nsems, int _semflg);

第一个参数为key_t类型的key值,一般由ftok函数产生
第二个参数为创建信号量的个数,以数组的方式存储
第三个参数用来标识信号量集合的权限,
2、控制信号量集合、信号量
在linux操作系统中,可使用semctl函数对一个信号量集合以及信号量集合中的某个或某几个信号量进行操作

int semctl(int _semid, int _semunm,int _cmd,...);

该函数最多可有4个参数,第一个参数为要操作的信号量集合标识符,该值一个由semget函数返回
第二个参数为集合中信号量的编号。如果要标识某个信号量,此值为该信号量的下标,如果要操作整个信号量集合,此参数无意义
第三个参数为要执行的操作,如果对整个信号量集合
如果要对信号量集合中某个或某些信号量操作,则包括:
3、信号量操作
semop系统调用来操作信号量集合

int semop(int _semid, struct sembuf *_sops, size_t _nsops);

第一个参数为要操作的信号量集合ID
第二个参数为struct sembuf结构的变量

struct sembuf{    unsigned short sem_num;//信号量下标short sem_op;//信号量操作short sem_flg;//操作标识}

sem_op:该值如果为正整数表示增加信号量的值,如果为负整数表示减小信号量的值,如果为0表示信号量的当前值进行是否为0的测试
sem_flg为操作标识,可选以下的值
IPC_NOWAIT:在对信号量集合的操作不能执行的情况下,调用立即返回,对某信号量操作,即使其中一个操作失败,也不会修改集合中的其他信号量
SEM_UNDO:当进程退出后,该进程对sem进行的操作将被撤销

共享内存

共享内存IPC原理
共享内存进程间通信机制主要用于实现进程间大量的数据传输,共享内存是在内存中单独开辟一段内存空间,这段内存空间有自己的特有的数据结构,包括访问权限、大小和最近访问的时间等
两个进程在使用此共享内存空间之前,需要在进程地址空间与共享内存空间之间,即将共享内存挂载到进程中
Linux共享内存管理
创建共享内存
系统调用shmget函数声明创建共享内存

int shmget(key_t _key, size_t _size, int _shmflg);

此函数有3个参数,第一个参数为key值,一般由ftok产生
第二个参数为要创建的共享内存段大小
第三个参数shmflg用来标识内存段的创建标识
IPC_CREAT 如果不存在就创建
IPC_EXCL 如果存在则返回失败
IPC_NOWAIT 不等待直接返回
SHM_R 可读
SHM_W 可写
共享内存控制
linux系统使用shmctl函数来实现共享内存空间的控制,包括读取状态、设置状态和删除操作

int shmctl(int _shmid, int _cmd, struct shmid_ds *_buf)

第一个参数为要操作的共享内存标识符,该值一般由shmget返回
第二个参数为要执行的操作
IPC_RMID 删除
IPC_SET 设置ipc_perm参数
IPC_STAT 获取ipc_perm参数
IPC_INFO 如ipcs
如果是超级用户
SHM_LOCK 锁定共享内存段
SHM_UMLOCK 解锁共享内存段
第三个参数为struct shmid_ds解构的临时共享内存变量信息

映射共享内存对象

在进程使用一段共享内存空间前,需要将共享内存与当前进程建立联系,即将该共享内存映射挂载到当前进程,系统调用shma()实现将一个共享内存段映射到调用进程的数据段中,并返回该内存空间首地址

void *shmat(int _shmid, _const void *_shmaddr, int _shflg);

第一个参数为要操作的共享内存标识符,该值一般由shmget函数返回
第二个参数指定共享内存的映射地址,如果该值为非零,则将用此值作为映射共享内存的地址,如果此值为0,则有系统来选择映射的地址
第三个参数用来指定共享内存段的访问权限和映射条件

分离共享内存对象

在使用完毕共享内存空间后,需要使用shmdt函数调用将其与当前进程分离。

int shmdt(——const void *_shmaddr);

此函数只有一个参数,即与当前进程分离的共享内存标识ID
共享进程在父子进程间遵循以下约定
使用frok函数创建一个子进程后,该进程继承父亲挂载的共享进程
如果调用exec执行一个新的程序,则所有挂载的共享内存将被自动卸载
如果在某个进程中调用了exit()函数,所有挂载的共享的将与当前进程脱离关系

原创粉丝点击