信号机制、消息队列、信号灯

目录

  • 一、信号机制
    • 1. 信号概念
      • (1)信号的产生:
      • (2)常用信号
    • 2. 信号的发送和定时器
      • (1)信号命令 — kill / kellall
      • (2)信号发送函数 — kill / raise
      • (3)信号相关函数 — alarm / pause
          • 1. alarm
          • 2. ualarm (循环发送)
          • 3. timer_create
          • 4. settimer
    • 3. 信号的捕捉
    • 4. 信号的SIGCHLD
    • 5. 信号的阻塞和信号集
      • (1)信号集操作函数
    • 6. 信号驱动任务
  • 二、消息队列
    • 1. 概念
    • 2. 消息队列的使用:
    • 3. 打开/创建消息队列
    • 4. 消息的接收:
    • 5. 消息队列的控制
    • 6 示例
  • 三、信号灯
    • 1. 有名信号灯
    • 2. 无名信号灯
    • 3. systemV 信号灯

一、信号机制

1. 信号概念

概念:信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式
linux内核通过信号通知用户进程,不同的信号类型代表不同的事件
Linux对早期的unix信号机制进行了扩展
进程对信号有不同的响应方式

  1. 缺省方式
  2. 忽略信号
  3. 捕捉信号

所有信号的产生及处理全部都是由内核完成的

(1)信号的产生:

  1. 按键产生
  2. 系统调用函数产生(比如raise, kill)
  3. 硬件异常
  4. 命令行产生 (kill)
  5. 软件条件(比如被0除,访问非法内存等)

(2)常用信号

信号名 含义 默认操作
SIGHUP 该信号在用户键入INTR字符(Ctrl-C)时产生,内核发送此信号送到当前终端的所有前台进程 终止
SIGINT 该信号在用户终端关闭时产生,通常是发给和该终端关联的会话内的所有进程 终止
SIGQUIT 该信号和SIGINT类似,但由QUIT字符(通常是Ctrl-)来产生 终止
SIGILL 该信号在一个进程企图执行一条非法指令时产生 终止
SIGSEV 该信号在非法访问内存时产生,如野指针、缓冲区溢出 终止
SIGPIPE 当进程往一个没有读端的管道中写入时产生,代表“管道断裂” 终止
SIGKILL 该信号用来结束进程,并且不能被捕捉和忽略 终止
SIGSTOP 该信号用于暂停进程,并且不能被捕捉和忽略 暂停进程
SIGTSTP 该信号用于暂停进程,用户可键入SUSP字符(通常是Ctrl-Z)发出这个信号 暂停进程
SIGCONT 该信号让进程进入运行态 继续运行
SIGALRM 该信号用于通知进程定时器时间已到 终止
SIGUSR1/2 该信号保留给用户程序使用 终止
SIGCHLD 是子进程状态改变发给父进程的。 忽略

2. 信号的发送和定时器

(1)信号命令 — kill / kellall

kill命令:

默认情况下,kill命令发送的信号是终止信号(SIGTERM),通常会请求进程正常退出。

kill [-signal] pid

示例:

kill -9 12345  // 发送SIGKILL信号给进程ID为12345的进程,强制终止它
kill -l  // 查看所有信号名 [signal]

killall命令:

killall命令用于根据进程名终止或发送信号给所有匹配的进程。你可以使用进程名、用户名、程序名等来匹配进程。

killall [-u user | prog]

示例:

killall firefox  // 终止所有名为"firefox"的进程
killall -u Linux  // 终止所有由"Linux"用户启动的进程

(2)信号发送函数 — kill / raise

int kill(pid_t pid, int signum)
int raise(int sig);     // 给自己发信号,等价于kill(getpid(), signo);
功能:发送信号
参数:

	pid: 	> 0:发送信号给指定进程

		    = 0:发送信号给跟调用kill函数的那个进程处于同一进程组的进程。

		    < -1: 取绝对值,发送信号给该绝对值所对应的进程组的所有组员。

  			= -1:发送信号给,有权限发送的所有进程。

	signum:待发送的信号

示例:

#include 
#include 
#include 

int main() {
    pid_t target_pid = 12345; // 将此处的 12345 替换为目标进程的实际进程ID

    // 发送终止信号(SIGTERM)给目标进程
    int result = kill(target_pid, SIGTERM);

    if (result == 0) {
        printf("终止信号已成功发送给进程 %d\n", target_pid);
    } else {
        perror("发送信号时出错");
    }

    return 0;
}

