System V

Message Queue based on System V

จะมีลักษณะคล้ายกับไฟล์ pipe คือสามารถส่งบล็อกข้อมูลจากโปรเซสหนึ่งไปยังอีกโปรเซสหนึ่งผ่านลีนุกซ์คอร์เนลเช่นเดียวกัน โดยแต่ละบล็อกข้อมูลจะมีหมายเลขอ้างอิงกำหนดไว้ให้เพื่อให้โปรเซสอื่นๆสามารถอ้างอิงหมายเลขข้อมูลที่ต้องการนั้นได้ ซึ่งจะไม่เหมือนกับการส่งข้อมูลผ่าน pipe ซึ่งทั้งสองโปรเซสจะต้องรอซึ่งกันและกัน ดังนั้นขั้นตอนการส่งข้อมูลแบบ message queue จะมีขั้นตอนคือโปรเซสที่ทำการเขียนข้อมูลไปยัง queue (ด้วยคำสั่ง msgsnd) แล้ว ก็สามารถจบการทำงานได้ทันที ถ้าโปรเซสใดต้องการอ่านก็สามารถเข้าไปอ่านข้อมูลใน queue ได้ภายหลัง (ด้วยคำสั่ง msgrcv) ซึ่งตัว message queue เปรียบเสมือนกล่องส่วนกลางที่จะมีโปรเซสใดก็ได้ที่สามารถเข้ามาทิ้งข้อมูลไว้ หรือจะมีโปรเซสใดก็ได้ที่จะเข้ามาดึงข้อความจากกล่องที่แชร์ไว้นี้ได้

ภายใต้มาตราฐาน System V IPC เมื่อโปรเซสมีการสร้าง message queue แล้ว แม้ว่าโปรเซสนั้นถูกทำลายหรือสิ้นสุดการทำงานไปแล้วก็ตาม แต่ตัว message queue นั้นก็ยังค้างอยู่ในระบบซึ่งสามารถใช้คำสั่ง ipcs ภายใน shell เพื่อแสดงรายการ message queue ที่มีอยู่ในระบบดังตัวอย่างการใช้งานข้างล่างและสามารถใช้คำสั่ง ipcrm เมื่อต้องการลบ message queue ออกไปจากระบบ

$ ipcs -q
------ Message Queues --------
key        msqid      owner       perms      used-bytes   messages    
0x42016dfa 32768      sriborrirux  644        0            0     

ฟังก์ชันและตัวแปรที่เกี่ยวข้อง

  • ไลบรารีที่เกี่ยวข้องคือ sys/types.h, sys/ipc.h และ sys/msg.h

  • ฟังก์ชัน int msgget(key_t key, int msgflg);

    • เมื่อต้องการติดต่อไปยัง message queue หรือสร้าง message queue ขึ้นมาใหม่ โดยการระบุหมายเลขกุญแจ (key) ซึ่งในกรณีที่มีการสร้าง message queue จะต้องระบุค่าแฟลก (flag) ให่กับตัวแปร msgflg เช่น 0666|IPC_CREAT เพื่อระบุสิทธิ์การเข้าถึงของ message queue ของตัวที่จะถูกสร้างขึ้น

ตัวอย่างเช่น โปรแกรม A ต้องการสร้างกุญแจ เพื่อทำการสร้าง message queue ตัวใหม่ จะมีขั้นตอนดังนี้

...
key = ftok("/home/wiroon/somefile", 'P');
msqid = msgget(key, 0666 | IPC_CREAT);
...

ซึ่งจากคำสั่งข้างต้นก็จะได้ message queue ที่มีสิทธิ์การเข้าถึงเป็น 666 หรือ rw-rw-rw- และหมายเลขเฉพาะของ msqid เพื่อให้โปรเซสอื่นๆอ้างอิงหมายเลขของ message queue ที่ต้องการจะเข้าถึงได้ แต่อย่างไรก็ตามโปรเซสใดก็ตามถ้าต้องการเข้าถึง message queue ที่ถูกสร้างโดยโปรแกรม A ก็จะต้องมีขั้นตอนในการนำกุญแจที่จะสามารถเข้ามาเปิดเอาหมายเลข message queue นั้นได้ ดังนั้น โปรแกรมอื่นๆ จึงจำเป็นต้องใช้พารามิเตอร์ภายในฟังก์ชัน ftok() เดียวกันโปรแกรม A ใช้ แล้วจึงจะได้หมายเงขของ message queue (msqid) โดยใช้คำสั่งภายในดังตัวอย่างโปรแกรมข้างล่างนี้

