Các giao thức được sử dụng để giao tiếp giữa các process với nhau thì được gọi tắt là IPC. Có rất nhiều IPC được sử dụng nhưng bài này mình sẽ giới thiệu qua một số IPC mà mình hay gặp nhất.


Named Pipes

Pipes được thiết kế để giao tiếp giữa cácprocess có quan hệ với nhau thì Named Pipes có thể dùng cho cả các process không có quan hệ. Mặc dù Named Pipes cũng có thể dùng cho các tiến trình có quan hệ, nhưng việc dùng Named Pipes trong trường hợp đó lại không có nhiều ý nghĩa. Với pipe thông thường, ta dùng một pipe cho giao tiếp một chiều và hai pipe cho giao tiếp hai chiều. Ta có thể dùng một Named Pipe duy nhất để giao tiếp hai chiều (giữa server và client, và đồng thời giữa client và server), vì Named Pipe hỗ trợ giao tiếp hai chiều.

Một tên gọi khác của named pipe is FIFO (First-In-First-Out). Dưới đây là systemcall (mknod()) để tạo một named pipe, đây là một loại tệp đặc biệt.

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int mknod(const char *pathname, mode_t mode, dev_t dev);


Lời gọi hệ thống này sẽ tạo ra một file đặc biệt hoặc một nút trong hệ thống file, chẳng hạn như file thông thường, file thiết bị hoặc FIFO. Các tham số truyền vào lời gọi hệ thống gồm có pathname, mode và dev.

  • pathname: đường dẫn của file sẽ được tạo, kèm theo các thuộc tính được xác định bởi mode và dev. Nếu đường dẫn không chỉ định thư mục cụ thể thì file sẽ được tạo trong thư mục hiện tại.
  • mode: xác định kiểu file, bao gồm loại file và quyền truy cập, như đã nêu trong các bảng mô tả.
  • dev: dùng để chỉ thông tin thiết bị, chẳng hạn như số hiệu major và minor của device.


File Type Description

S_IFBLK Block special

S_IFREG Regular file

S_IFCHR Character special

S_IFDIR Directory

S_IFIFO FIFO special

S_IFLNK Symbolic Link


File Mode Description

S_IRWXU Read, write, execute/search by owner

S_IWGRP Write permission, group

S_IRUSR Read permission, owner

S_IXGRP Execute/search permission, group

S_IWUSR Write permission, owner

S_IRWXO Read, write, execute/search by others

S_IXUSR Execute/search permission, owner

S_IROTH Read permission, others

S_IRWXG Read, write, execute/search by group

S_IWOTH Write permission, others

S_IRGRP Read permission, group

S_IXOTH Execute/search permission, others


Chế độ (mode) của file cũng có thể được biểu diễn bằng dạng bát phân (octal) như 0XYZ, trong đó:

  • X đại diện cho quyền của owner (người sở hữu),
  • Y đại diện cho quyền của group (nhóm),
  • Z đại diện cho quyền của others (người dùng khác).


Giá trị của X, Y hoặc Z nằm trong khoảng từ 0 đến 7.

Giá trị quyền tương ứng là:

  • read (đọc): 4
  • write (ghi): 2
  • execute (thực thi): 1


Nếu cần kết hợp nhiều quyền, ta cộng các giá trị lại với nhau.

Ví dụ: nếu ta chỉ định 0640, điều này có nghĩa là:

  • owner có quyền đọc và ghi (4 + 2 = 6),
  • group có quyền đọc (4),
  • others không có quyền (0).


Lời gọi hệ thống này sẽ trả về 0 nếu thành công, và -1 nếu thất bại.

Để biết nguyên nhân thất bại, hãy kiểm tra biến errno hoặc dùng hàm perror().