示例:

#include 
#include 

void signal_handler(int sig) {
    printf("Received signal: %d\n", sig);
}

int main() {
    signal(SIGINT, signal_handler); // 设置信号处理函数,捕获Ctrl+C
    raise(SIGINT); // 发送信号给自己,触发信号处理函数

    return 0;
}

(3)信号相关函数 — alarm / pause

1. alarm
unsigned int alarm(unsigned int seconds);

	功能:用于设置定时器,以在指定的秒数后发送 SIGALRM 信号。
	参数:		
			seconds:定时秒数,表示定时器的时间间隔,即多少秒后触发定时器。
	返回值:返回之前定时器的剩余秒数,如果之前没有定时器,返回值为0。


注意事项:

	alarm 函数不是线程安全的。在多线程环境中,如果不小心调用 alarm 函数,可能会影响其他线程。

	alarm 函数是非阻塞的,它会设置定时器并立即返回。之后,定时器会在指定的秒数后触发 SIGALRM 信号。

	在设置定时器时,如果之前已经存在定时器,调用 alarm 函数会取消之前的定时器。

示例:

#include 
#include 
#include 

void alarm_handler(int signum) {
    printf("定时器触发,收到 SIGALRM 信号!\n");
}

int main() {
    signal(SIGALRM, alarm_handler);

    unsigned int interval = 3; // 设置定时器触发的时间间隔(秒)

    printf("等待 %u 秒...\n", interval);

    unsigned int remaining_seconds = alarm(interval); // 设置定时器并获取之前的剩余秒数

    // 在这里添加一些其他的操作

    printf("定时器已触发,剩余 %u 秒\n", remaining_seconds);

    while (1) {
        // 等待
    }

    return 0;
}

输出结果:
在这里插入图片描述

2. ualarm (循环发送)
useconds_t ualarm(useconds_t usecs, useconds_t interval);

eg:
	value:参数指定了初始定时器的时间值(以微秒为单位)。
	interval:参数指定了定时器到期后,如果它会被重置的间隔时间值(以微秒为单位)。
	ualarm:函数的返回值是上一个定时器剩余的时间值。

例子:

#include 
#include 
#include 

void handle_alarm(int signum) {
    printf("Received SIGALRM signal.\n");
}

int main() {
    if (signal(SIGALRM, handle_alarm) == SIG_ERR) {
        perror("signal");
        return 1;
    }

    useconds_t initial_value = 1000000;  // 1秒
    useconds_t interval = 1000000;       // 1秒

    useconds_t remaining_time = ualarm(initial_value, interval);
    printf("Remaining time: %lu microseconds\n", remaining_time);

    while (1) {
        // 主循环保持进程运行
    }

    return 0;
}

在这个例子中,ualarm 函数会在初始时间(1秒)后触发 SIGALRM 信号,然后每隔1秒再次触发。handle_alarm 函数用于处理信号。

3. timer_create

timer_create 函数是用于创建一个定时器的 POSIX 函数,它可以在一定时间间隔后触发一个信号。这个函数允许你在定时器超时时发送一个指定的信号给进程。

int timer_create(clockid_t clockid, struct sigevent *sevp, timer_t *timerid);

eg:
	clockid:指定了定时器的时钟源,常用的有 CLOCK_REALTIME(实时时钟)和 CLOCK_MONOTONIC(单调时钟)。前者基于实际时间,后者基于系统启动时间,不受系统时间调整的影响。
	
	sevp:一个指向 struct sigevent 结构的指针,用于指定定时器的事件通知方式。可以设置为 NULL,表示使用默认方式。
	
	timerid:一个用于存储创建后的定时器标识符的变量。
	
	struct sigevent 结构允许你指定定时器到期时如何通知进程,它包含了一个联合体来支持不同的通知方式,例如线程通知、信号通知等。
	
	timer_t 是一个用于标识定时器的数据类型,实际上是一个不透明的指针类型。

struct sigevent结构体

struct sigevent {
    int sigev_notify;         // 通知方法(SIGEV_SIGNAL 或 SIGEV_THREAD)
    int sigev_signo;          // 发送的信号编号或线程函数(如果是 SIGEV_THREAD)
    union sigval sigev_value; // 传递给信号处理函数或线程函数的值
    void (*sigev_notify_function)(union sigval); // 线程启动函数(如果是 SIGEV_THREAD)
    void *sigev_notify_attributes; // 线程属性(如果是 SIGEV_THREAD)
    _Atomic void *sigev_notify_thread_id; // 线程 ID(如果是 SIGEV_THREAD_ID)
};