...
key = ftok("/home/wiroon/somefile", 'P');
msqid = msgget(key, 0666);
...

หลังจากสร้างกุญแจและ message queue ได้เรียบร้อยแล้ว แต่อย่างไรก็ตาม message queue ก็ยังมีข้อจำกัดเรื่องขนาดบล็อกข้อมูล ที่จะถูกจำกัดขนาดไว้รวมทั้งจำนวนบล็อกทั้งหมดที่มีบนระบบก็จะถูกจำกัดไว้เช่นกัน ซึ่งภายในระบบปฏิบัติการลีนุกซ์ตัวแปรบล็อกข้อมูลจะประกอบไปด้วย 2 ส่วนคือ MSGMAX(4096) และ MSGMNB(16384) ซึ่งตัวแรกจะหมายถึงความจุของแต่ละบล็อกข้อความ และตัวที่สองจะหมายถึงความจุทั้งหมดของ queue แต่ในบางระบบ ค่าเหล่านี้อาจจะแตกต่างกันไป หรืออาจจะไม่ใช้ค่านี้เลยก็เป็นได้

ฟังก์ชันและตัวแปรที่เกี่ยวข้อง

  • ฟังก์ชัน int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

    • เป็นฟังก์ชันสำหรับใช้ส่งข้อความเข้าไปเก็บใน message queue ตามที่ระบุมหายเลขไว้ (msqid) สำหรับตัวพอยเตอร์ *msgp เป็นตัวชี้ไปยังข้อมูลที่ต้องการนำไปวางบน message queue ตามขนาดไบต์ข้อมูลที่ระบุไว้ (msgsz) นอกจากนั้นก็สามารถกำหนดค่าแฟลกในตัวแปร msgflg ได้เช่นกัน แต่โดยทั่วไปจะกำหนดไว้ในเป็นศูนย์

ในทางปฏิบัติการสร้างข้อมูลเพื่อนำไปเก็บไว้ใน message queue จะมีการสร้างให้อยู่ในลักษณะตัวแปรแบบ struct เช่น

struct my_msgbuf {
    long mtype;
    char mtext[200];
};

ซึ่งสังเกตว่าจะมีการกำหนดตัวแปร mtype ที่เป็นชนิด long ไว้เริ่มต้นเสมอ ตามคำแนะนำของการใช้งาน message queue ดังนั้นเมื่อต้องหาขนาดของข้อมูลที่แท้จริง จำเป็นจะต้องลบด้วยขนาดของตัวแปร mtype ก่อนเสมอ เช่น

/* calculate the size of the data to send: */
int size = sizeof(struct my_msgbuf) - sizeof(long); 

ฟังก์ชันและตัวแปรที่เกี่ยวข้อง

  • ฟังก์ชัน int msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

    • เป็นฟังก์ชันสำหรับอ่านข้อมูลจาก message ลงในตัวแปรพอยเตอร์ *msgp ที่ชี้ไปที่ข้อมูลที่ได้รับมา ดังตัวอย่างข้างล่างนี้

...
key_t key;
int msqid;
struct my_msgbuf rev_msgbuf;
struct pirate_msgbuf pmb; /* where data is to be kept */


key = ftok("/home/wiroon/somefile", 'P');
msqid = msgget(key, 0666 | IPC_CREAT);


/* get data off the queue! */
msgrcv(msqid, &rev_msgbuf, sizeof(struct my_msgbuf) - sizeof(long), 2, 0);
...
  • ฟังก์ชัน int msgctl(int msqid, int cmd, struct msqid_ds *buf);

    • เป็นฟังก์ชันสำหรับใช้ในควบคุม message queue หมายเลขที่ต้องการ (msqid) โดยการระบุคำสั่ง (cmd) ที่ต้องการเช่น IPC_RMID เพื่อต้องการลบ queue เป็นต้น ทิ้งออกจากระบบ นอกจากจะใช้คำสั่ง ipcrm ที่เคยอธิบายไว้ข้างบน ซึ่งในกรณีที่ใช้ IPC_RMID ก็สามารถระบุค่า NULL ให้กับตัวแปร *buf ได้