Giả sử chúng ta có một chương trình chạy server trên một terminal và chạy client trên một terminal khác. Chương trình này chỉ thực hiện giao tiếp một chiều: Client nhận input từ người dùng và gửi thông điệp đó đến server, server sẽ in thông điệp ra màn hình.Quá trình này tiếp tục cho đến khi người dùng nhập chuỗi "end".


Hãy cùng tìm hiểu điều này qua một ví dụ:

  • Bước 1 – Tạo hai tiến trình: một là fifoserver, và một là fifoclient.
  • Bước 2 – Tiến trình Server thực hiện các việc sau:

Tạo một named pipe (dùng system call mknod()) với tên MYFIFO, nếu nó chưa tồn tại.

Mở named pipe ở chế độ chỉ đọc.

FIFO được tạo với quyền: owner có quyền đọc và ghi, group có quyền đọc, và others không có quyền nào.

Chờ vô hạn để nhận tin nhắn từ Client.

Nếu thông điệp nhận được từ client khác "end", server in ra thông điệp đó. Nếu thông điệp là "end", server đóng FIFO và kết thúc tiến trình.

  • Bước 3 – Tiến trình Client thực hiện các việc sau:

Mở named pipe ở chế độ chỉ ghi.

Nhận một chuỗi từ người dùng.

Kiểm tra xem chuỗi nhập vào có phải "end" hay không. Dù là "end" hay không, client đều gửi thông điệp đó cho server. Tuy nhiên, nếu chuỗi là "end", client sẽ đóng FIFO và kết thúc tiến trình.

Lặp lại vô hạn cho đến khi người dùng nhập chuỗi "end".


Hãy xem qua ví dụ của FIFO server file - fifoserver.c

/* Filename: fifoserver.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define FIFO_FILE "MYFIFO"
int main() {
   int fd;
   char readbuf[80];
   char end[10];
   int to_end;
   int read_bytes;
   
   /* Create the FIFO if it does not exist */
   mknod(FIFO_FILE, S_IFIFO|0640, 0);
   strcpy(end, "end");
   while(1) {
      fd = open(FIFO_FILE, O_RDONLY);
      read_bytes = read(fd, readbuf, sizeof(readbuf));
      readbuf[read_bytes] = '\0';
      printf("Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
      to_end = strcmp(readbuf, end);
      if (to_end == 0) {
         close(fd);
         break;
      }
   }
   return 0;
}


Tiếp theo là code của FIFO client sample code - fifoclient.c