struct itimerspec结构体

struct itimerspec {
    struct timespec it_interval; // 周期定时器的间隔时间
    struct timespec it_value;    // 初始超时时间
};

struct sigaction结构体

struct sigaction {
    void (*sa_handler)(int);        // 信号处理函数
    void (*sa_sigaction)(int, siginfo_t *, void *); // 带有附加信息的信号处理函数
    sigset_t sa_mask;               // 用于阻塞的信号集
    int sa_flags;                   // 信号处理标志
    void (*sa_restorer)(void);      // 未使用
};

示例:

#include 
#include 
#include 
#include 

void handle_timer(int signum, siginfo_t *info, void *context) {
    printf("Timer expired.\n");
}

int main() {
    timer_t timerid;
    struct sigevent sev;
    struct itimerspec its;

    sev.sigev_notify = SIGEV_SIGNAL;
    sev.sigev_signo = SIGALRM;
    sev.sigev_value.sival_ptr = &timerid;

    if (timer_create(CLOCK_REALTIME, &sev, &timerid) == -1) {
        perror("timer_create");
        return 1;
    }

    its.it_value.tv_sec = 1;     // 初始时间:1秒
    its.it_value.tv_nsec = 0;
    its.it_interval.tv_sec = 1;  // 间隔时间:1秒
    its.it_interval.tv_nsec = 0;

    if (timer_settime(timerid, 0, &its, NULL) == -1) {
        perror("timer_settime");
        return 1;
    }

    struct sigaction sa;
    sa.sa_flags = SA_SIGINFO;
    sa.sa_sigaction = handle_timer;
    sigaction(SIGALRM, &sa, NULL);

    while (1) {
        // 主循环保持进程运行
    }

    return 0;
}
4. settimer
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
功能:定时的发送alarm信号
eg:
which 参数指定了要设置的定时器类型。常用的取值有三种:
	ITIMER_REAL:以实际时间为基准,发送 SIGALRM 信号。
	ITIMER_VIRTUAL:以进程在用户态运行的时间为基准,发送 SIGVTALRM 信号。
	ITIMER_PROF:以进程在用户态和内核态运行的总时间为基准,发送 SIGPROF 信号。
	
new_value 是一个指向 struct itimerval 结构的指针,用于设置新的定时器值。

old_value 是一个指向 struct itimerval 结构的指针,用于存储之前的定时器值(如果需要的话)。
struct itimerval 结构定义如下:

struct timeval {
    time_t      tv_sec;         /* seconds */
    suseconds_t tv_usec;        /* microseconds */
};
struct timeval 结构体定义如下:

struct timeval {
    time_t      tv_sec;     // 秒
    suseconds_t tv_usec;    // 微秒
};

例子:

#include 
#include 
#include 

void handle_alarm(int signum) {
    printf("Timer expired.\n");
}

int main() {
    struct itimerval timer;
    timer.it_value.tv_sec = 1;     // 初始时间:1秒
    timer.it_value.tv_usec = 0;
    timer.it_interval.tv_sec = 2;  // 间隔时间:2秒
    timer.it_interval.tv_usec = 0;

    if (signal(SIGALRM, handle_alarm) == SIG_ERR) {
        perror("signal");
        return 1;
    }

    if (setitimer(ITIMER_REAL, &timer, NULL) == -1) {
        perror("setitimer");
        return 1;
    }

    while (1) {
        // 主循环保持进程运行
    }

    return 0;
}

3. 信号的捕捉

信号机制、消息队列、信号灯_第1张图片
信号捕捉过程:

  1. 定义新的信号的执行函数handle。
  2. 使用signal/sigaction 函数,把自定义的handle和指定的信号相关联。

signal函数

typedef void (*sighandler_t)(int);
sighandler_t  signal(int signum, sighandler_t handler);

功能:捕捉信号执行自定义函数
返回值:成功时返回原先的信号处理函数,失败时返回SIG_ERR
**参数:**
	 signo 要设置的信号类型
 	 handler 指定的信号处理函数: SIG_DFL代表缺省方式; SIG_IGN 代表忽略信号;  

系统建议使用sigaction函数,因为signal在不同类unix系统的行为不完全一样。
sigaction函数:

int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
struct sigaction {
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);
}

**参数:**
signum:处理的信号
act,oldact: 处理信号的新行为和旧的行为,是一个sigaction结构体。

sigaction结构体成员定义如下:
sa_handler: 是一个函数指针,其含义与 signal 函数中的信号处理函数类似
sa_sigaction: 另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。
sa_flags参考值如下:
SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数
SA_RESTART:使被信号打断的系统调用自动重新发起。
SA_RESETHAND:信号处理之后重新设置为默认的处理方式。
    SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。
re_restorer:是一个已经废弃的数据域

例子:

#include 
#include 
#include 
#include 
#include 
#include 

typedef void (*sighandler_t)(int);

sighandler_t oldact;

void handle(int sig){
   if(sig == SIGINT){
        printf("I cath the SIGINT \n");
   }else if (sig==SIGALRM){
       printf("second timer \n");
       alarm(1);
   }
    //    signal(SIGINT,oldact);
}

void mytimer(int sig){


}


int main(){
    struct sigaction act;
    act.sa_handler = handle;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    sigaction(SIGINT,&act,NULL);
    alarm(1);
    sigaction(SIGALRM,&act,NULL);
//    oldact = signal(SIGINT,handle);

    while(1){
        sleep(1);
    }

} 


4. 信号的SIGCHLD

SIGCHLD的产生条件

  1. 子进程终止时
  2. 子进程接收到SIGSTOP信号停止时
  3. 子进程处在停止态,接受到SIGCONT后唤醒时
    例子:
#include 
#include 
#include 
#include 
#include 


void handle(int sig){

    wait(NULL);
    printf("Get sig =%d\n",sig);

}


int main(){
    pid_t pid;
    struct sigaction act;
    act.sa_handler = handle;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);

    pid = fork();

    if(pid>0){
        //wait(NULL);

        sigaction(SIGCHLD,&act,NULL);
        while(1){
            printf("this is father process\n");
            sleep(1);
        }

    }else if(pid==0){
        sleep(5);
        exit(0);
    }


}

5. 信号的阻塞和信号集

有时候不希望在接到信号时就立即停止当前执行,去处理信号,同时也不希望忽略该信号,而是延时一段时间去调用信号处理函数。这种情况可以通过阻塞信号实现。
信号的阻塞概念:信号的”阻塞“是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生。
信号机制、消息队列、信号灯_第2张图片

信号的状态
信号递达(Delivery ):实际信号执行的处理过程(3种状态:忽略,执行默认动作,捕获)
信号未决(Pending):从产生到递达之间的状态

(1)信号集操作函数

sigset_t set; 自定义信号集。 是一个32bit 64bit 128bit的数组。

sigemptyset(sigset_t *set); 清空信号集

sigfillset(sigset_t *set); 全部置1

sigaddset(sigset_t *set, int signum); 将一个信号添加到集合中

sigdelset(sigset_t *set, int signum); 将一个信号从集合中移除

sigismember(const sigset_t *set,int signum); 判断一个信号是否在集合中。

设定对信号集内的信号的处理方式(阻塞或不阻塞)

#include
int sigprocmask( int how, const sigset_t *restrict set, sigset_t *restrict oset );
返回值:若成功则返回0,若出错则返回-1

首先,若oset是非空指针,那么进程的当前信号屏蔽字通过oset返回。

其次,若set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。

how可选用的值:(注意,不能阻塞SIGKILL和SIGSTOP信号)

SIG_BLOCK : 把参数set中的信号添加到信号屏蔽字中
SIG_UNBLOCK: 从信号屏蔽字中删除参数set中的信号
SIG_SETMASK: 把信号屏蔽字设置为参数set中的信号

int pause(void);
进程一直阻塞,直到被信号中断,返回值:-1 并设置errno为EINTR
函数行为:

  1. 如果信号的默认处理动作是终止进程,则进程终止,pause函数么有机会返回。
  2. 如果信号的默认处理动作是忽略,进程继续处于挂起状态,pause函数不返回
  3. 如果信号的处理动作是捕捉,则调用完信号处理函数之后,pause返回-1。
  4. pause收到的信号如果被屏蔽,那么pause就不能被唤醒

int sigsuspend(const sigset_t *sigmask);
功能:将进程的屏蔽字替换为由参数sigmask给出的信号集,然后挂起进程的执行
参数
sigmask:希望屏蔽的信号