ตัวอย่างที่ 1

แสดงตัวอย่างโปรแกรมสำหรับส่งบล๊อกข้อมูลเข้าไปใน Message Queues โดยสร้างโปรแกรมส่งข้อมูลไปเก็บไว้ใน message queue ชื่อว่า msg_sender.c และโปรแกรมสำหรับอ่านค่าจาก message queue ชื่อว่า msg_receiver.c ดังข้างล่างนี้

/*
 * System V IPC
 ** msg_sender.c -- writes to a message queue
 */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

struct my_msgbuf {
	long mtype;
	char mtext[200];
};

int main(void) {
	struct my_msgbuf buf;
	int msqid;
	key_t key;

	if ((key = ftok("msg_sender.c", 'B')) == -1) {
		perror("ftok");
		exit(1);
	}

	if ((msqid = msgget(key, 0644 | IPC_CREAT)) == -1) {
		perror("msgget");
		exit(1);
	}
	printf("Enter lines of text, ^D to quit:\n");

	buf.mtype = 1; /* in this case we don’t need to classify any data types yet */
	while (fgets((buf.mtext), sizeof(buf) + 1, stdin)) {
		if (msgsnd(msqid, (struct msgbuf*) &buf, sizeof(buf), 0) == -1)
			perror("msgsnd");
	}

	if (msgctl(msqid, IPC_RMID, NULL) == -1) {
		perror("msgctl");
		exit(1);
	}

	return 0;
}

โปรแกรม msg_receiver.c สำหรับอ่านบล๊อกข้อมูลภายใน Message Queues ที่โปรแกรม msg_sender.c เขียนข้อมูลเข้าไป

/*
 ** msg_receiver.c -- reads from a message queue
 */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

struct my_msgbuf {
	long mtype;
	char mtext[200];
};

int main(void) {
	struct my_msgbuf buf;
	int msqid;
	key_t key;

	if ((key = ftok("msg_sender.c", 'B')) == -1) {
		/* same key as msg_sender.c */
		perror("ftok");
		exit(1);
	}
	if ((msqid = msgget(key, 0644)) == -1) { /* connect to the queue */
		perror("msgget");
		exit(1);
	}
	printf("spock: ready to receive messages, captain.\n");

	for (;;) { /* Spock never quits! */
		if (msgrcv(msqid, (struct msgbuf*) &buf, sizeof(buf), 0, 0) == -1) {
			perror("msgrcv");
			exit(1);
		}
		printf("spock: \"%s\"\n", buf.mtext);
	}

	return 0;
}

ทำการคอมไพล์โปรแกรม msg_sender แล้วรันโปรแกรมเพื่อพร้อมเขียนข้อมูลลง message queue ต่อไป

$ gcc -o msg_sender msg_sender.c -Wall
$ ./msg_sender
Enter lines of text, ^D to quit:

กลับมาที่ หน้าต่าง Terminal ที่ 1 เพื่อทำการพิมพ์ข้อความเพื่อส่งไปยัง msg_receiver ดังตังอย่างข่างล่าง

หน้าต่าง Terminal ที่ 1

...
Enter lines of text, ^D to quit:
Hello from Sender....   <-- ข้อความที่พิพม์จาก msg_sender ในหน้าต่าง terminal ที่ 1

จะสังเกตุเห็นข้อความปรากฏขึ้นในหน้าต่าง Terminal ที่ 2 เนื่องจากโปรเซส msg_receiver ได้รับแล้วแสดงดังนี้

หน้าต่าง Terminal ที่ 2

...
spock: ready to receive messages, captain.
spock: "Hello from Sender...." <-- ข้อความที่ msg_receiver ได้รับ ในหน้าต่าง terminal ที่ 2

Last updated

Assoc. Prof. Wiroon Sriborrirux, Founder of Advance Innovation Center (AIC) and Bangsaen Design House (BDH), Electrical Engineering Department, Faculty of Engineering, Burapha University