/* Filename: fifoclient.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define FIFO_FILE "MYFIFO"
int main() {
   int fd;
   int end_process;
   int stringlen;
   char readbuf[80];
   char end_str[5];
   printf("FIFO_CLIENT: Send messages, infinitely, to end enter \"end\"\n");
   fd = open(FIFO_FILE, O_CREAT|O_WRONLY);
   strcpy(end_str, "end");
   
   while (1) {
      printf("Enter string: ");
      fgets(readbuf, sizeof(readbuf), stdin);
      stringlen = strlen(readbuf);
      readbuf[stringlen - 1] = '\0';
      end_process = strcmp(readbuf, end_str);
      
      //printf("end_process is %d\n", end_process);
      if (end_process != 0) {
         write(fd, readbuf, strlen(readbuf));
         printf("Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
      } else {
         write(fd, readbuf, strlen(readbuf));
         printf("Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
         close(fd);
         break;
      }
   }
   return 0;
}


Biên dịch và kết quả: 


Shared Memory

Nhắc lại rằng: mỗi tiến trình đều có không gian địa chỉ riêng. Nếu một tiến trình muốn truyền thông tin từ vùng nhớ của nó sang một tiến trình khác, thì điều đó chỉ có thể thực hiện thông qua các kỹ thuật IPC (Inter-Process Communication). Như chúng ta đã biết, giao tiếp có thể diễn ra giữa các tiến trình có quan hệ hoặc không có quan hệ.

Thông thường:

  • Giao tiếp giữa các tiến trình có quan hệ được thực hiện bằng Pipes hoặc Named Pipes.
  • Giao tiếp giữa các tiến trình không có quan hệ (ví dụ một tiến trình chạy ở terminal này và một tiến trình chạy ở terminal khác) có thể được thực hiện bằng Named Pipes, hoặc bằng các kỹ thuật IPC phổ biến khác như Shared Memory và Message Queues.


Phần này chúng ta sẽ tìm hiểu về Shared Memory.

Chúng ta biết rằng để giao tiếp giữa hai hay nhiều tiến trình, ta có thể dùng shared memory. Nhưng trước khi sử dụng shared memory, cần phải thực hiện một số thao tác với system calls. Cụ thể như sau:

shmget(): Tạo segment bộ nhớ chia sẻ hoặc sử dụng một segment đã tồn tại.

shmat(): Gắn (attach) tiến trình vào segment shared memory đã tạo.

shmdt(): Tách (detach) tiến trình khỏi segment shared memory

shmctl(): Thực hiện các thao tác điều khiển trên segment shared memory


Bây giờ, hãy xem chi tiết một vài system call liên quan đến shared memory.

#include <sys/ipc.h>
#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg)


System call shmget() ở trên dùng để tạo hoặc cấp phát một segment shared memory kiểu System V. Các tham số cần truyền vào như sau:

  • Tham số: key
  • Dùng để nhận diện segment shared memory.Giá trị key có thể là một giá trị tùy ý (arbitrary value) hoặc được tạo ra từ hàm thư viện ftok(). Cũng có thể là IPC_PRIVATE, nghĩa là tiến trình server và client (có quan hệ cha-con) sẽ dùng key này, tức là giao tiếp giữa các tiến trình có liên quan. Nếu client muốn sử dụng shared memory với key này, client phải là tiến trình con của server. Đồng thời, tiến trình con phải được tạo sau khi tiến trình cha đã tạo shared memory.
  • Tham số: size
  • Là kích thước của segment shared memory, được làm tròn lên theo bội số của PAGE_SIZE.
  • Tham số: shmflg
  • Xác định cờ (flag) cho shared memory, ví dụ: IPC_CREAT → tạo segment mới nếu chưa tồn tại, IPC_EXCL → kết hợp với IPC_CREAT để tạo segment mới, nếu segment đã tồn tại thì gọi sẽ thất bại.
  • Ngoài ra, cần truyền luôn quyền truy cập (permissions) cho segment.

Hàm này sẽ trả về một identifier hợp lệ của shared memory (sử dụng cho các cuộc gọi shared memory tiếp theo) nếu thành công, và trả về -1 nếu thất bại.Để biết nguyên nhân thất bại, bạn có thể kiểm tra biến errno hoặc dùng hàm perror().

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

#include <sys/types.h>
#include <sys/shm.h>

void * shmat(int shmid, const void *shmaddr, int shmflg)


Hàm system call trên dùng để thực hiện thao tác shared memory cho segment kiểu System V, tức là gắn (attach) một segment shared memory vào không gian địa chỉ của tiến trình gọi hàm. Các tham số cần truyền vào như sau:

  • Tham số: shmid
  • Đây là identifier của segment shared memory. Giá trị này chính là kết quả trả về của shmget().
  • Tham số: shmaddr
  • Xác định địa chỉ gắn segment trong không gian địa chỉ của tiến trình. Nếu shmaddr là NULL, hệ thống sẽ tự chọn địa chỉ phù hợp để gắn segment. Nếu shmaddr không phải NULL và cờ SHM_RND được chỉ định trong shmflg, segment sẽ được gắn vào địa chỉ bội số gần nhất của SHMLBA (Lower Boundary Address). Nếu không dùng SHM_RND, shmaddr phải là địa chỉ căn page, nơi segment sẽ được gắn/bắt đầu.
  • Tham số: shmflg
  • Xác định các cờ (flag) cho thao tác attach, ví dụ: SHM_RND → làm tròn địa chỉ tới bội số SHMLBA, SHM_EXEC → cho phép thực thi nội dung segment, SHM_RDONLY → gắn segment chỉ đọc (mặc định là read-write), SHM_REMAP → thay thế mapping hiện tại trong khoảng chỉ định bởi shmaddr và tiếp tục đến hết segment.

Trả về địa chỉ gắn của segment nếu thành công và -1 nếu thất bại. Để biết nguyên nhân thất bại, kiểm tra errno hoặc dùng hàm perror().

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

#include <sys/types.h>
#include <sys/shm.h>

int shmdt(const void *shmaddr)


Hàm system call trên dùng để thực hiện thao tác shared memory cho segment kiểu System V, cụ thể là tách (detach) segment shared memory khỏi không gian địa chỉ của tiến trình gọi hàm.

Tham số: shmaddr 

Địa chỉ của segment shared memory cần tách. Segment cần tách phải là địa chỉ được trả về từ hàm shmat(). Kết quả của hàm sẽ trả về 0 nếu thành công và -1 nếu thất bại.

Để biết nguyên nhân thất bại, kiểm tra biến errno hoặc dùng hàm perror().

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

#include <sys/ipc.h>
#include <sys/shm.h>

int shmctl(int shmid, int cmd, struct shmid_ds *buf)


Hàm system call trên dùng để thực hiện các thao tác điều khiển (control) cho segment shared memory kiểu System V. Các tham số cần truyền vào như sau:

  • Tham số: shmid

Là identifier của segment shared memory, chính là giá trị trả về từ shmget().

  • Tham số: cmd

Là lệnh để thực hiện thao tác điều khiển trên segment shared memory. Các giá trị hợp lệ của cmd gồm:

IPC_STAT Sao chép thông tin hiện tại của các thành phần trong struct shmid_ds vào cấu trúc được trỏ bởi buf. Lệnh này yêu cầu quyền read của segment.

IPC_SET Thiết lập thông tin như user ID, group ID của owner, quyền truy cập, … dựa vào dữ liệu trong cấu trúc buf.

IPC_RMID Đánh dấu segment để xóa. Segment chỉ bị xóa khi tiến trình cuối cùng đã detach khỏi nó.

IPC_INFO Trả về thông tin về giới hạn và tham số shared memory trong cấu trúc trỏ bởi buf.

SHM_INFO Trả về một cấu trúc shm_info chứa thông tin về nguồn tài nguyên hệ thống đã dùng bởi shared memory.

  • Tham số: buf

Là con trỏ tới cấu trúc shared memory struct shmid_ds.Giá trị trong cấu trúc này sẽ được dùng để lấy hoặc thiết lập thông tin tùy theo lệnh cmd.


Hãy xem ví dụ sau:

  • Tạo hai processes: Một tiến trình để ghi vào shared memory (shm_write.c) và một tiến trình để đọc từ shared memory (shm_read.c).
  • Tiến trình ghi (shm_write.c) sẽ tạo một segment shared memory có kích thước 1KB (và các flags). Sau đó gắn (attach) segment này vào không gian địa chỉ của tiến trình.
  • Ghi 5 lần mỗi lần các chữ cái từ A đến E, mỗi lần 5 byte vào shared memory. Byte cuối cùng dùng để đánh dấu kết thúc buffer.
  • Tiến trình đọc (shm_read.c) sẽ đọc dữ liệu từ shared memory và in ra standard output (màn hình).
  • Các tiến trình ghi và đọc được thực hiện cùng lúc. 
  • Sau khi ghi xong, tiến trình ghi sẽ cập nhật biến complete trong struct shmseg để báo hiệu đã hoàn tất việc ghi vào shared memory.
  • Tiến trình đọc sẽ tiếp tục đọc từ shared memory và hiển thị ra màn hình cho đến khi phát hiện biến complete báo hiệu kết thúc.

Chương trình chỉ thực hiện ghi và đọc vài lần để đơn giản hóa và tránh vòng lặp vô hạn, đồng thời giảm sự phức tạp của chương trình.


Code cho write process (shm_write.c)

/* Filename: shm_write.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>

#define BUF_SIZE 1024
#define SHM_KEY 0x1234

struct shmseg {
   int cnt;
   int complete;
   char buf[BUF_SIZE];
};
int fill_buffer(char * bufptr, int size);

int main(int argc, char *argv[]) {
   int shmid, numtimes;
   struct shmseg *shmp;
   char *bufptr;
   int spaceavailable;
   shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
   if (shmid == -1) {
      perror("Shared memory");
      return 1;
   }

   // Attach to the segment to get a pointer to it.
   shmp = shmat(shmid, NULL, 0);
   if (shmp == (void *) -1) {
      perror("Shared memory attach");
      return 1;
   }

   /* Transfer blocks of data from buffer to shared memory */
   bufptr = shmp->buf;
   spaceavailable = BUF_SIZE;
   for (numtimes = 0; numtimes < 5; numtimes++) {
      shmp->cnt = fill_buffer(bufptr, spaceavailable);
      shmp->complete = 0;
      printf("Writing Process: Shared Memory Write: Wrote %d bytes\n", shmp->cnt);
      bufptr = shmp->buf;
      spaceavailable = BUF_SIZE;
      sleep(3);
   }
   printf("Writing Process: Wrote %d times\n", numtimes);
   shmp->complete = 1;

   if (shmdt(shmp) == -1) {
      perror("shmdt");
      return 1;
   }

   if (shmctl(shmid, IPC_RMID, 0) == -1) {
      perror("shmctl");
      return 1;
   }
   printf("Writing Process: Complete\n");
   return 0;
}