例子:

#include 
#include 
#include 

void handle(int sig) {
	printf("I get sig = %d\n", sig);
}
int main () {
	struct sigaction act;
	act.sa_handler = handle;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	sigaction(SIGINT, &act, NULL);

	sigset_t set;
	sigemptyset(&set);
	sigaddset(&set, SIGINT);
	sigprocmask(SIG_BLOCK, &set, NULL);
	sleep(5);
	sigprocmask(SIG_UNBLOCK, &set, NULL);


	while (1) {
		sleep(1);
	}
}

6. 信号驱动任务

例子:

#include 
#include 
#include 

void handle(int sig) {
	printf("I get sig = %d\n", sig);
}

void Mytask() {
	printf("My task start\n");
	sleep(3);
	struct sigaction act;
	act.sa_handler = handle;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	sigaction(SIGINT, &act, NULL);
	sigaction(SIGHUP, &act, NULL);

	sigset_t set, set2;
	sigemptyset(&set2);
	sigaddset(&set, SIGHUP);
	sigaddset(&set, SIGINT);
	pause();

	while(1) {
		sigprocmask(SIG_BLOCK, &set, NULL);
		Mytask();
//		sigprocmask(SIG_UNBLOCK, &set, NULL);
//		pause();
		sigsuspend(&set2);
	}
	printf("After pause \n");

	while (1) {
		sleep(1);
	}
}	printf("My task end\n");
}

int main () {
	struct sigaction act;
	act.sa_handler = handle;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	sigaction(SIGINT, &act, NULL);
	sigaction(SIGHUP, &act, NULL);

	sigset_t set, set2;
	sigemptyset(&set2);
	sigaddset(&set, SIGHUP);
	sigaddset(&set, SIGINT);
	pause();

	while(1) {
		sigprocmask(SIG_BLOCK, &set, NULL);
		Mytask();
//		sigprocmask(SIG_UNBLOCK, &set, NULL);
//		pause();
		sigsuspend(&set2);
	}
	printf("After pause \n");

	while (1) {
		sleep(1);
	}
}

二、消息队列

1. 概念

消息队列是System V IPC对象的一种
信号机制、消息队列、信号灯_第3张图片

2. 消息队列的使用:

发送端:

  1. 申请Key
  2. 打开/创建消息队列 msgget
  3. 向消息队列发送消息 msgsnd

接收端:

  1. 打开/创建消息队列 msgget
  2. 从消息队列接收消息 msgrcv
  3. 控制(删除)消息队列 msgctl

3. 打开/创建消息队列

#include
#include
int msgget(key_t key, int msgflg);

成功时返回消息队列的id,失败时返回EOF
key 和消息队列关联的key IPC_PRIVATE 或 ftok
msgflg 标志位 IPC_CREAT|0666 IPC_CREAT:没有创建,有则打开。

发送消息
#include
#include
int msgsnd(int msgid, const void *msgp, size_t size,
int msgflg);

成功时返回0,失败时返回-1
msgid 消息队列id
msgp 消息缓冲区地址
size 消息正文长度
msgflg 标志位 0 或 IPC_NOWAIT
msgflg:
0:当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列
IPC_NOWAIT:当消息队列已满的时候,msgsnd函数不等待立即返回

消息格式:

typedef struct{
long msg_type;
char buf[128];
}msgT;

注意:

  1. 消息结构必须有long类型的msg_type字段,表示消息的类型。
  2. 消息长度不包括首类型 long

4. 消息的接收:

#include 
 #include 
 int msgrcv(int msgid, void *msgp, size_t size, long msgtype,
                   int msgflg);

成功时返回收到的消息长度,失败时返回-1
msgid 消息队列id
msgp 消息缓冲区地址
size 指定接收的消息长度
msgtype 指定接收的消息类型
msgflg 标志位
msgtype:
msgtype=0:收到的第一条消息,任意类型。
msgtype>0:收到的第一条 msg_type类型的消息。
msgtype<0:接收类型等于或者小于msgtype绝对值的第一个消息。
例子:如果msgtype=-4,只接受类型是1、2、3、4的消息

msgflg:
0:阻塞式接收消息
IPC_NOWAIT:如果没有返回条件的消息调用立即返回,此时错误码为ENOMSG
MSG_EXCEPT:与msgtype配合使用返回队列中第一个类型不为msgtype的消息

5. 消息队列的控制