int fill_buffer(char * bufptr, int size) {
   static char ch = 'A';
   int filled_count;

   //printf("size is %d\n", size);
   memset(bufptr, ch, size - 1);
   bufptr[size-1] = '\0';
   if (ch > 122)
   ch = 65;
   if ( (ch >= 65) && (ch <= 122) ) {
      if ( (ch >= 91) && (ch <= 96) ) {
         ch = 65;
      }
   }
   filled_count = strlen(bufptr);

   //printf("buffer count is: %d\n", filled_count);
   //printf("buffer filled is:%s\n", bufptr);
   ch++;
   return filled_count;
}


Code cho process read (shm_read.c)

/* Filename: shm_read.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>

#define BUF_SIZE 5
#define SHM_KEY 0x1234

struct shmseg {
   int cnt;
   int complete;
   char buf[BUF_SIZE];
};

int main(int argc, char *argv[]) {
   int shmid;
   struct shmseg *shmp;
   shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
   if (shmid == -1) {
      perror("Shared memory");
      return 1;
   }

   // Attach to the segment to get a pointer to it.
   shmp = shmat(shmid, NULL, 0);
   if (shmp == (void *) -1) {
      perror("Shared memory attach");
      return 1;
   }

   /* Transfer blocks of data from shared memory to stdout*/
   while (shmp->complete != 1) {
      printf("segment contains : \n\"%s\"\n", shmp->buf);
      if (shmp->cnt == -1) {
         perror("read");
         return 1;
      }
      printf("Reading Process: Shared Memory: Read %d bytes\n", shmp->cnt);
      sleep(3);
   }
   printf("Reading Process: Reading Done, Detaching Shared Memory\n");
   if (shmdt(shmp) == -1) {
      perror("shmdt");
      return 1;
   }
   printf("Reading Process: Complete\n");
   return 0;
}


Kết quả:


Message Queues

Tại sao chúng ta cần message queues trong khi đã có shared memory? Có nhiều lý do, hãy chia ra các điểm để dễ hiểu:

Tính duy nhất của dữ liệu: Khi một message được nhận bởi một tiến trình, nó sẽ không còn có sẵn cho các tiến trình khác. Trong khi đó, với shared memory, dữ liệu có thể được truy cập bởi nhiều tiến trình.

Thông điệp nhỏ: Nếu muốn giao tiếp với các message nhỏ, message queue sẽ đơn giản và tiện lợi hơn so với shared memory.

Đồng bộ hóa: Dữ liệu trong shared memory cần được bảo vệ bằng cơ chế đồng bộ hóa (synchronization) khi nhiều tiến trình truy cập cùng lúc. Với message queue, cơ chế này đã được quản lý sẵn.

Tần suất ghi/đọc cao: Nếu việc ghi và đọc dữ liệu trong shared memory diễn ra rất thường xuyên, việc triển khai đồng bộ hóa sẽ phức tạp và khó quản lý, không hiệu quả bằng message queue.

Chỉ một số tiến trình cần dữ liệu: Nếu không phải tất cả tiến trình đều cần truy cập shared memory, mà chỉ một vài tiến trình cần, message queue sẽ hiệu quả hơn.