#include 
 #include 
 int msgctl(int msgid, int cmd, struct msqid_ds *buf);

成功时返回0,失败时返回-1
msgid 消息队列id
cmd 要执行的操作 IPC_STAT / IPC_SET / IPC_RMID(删除)
buf 存放消息队列属性的地址

6 示例

例子:
msgsnd.c

#include 
#include 
#include 
#include 

// 定义消息结构体
typedef struct {
    long msg_type; // 消息类型,必须为长整型
    char buf[128]; // 消息内容,最大长度为 128 字节
} msgT;

#define MSGLEN (sizeof(msgT) - sizeof(long)) // 消息长度

int main() {
    key_t key;
    int msgid;
    int ret;
    msgT msg;

    // 使用 ftok 函数生成一个唯一的键值
    key = ftok(".", 100);
    if (key < 0) {
        perror("ftok");
        return 0;
    }

    // 使用 msgget 函数创建或获取一个消息队列
    msgid = msgget(key, IPC_CREAT | 0666);
    if (msgid < 0) {
        perror("msgget");
        return 0;
    }

    // 设置消息类型为 1,并发送消息
    msg.msg_type = 1;
    strcpy(msg.buf, "this msg type 1");
    ret = msgsnd(msgid, &msg, MSGLEN, 0);
    if (ret < 0) {
        perror("msgsnd");
        return 0;
    }

    // 以类似的方式发送其他类型的消息
    msg.msg_type = 2;
    strcpy(msg.buf, "this msg type 2");
    ret = msgsnd(msgid, &msg, MSGLEN, 0);
    if (ret < 0) {
        perror("msgsnd");
        return 0;
    }

    msg.msg_type = 3;
    strcpy(msg.buf, "this msg type 3");
    ret = msgsnd(msgid, &msg, MSGLEN, 0);
    if (ret < 0) {
        perror("msgsnd");
        return 0;
    }

    msg.msg_type = 4;
    strcpy(msg.buf, "this msg type 4");
    ret = msgsnd(msgid, &msg, MSGLEN, 0);
    if (ret < 0) {
        perror("msgsnd");
        return 0;
    }

    msg.msg_type = 5;
    strcpy(msg.buf, "this msg type 5");
    ret = msgsnd(msgid, &msg, MSGLEN, 0);
    if (ret < 0) {
        perror("msgsnd");
        return 0;
    }

    // 消息发送完毕,程序结束
    return 0;
}

msgrcv.c

#include 
#include 
#include 
#include 

// 定义消息结构体
typedef struct {
    long msg_type; // 消息类型,必须为长整型
    char buf[128]; // 消息内容,最大长度为 128 字节
} msgT;

#define MSGLEN (sizeof(msgT) - sizeof(long)) // 消息长度

int main() {
    int msgid;
    key_t key;
    msgT msg;
    int ret;

    // 使用 ftok 函数生成一个唯一的键值,要与发送消息的程序使用相同的 key
    key = ftok(".", 100);
    if (key < 0) {
        perror("ftok");
        return 0;
    }

    // 使用 msgget 函数创建或获取一个消息队列,要与发送消息的程序使用相同的 key
    msgid = msgget(key, IPC_CREAT | 0666);
    if (msgid < 0) {
        perror("msgget");
        return 0;
    }

    int count = 0;
    while (1) {
        // 使用 msgrcv 函数接收消息,第二个参数是消息缓冲区,第三个参数是消息长度,第四个参数是消息类型
        ret = msgrcv(msgid, &msg, MSGLEN, 0, 0);
        if (ret < 0) {
            perror("msgrcv");
            return 0;
        }

        count++;

        // 打印接收到的消息类型和内容
        printf("Received msg type=%ld, buf=%s\n", msg.msg_type, msg.buf);

        if (count > 3) {
            break; // 接收到足够数量的消息后退出循环
        }
    }

    // 使用 msgctl 函数删除消息队列
    ret = msgctl(msgid, IPC_RMID, NULL);
    if (ret < 0) {
        perror("msgctl");
        return 0;
    }

    return 0;
}

三、信号灯

信号灯/信号量(semaphore)
概念:是不同进程间或一个给定进程内部不同线程间同步的机制。类似我们的
PV操作概念:
生产者和消费者场景
P(S) 含义如下:
if (信号量的值大于0) {
申请资源的任务继续运行;
信号量的值减一;
} else {
申请资源的任务阻塞;
}
V(S) 含义如下:
信号量的值加一;
if (有任务在等待资源) {
唤醒等待的任务,让其继续运行
}

Posix 有名信号灯
Posix 无名信号灯 (linux只支持线程同步)
System V 信号灯

Posix 有名信号灯和无名信号灯使用:
信号机制、消息队列、信号灯_第4张图片

1. 有名信号灯

有名信号灯打开
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);
参数:
name:name是给信号灯起的名字
oflag:打开方式,常用O_CREAT
mode:文件权限。常用0666
value:信号量值。二元信号灯值为1,普通表示资源数目

信号灯文件位置:/dev/shm

有名信号灯关闭
int sem_close(sem_t *sem);

有名信号灯的删除
int sem_unlink(const char* name);

例子:
sem_w.c

#include            /* For O_* constants */
#include         /* For mode constants */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
void delsemfile(int sig){
    sem_unlink("mysem_w");
    exit(0);

}

int main(){


   sem_t *sem_r,*sem_w;
   key_t key;
   int shmid;
   char *shmaddr;

   struct sigaction act;
   act.sa_handler = delsemfile;
   act.sa_flags = 0;
   sigemptyset(&act.sa_mask);

   sigaction(SIGINT,&act,NULL);

   key = ftok(".",100);
   if(key<0){
       perror("ftok");
       return 0;
   }

   shmid = shmget(key,500,0666|IPC_CREAT);
   if(shmid<0){
       perror("shmget");
       return 0;
   }

   shmaddr = shmat(shmid,NULL,0);
   
   sem_r = sem_open("mysem_r",O_CREAT|O_RDWR,0666,0);
   sem_w = sem_open("mysem_w",O_CREAT|O_RDWR,0666,1);

   while(1){
        sem_wait(sem_w);
        printf(">");
        fgets(shmaddr,500,stdin);
        sem_post(sem_r);
   }

}


sem_r.c

#include            /* For O_* constants */
#include         /* For mode constants */
#include 
#include 
#include 
#include 
#include 
#include 
#include 


void delsemfile(int sig){
    sem_unlink("mysem_r");
    exit(0);

}


int main(){

   sem_t *sem_r,*sem_w;
   key_t key;
   int shmid;
   char *shmaddr;
   struct sigaction act;
   act.sa_handler = delsemfile;
   act.sa_flags = 0;
   sigemptyset(&act.sa_mask);

   sigaction(SIGINT,&act,NULL);



   key = ftok(".",100);
   if(key<0){
       perror("ftok");
       return 0;
   }

   shmid = shmget(key,500,0666|IPC_CREAT);
   if(shmid<0){
       perror("shmget");
       return 0;
   }

   shmaddr = shmat(shmid,NULL,0);
   
   sem_r = sem_open("mysem_r",O_CREAT|O_RDWR,0666,0);
   sem_w = sem_open("mysem_w",O_CREAT|O_RDWR,0666,1);

   while(1){
        sem_wait(sem_r);
        printf("%s\n",shmaddr);
        sem_post(sem_w);
   }

}


2. 无名信号灯

无名信号灯初始化
int sem_init(sem_t *sem, int shared, unsigned int value);
参数:
sem:需要初始化的信号灯变量
shared: shared指定为0,表示信号量只能由初始化这个信号量的进程使用,不能在进程间使用,linux 不支持进程间同步。
Value:信号量的值

无名信号灯销毁
int sem_destroy(sem_t* sem);

信号灯P操作
int sem_wait(sem_t *sem);
获取资源,如果信号量为0,表示这时没有相应资源空闲,那么调用线程就将挂起,直到有空闲资源可以获取

信号灯V操作
int sem_post(sem_t *sem);
释放资源,如果没有线程阻塞在该sem上,表示没有线程等待该资源,这时该函数就对信号量的值进行增1操作,表示同类资源多增加了一个。如果至少有一个线程阻塞在该sem上,表示有线程等待资源,信号量为0,这时该函数保持信号量为0不变,并使某个阻塞在该sem上的线程从sem_wait函数中返回

注意:编译posix信号灯需要加pthread动态库。

例子:
sem_thread.c

#include            /* For O_* constants */
#include         /* For mode constants */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


sem_t sem_r,sem_w;
char *shmaddr;
void destroysem(int sig){
//    sem_unlink("mysem_w");
   sem_destroy(&sem_r);
   sem_destroy(&sem_w);
   exit(0);

}

void *readmem(void *arg){
    while(1){
        sem_wait(&sem_r);
        printf("%s\n",shmaddr);
        sem_post(&sem_w);

    }

}


int main(){


   key_t key;
   int shmid;

   struct sigaction act;
   act.sa_handler = destroysem;
   act.sa_flags = 0;
   sigemptyset(&act.sa_mask);

   sigaction(SIGINT,&act,NULL);

   key = ftok(".",100);
   if(key<0){
       perror("ftok");
       return 0;
   }

   shmid = shmget(key,500,0666|IPC_CREAT);
   if(shmid<0){
       perror("shmget");
       return 0;
   }

   shmaddr = shmat(shmid,NULL,0);
   
   sem_init(&sem_r,0,0);
   sem_init(&sem_w,0,1);

   pthread_t tid;
   pthread_create(&tid,NULL,readmem,NULL);
    
   while(1){
        sem_wait(&sem_w);
        printf(">");
        fgets(shmaddr,500,stdin);
        sem_post(&sem_r);
   }

}


3. systemV 信号灯

System V 信号灯使用:

int semget(key_t key, int nsems, int semflg);
功能:创建/打开信号灯
参数:key:ftok产生的key值(和信号灯关联的key值)
       nsems:信号灯集中包含的信号灯数目
       semflg:信号灯集的访问权限,通常为IPC_CREAT |0666
返回值:成功:信号灯集ID ; 失败:-1

int semop ( int semid, struct sembuf *opsptr, size_t nops);
功能:对信号灯集合中的信号量进行P - V操作
参数:semid:信号灯集ID
   struct sembuf {
    short sem_num; // 要操作的信号灯的编号
    short sem_op; // 1 : 释放资源,V操作
// -1 : 分配资源,P操作
    short sem_flg; // 0(阻塞),IPC_NOWAIT, SEM_UNDO
   };//对某一个信号灯的操作,如果同时对多个操作,则需要定义这种结构体数组

nops: 要操作的信号灯的个数 ,1个
返回值:成功 :0 ; 失败:-1

int semctl ( int semid, int semnum, int cmd…/union semun arg/);
功能:信号灯集合的控制(初始化/删除)
参数:semid:信号灯集ID
   semnum: 要操作的集合中的信号灯编号
   cmd:
   GETVAL:获取信号灯的值,返回值是获得值
   SETVAL:设置信号灯的值,需要用到第四个参数:共用体
   IPC_RMID:从系统中删除信号灯集合
返回值:成功 0 ; 失败 -1

例子:
sysvsem.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 



#define SEM_READ   0
#define SEM_WRITE  1

union semun {
   int val;
};               

void Poperation(int semid,int semindex){
   struct sembuf sbuf;
   sbuf.sem_num =  semindex;
   sbuf.sem_op = -1;
   sbuf.sem_flg = 0;

   semop(semid,&sbuf,1);


}
void Voperation(int semid,int semindex){
   struct sembuf sbuf;
   sbuf.sem_num =  semindex;
   sbuf.sem_op = 1;
   sbuf.sem_flg = 0;
              
   semop(semid,&sbuf,1);


}




int main(){

    key_t key;
    char *shmaddr;
    int semid,shmid;
    key = ftok(".",100);
    if(key<0){
        perror("ftok");
        return 0;
    }
    
    semid = semget(key,2,IPC_CREAT |0666);
    if(semid<0){
        perror("semget");
        return 0;
    }
    shmid = shmget(key,500,IPC_CREAT |0666);
    shmaddr = shmat(shmid,NULL,0);
    union semun mysem;
    mysem.val = 0;
    semctl(semid,SEM_READ,SETVAL,mysem);
    mysem.val = 1;
    semctl(semid,SEM_WRITE,SETVAL,mysem);

    pid_t pid;
    pid = fork();
    if(pid<0){
        perror("fork");
        shmctl(shmid,IPC_RMID,NULL);
        semctl(semid,0,IPC_RMID);
        exit(-1);
    }else if(pid == 0){
        while(1){
            Poperation(semid,SEM_READ);
            printf("%s\n",shmaddr);

            Voperation(semid,SEM_WRITE);

        }
 

    }else{
        while(1){
            Poperation(semid,SEM_WRITE);
            printf(">");
            fgets(shmaddr,32,stdin);

            Voperation(semid,SEM_READ);

        }



    }

}

你可能感兴趣的:(linux)