Giao tiếp với nhiều loại dữ liệu: Tiến trình A gửi message type 1 tới B, Tiến trình A gửi message type 10 tới C, Tiến trình A gửi message type 20 tới D, Trong trường hợp này, message queue dễ triển khai hơn. Message type có thể là số 0, số dương hoặc số âm.

FIFO (First In First Out): Message queue tuân theo thứ tự FIFO, tức là message được chèn vào đầu tiên sẽ được lấy ra trước.

Việc sử dụng shared memory hay message queues phụ thuộc vào yêu cầu của ứng dụng và cách tối ưu hóa việc sử dụng. Cách thức giao tiếp qua Message Queues: Một tiến trình ghi vào message queue, tiến trình khác đọc từ message queue. Khác với shared memory, chỉ một tiến trình đọc được một message, nên dữ liệu không bị trùng lặp và đồng bộ hóa được xử lý sẵn.


Ghi dữ liệu vào Shared Memory theo nhiều gói dữ liệu và đọc từ nhiều tiến trình theo loại message. Một tiến trình ghi vào shared memory các gói dữ liệu khác nhau.Nhiều tiến trình khác có thể đọc dữ liệu dựa trên loại message (eg: message type, …).

Để giao tiếp giữa các tiến trình bằng message queues, cần thực hiện các bước sau:

  1. Tạo hoặc truy cập message queue (msgget()).
  2. Gửi message vào queue (msgsnd()).
  3. Nhận message từ queue (msgrcv()).
  4. Xóa message queue khi không còn sử dụng (msgctl()).

Bây giờ chúng ta sẽ đi qua từng systemcall ở trên:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgget(key_t key, int msgflg)


Tham số: key - Dùng để nhận diện message queue. Giá trị key có thể là một giá trị tùy ý hoặc được tạo từ hàm thư viện ftok().

Tham số: msgflg - Xác định các cờ (flag) cho message queue (ví dụ:IPC_CREAT → tạo queue nếu chưa tồn tại, IPC_EXCL → kết hợp với IPC_CREAT để tạo queue, nếu queue đã tồn tại thì gọi sẽ thất bại).


Kết quả trả về:

Trả về identifier hợp lệ của message queue (sử dụng cho các cuộc gọi message queue tiếp theo) nếu thành công và trả về -1 nếu thất bại. Để biết nguyên nhân thất bại, kiểm tra errno hoặc dùng hàm perror().

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgsnd(int msgid, const void *msgp, size_t msgsz, int msgflg)


msgid: Nhận diện message queue, tức là message queue identifier. Giá trị này được trả về khi msgget() thành công.

msgp: Con trỏ tới message cần gửi. Message được định nghĩa trong cấu trúc:

struct msgbuf {
   long mtype;   // Loại message
   char mtext[1]; // Nội dung message
};
  • mtype: dùng để giao tiếp với các loại message khác nhau, chi tiết sẽ được giải thích ở msgrcv().
  • mtext: mảng hoặc cấu trúc chứa dữ liệu thực tế của message, kích thước được chỉ định bởi msgsz (giá trị dương). Nếu không đề cập mtext, message được coi là kích thước 0, điều này được phép.

msgsz: Kích thước của message (message nên kết thúc bằng ký tự null).

msgflg: Chỉ định các cờ (flags), ví dụ: IPC_NOWAIT → trả về ngay lập tức nếu không có message trong queue, MSG_NOERROR → cắt bớt message text nếu dài hơn msgsz byte.

Trả về identifier hợp lệ của message queue (sử dụng cho các cuộc gọi message queue tiếp theo) nếu thành công và trả về -1 nếu thất bại. Để biết nguyên nhân thất bại, kiểm tra errno hoặc dùng hàm perror().

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgrcv(int msgid, const void *msgp, size_t msgsz, long msgtype, int msgflg)


msgid: Nhận diện message queue, tức là message queue identifier. Giá trị này được trả về khi msgget() thành công.

msgp: Con trỏ tới message nhận được, định nghĩa trong cấu trúc:

			struct msgbuf {
  				long mtype;   // Loại message
   				char mtext[1]; // Nội dung message
			};
  • mtype: dùng để giao tiếp với các loại message khác nhau.
  • mtext: mảng hoặc cấu trúc chứa dữ liệu thực tế của message, kích thước được chỉ định bởi msgsz (giá trị dương). Nếu không đề cập mtext, message được coi là kích thước 0, điều này được phép.


msgsz: Kích thước của message nhận được (message nên kết thúc bằng ký tự null).

msgtype: Xác định loại message muốn nhận: 

  • = 0 → nhận message đầu tiên trong queue 
  • > 0 → nhận message đầu tiên có loại msgtype (ví dụ msgtype=10 → chỉ nhận message loại 10, bỏ qua các loại khác)

  • < 0 → nhận message đầu tiên có loại nhỏ hơn hoặc bằng giá trị tuyệt đối của msgtype (ví dụ msgtype=-5 → nhận message loại từ 1 đến 5, loại nhỏ nhất có sẵn.

msgflg: Chỉ định các cờ (flags), ví dụ:

  • IPC_NOWAIT → trả về ngay nếu không có message trong queue
  • MSG_NOERROR → cắt bớt message text nếu dài hơn msgsz byte

Hàm này sẽ trả về một identifier hợp lệ của shared memory (sử dụng cho các cuộc gọi shared memory tiếp theo) nếu thành công, và trả về -1 nếu thất bại.Để biết nguyên nhân thất bại, bạn có thể kiểm tra biến errno hoặc dùng hàm perror().

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgctl(int msgid, int cmd, struct msqid_ds *buf)


msgid: Nhận diện message queue, tức là message queue identifier. Giá trị này được trả về khi msgget() thành công.

cmd: Lệnh để thực hiện thao tác điều khiển trên message queue. Các giá trị hợp lệ của cmd gồm:

IPC_STAT Sao chép thông tin hiện tại của các thành phần trong struct msqid_ds vào cấu trúc được trỏ bởi buf. Lệnh này yêu cầu quyền read của queue.

IPC_SET Thiết lập thông tin như user ID, group ID của owner, quyền truy cập, … dựa vào dữ liệu trong cấu trúc buf.

IPC_RMID Xóa message queue ngay lập tức.

IPC_INFO Trả về thông tin về giới hạn và tham số message queue trong cấu trúc buf kiểu struct msginfo.

MSG_INFO Trả về một cấu trúc msginfo chứa thông tin về nguồn tài nguyên hệ thống đã sử dụng bởi message queue.

buf: Con trỏ tới cấu trúc message queue struct msqid_ds. Giá trị trong cấu trúc này sẽ được dùng để lấy hoặc thiết lập thông tin tùy theo lệnh cmd.


Hàm này sẽ trả về một identifier hợp lệ của shared memory (sử dụng cho các cuộc gọi shared memory tiếp theo) nếu thành công, và trả về -1 nếu thất bại.Để biết nguyên nhân thất bại, bạn có thể kiểm tra biến errno hoặc dùng hàm perror().

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Bước 1 - Tạo hai tiến trình: tiến trình gửi (send) vào message queue → msgq_send.c và tiến trình nhận (receive) từ message queue → msgq_recv.c

Bước 2 - Tạo key bằng hàm ftok(). Ban đầu, tạo file msgq.txt để có được key duy nhất.

Bước 3 - Tiến trình gửi (sending process) sẽ:

Đọc chuỗi nhập từ người dùng.

Loại bỏ ký tự xuống dòng nếu có.

Gửi chuỗi vào message queue.

Lặp lại quá trình cho đến khi kết thúc nhập (CTRL + D).

Khi nhận kết thúc nhập, gửi message "end" để báo hiệu kết thúc tiến trình.

Bước 4 - Tiến trình nhận (receiving process) sẽ:

Đọc message từ queue.

Hiển thị output.

Nếu nhận message "end", tiến trình kết thúc và exit.

Để đơn giản, ví dụ này không sử dụng message type. Một tiến trình ghi vào queue, một tiến trình đọc từ queue. Có thể mở rộng: ví dụ một tiến trình ghi và nhiều tiến trình đọc từ queue.

File gửi message sẽ có dạng như dưới đây (message sending into queue) (msgq_send.c)

/* Filename: msgq_send.c */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define PERMS 0644
struct my_msgbuf {
   long mtype;
   char mtext[200];
};

int main(void) {
   struct my_msgbuf buf;
   int msqid;
   int len;
   key_t key;
   system("touch msgq.txt");

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

   if ((msqid = msgget(key, PERMS | IPC_CREAT)) == -1) {
      perror("msgget");
      exit(1);
   }
   printf("message queue: ready to send messages.\n");
   printf("Enter lines of text, ^D to quit:\n");
   buf.mtype = 1; /* we don't really care in this case */

   while(fgets(buf.mtext, sizeof buf.mtext, stdin) != NULL) {
      len = strlen(buf.mtext);
      /* remove newline at end, if it exists */
      if (buf.mtext[len-1] == '\n') buf.mtext[len-1] = '\0';
      if (msgsnd(msqid, &buf, len+1, 0) == -1) /* +1 for '\0' */
      perror("msgsnd");
   }
   strcpy(buf.mtext, "end");
   len = strlen(buf.mtext);
   if (msgsnd(msqid, &buf, len+1, 0) == -1) /* +1 for '\0' */
   perror("msgsnd");

   if (msgctl(msqid, IPC_RMID, NULL) == -1) {
      perror("msgctl");
      exit(1);
   }
   printf("message queue: done sending messages.\n");
   return 0;
}

Code cho process nhận message (msgq_recv.c)
/* Filename: msgq_recv.c */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define PERMS 0644
struct my_msgbuf {
   long mtype;
   char mtext[200];
};

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

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

   if ((msqid = msgget(key, PERMS)) == -1) { /* connect to the queue */
      perror("msgget");
      exit(1);
   }
   printf("message queue: ready to receive messages.\n");

   for(;;) { /* normally receiving never ends but just to make conclusion
             /* this program ends wuth string of end */
      if (msgrcv(msqid, &buf, sizeof(buf.mtext), 0, 0) == -1) {
         perror("msgrcv");
         exit(1);
      }
      printf("recvd: \"%s\"\n", buf.mtext);
      toend = strcmp(buf.mtext,"end");
      if (toend == 0)
      break;
   }
   printf("message queue: done receiving messages.\n");
   system("rm msgq.txt");
   return 0;
}


Kết quả: