文章目录

  • 一、fork
  • 二、exec函数族
  • 三、system
  • 四、wait和waitpid
  • 五、无名管道
  • 六、有名管道
  • 七、消息队列
  • 八、共享内存
  • 九、信号量
  • 十、信号

一、fork

#include <sys/types.h>
#include <unistd.h>pid_t fork(void);

fork()函数用于创建一个和当前进程映像相同的子进程。fork执行一次,返回两次,可能有三种不同的值。如果执行成功,在父进程中返回子进程的ID,在子进程中返回0,如果执行失败返回-1;

vfork()函数功能与fork类似,二者区别如下:

  • fork子进程拷贝父进程的数据段;vfork子进程与父进程共享数据段;
  • fork父、子进程的执行次序不确定;vfork子进程先运行,父进程后运行。

此外,在调用fork创建子进程时会采用写时复制机制,即并不会把父进程占用的所有内存页复制给子进程,而是让父子进程共用相同的内存页,只有当子进程或者父进程对内存页进行修改时才会进行复制。具体来说,在创建子进程时会采用类似于共享内存的方式将子进程的逻辑地址空间映射到父进程的物理地址空间上,同时这块内存会被设置为只读(设置为只读是为了当对内存进行写操作时触发缺页异常),当子进程或者父进程对内存数据进行修改时,便会触发写时复制机制,将原来的内存页复制一份新的,并重新设置其内存映射关系,将发生缺页异常的进程的内存读写权限设置为可读写

#include <unistd.h>
#include <errno.h>#include <stdio.h>
#include <stdlib.h>int main() {int fork_pid = -1;fork_pid = fork();if (fork_pid > 0) {printf("I am a parent process, my pid is %d, my child is %d.\n", getpid(), fork_pid);} else if (fork_pid == 0) {printf("I am a child process, my pid is %d, my parent is %d.\n", getpid(), getppid());} else {perror("Create failed");exit(1);}exit(0);
}

运行结果

atreus@atreus-virtual-machine:~/code/220317$ make
gcc -Wall main.c -o main
atreus@atreus-virtual-machine:~/code/220317$ ./main
I am a parent process, my pid is 8480, my child is 8481.
I am a child process, my pid is 8481, my parent is 8480.
atreus@atreus-virtual-machine:~/code/220317$

在早期的系统中,创建进程比较简单。当调用fork时,内核会把所有的内部数据结构复制一份,复制进程的页表项,然后把父进程的地址空间中的内容也复制到子进程的地址空间中。但是从内核角度来说,这种复制方式是非常耗时的。因此,在现代的系统中采取了更多的优化。现代的Linux系统采用了写时复制技术(Copy on Write),而不是一创建子进程就将所有的数据都复制一份。

Copy on Write(COW)的主要思路是:如果子进程/父进程只是读取数据,而不是对数据进行修改,那么复制所有的数据是不必要的。因此,子进程/父进程只要保存一个指向该数据的指针就可以了。当子进程/父进程要去修改数据时,那么再复制该部分数据即可。这样也不会影响到子父进程的执行。因此,在执行fork时,子进程首先只复制一个页表项,当子进程/父进程有写操作时,才会对所有的数据块进行复制操作。

二、exec函数族

#include <unistd.h>extern char **environ;int execl(const char *pathname, const char *arg, .../* (char  *) NULL */);
int execlp(const char *file, const char *arg, .../* (char  *) NULL */);
int execle(const char *pathname, const char *arg, .../*, (char *) NULL, char *const envp[] */);
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);

exec函数族用被执行的程序替换调用它的程序,以上六个函数均为库函数,最终都要执行系统调用execve来实现相应功能。

与fork创建一个新的进程,产生一个新的PID不同,exec会启动一个新程序,替换原有的进程,因此进程的PID不会改变。如果调用成功,exec不会有返回值,否则返回-1。

函数参数如下:

  • pathname要执行的程序路径,可以是绝对路径或者是相对路径。在execl、execle和execv这3个函数中,使用带路径名的文件名作为参数。
  • file要执行的程序名称。如果该参数中包含/字符,则视为路径名直接执行。否则视为单独的文件名,系统将根据PATH环境变量指定的路径顺序搜索指定的文件。
  • argv:被执行程序所需的命令行参数数组
  • envp:带有该参数的exec函数可以在调用时指定一个环境变量数组,其他不带该参数的exec函数则使用调用进程的环境变量,类似于main函数中的第三个参数。
  • arg:要执行的程序的首个参数,即程序名本身,相当于argv[0]。
  • ...命令行参数列表,调用相应程序时有多少命令行参数就需要有多少个输入参数项。注意:在使用此类函数时,在所有命令行参数的最后应该增加一个空的参数项NULL,表明命令行参数结束。

main.c

#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>#include <stdio.h>
#include <stdlib.h>#define MAX_SIZE 10int main() {int fork_pid = -1;char *envp[MAX_SIZE] = {"my_path", NULL};char *arg_v[MAX_SIZE] = {"show", "execv", NULL};char *arg_vp[MAX_SIZE] = {"show", "execvp", NULL};char *arg_vpe[MAX_SIZE] = {"show", "execvpe", NULL};fork_pid = fork();if (fork_pid > 0) {printf("Parent porcess is %d, my child porcess is %d.\n", getpid(), fork_pid);waitpid(fork_pid, NULL, 0);} else if (fork_pid == 0) {// execl("./show", "show", "execl", NULL);// execlp("./show", "show", "execlp", NULL);execle("./show", "show", "execle", NULL, envp);// execv("./show", arg_v);// execvp("./show", arg_vp);// execvpe("./show", arg_vpe, envp);perror("Can't execute the exec family of functions");exit(1);} else {perror("Create failed:");exit(1);}exit(0);
}

show.c

#include <unistd.h>
#include <errno.h>#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main(int argc, char *argv[], char *env[]) {printf("Show is execued by %s.\n", argv[1]);printf("Child porcess is %d, my parent porcess is %d.\n", getpid(), getppid());/* 如果是execle或者execvpe调用的输出环境变量 */if (*(argv[1] + strlen(argv[1]) - 1) == 'e') {for (int i = 0; env[i] != NULL; i++) {printf("env[%d] = %s\n", i, env[i]);} }exit(0);
}

运行结果

atreus@atreus-virtual-machine:~/code/220317$ gcc -Wall main.c -o main
main.c: In function ‘main’:
main.c:16:11: warning: unused variable ‘arg_vpe’ [-Wunused-variable]16 |     char *arg_vpe[MAX_SIZE] = {"show", "execvpe", NULL};|           ^~~~~~~
main.c:15:11: warning: unused variable ‘arg_vp’ [-Wunused-variable]15 |     char *arg_vp[MAX_SIZE] = {"show", "execvp", NULL};|           ^~~~~~
main.c:14:11: warning: unused variable ‘arg_v’ [-Wunused-variable]14 |     char *arg_v[MAX_SIZE] = {"show", "execv", NULL};|           ^~~~~
atreus@atreus-virtual-machine:~/code/220317$ gcc -Wall show.c -o show
atreus@atreus-virtual-machine:~/code/220317$ ./main
Parent porcess is 6753, my child porcess is 6754.
Show is execued by execle.
Child porcess is 6754, my parent porcess is 6753.
env[0] = my_path
atreus@atreus-virtual-machine:~/code/220317$

三、system

#include <stdlib.h>int system(const char *command);

system()是一个封装好的库函数,而不是系统调用,它通过调用fork产生子进程,由子进程来执行execl("/bin/sh", "sh", "-c", command, (char *) NULL);,进而来执行参数command中的命令。

#include <stdlib.h>int main() {system("ls -li");exit(0);
}

运行结果

atreus@atreus-virtual-machine:~/code/220317$ make
gcc -Wall main.c -o main
atreus@atreus-virtual-machine:~/code/220317$ ./main
总用量 52
393231 -rwxrwxr-x 1 atreus atreus 16736 3月  21 10:24 main
393268 -rw-rw-r-- 1 atreus atreus    70 3月  21 10:24 main.c
393270 -rw-rw-r-- 1 atreus atreus    38 3月  21 09:54 makefile
393257 -rwxrwxr-x 1 atreus atreus 16872 3月  21 10:11 show
393620 -rw-rw-r-- 1 atreus atreus   521 3月  21 10:10 show.c
atreus@atreus-virtual-machine:~/code/220317$

四、wait和waitpid

#include <sys/types.h>
#include <sys/wait.h>pid_t wait(int *wstatus);pid_t waitpid(pid_t pid, int *wstatus, int options);

wait()函数和waitpid()函数会使父进程等待子进程的退出,主要用于解决僵尸进程和孤儿进程。

僵尸进程和孤儿进程:

  • 僵尸进程:一个父进程利用fork创建子进程,如果子进程退出,而父进程没有利用wait或者waitpid来获取子进程的状态信息,那么子进程的状态描述符依然保存在系统中。
  • 孤儿进程:一个父进程退出,而它的一个或几个子进程仍然还在运行,那么这些子进程就会变成孤儿进程,孤儿进程将被init进程(PID为1)所收养,并由init进程对它们完成状态收集的工作。
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 08:29 ?        00:00:01 /sbin/init splash

waitpid()函数函数参数如下:

  • pid:参数pid是需要等待的一个或多个进程的pid,它必须是下面这四种情况之一:
    <-1:等待所有这样的子进程,他们的组ID是参数pid的绝对值。例如传递-100表示等待所有在进程组100中的子进程;
    -1:等待任意子进程,和wait等效;
    0:等待与调用进程处于同一进程组的任意进程;
    >0:等待进程pid等于传入值的那个子进程。例如传递100表示等待pid为100的子进程;
  • wstatus:与wait一样,这个参数可以用来包含一些关于子进程的附加信息,如果只是想把进程回收,可以设定为NULL。
  • option:此参数要么为0,要么是下面选项进行或运算:
    WNOHANG:如果要等待的子进程已经结束,或者停止,或者处于继续运行的状态,则waitpid不会阻塞,而是立刻返回;
    WUNTRACED
    WCONTINUED

在调用成功时,waitpid返回状态发生改变的那个进程的pid。如果设置了WNOHANG参数,那么等待的进程状态没有改变时返回0。发生错误时返回-1。

code.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>int main() {pid_t pid;pid = fork();if (pid == -1) {// 创建进程失败printf("创建进程失败(%s)!\n", strerror(errno));return -1;} else if (pid == 0) {// 子进程sleep(2);printf("This is child process\n");exit(6);} else {// 父进程int status;if (waitpid(-1, &status, 0) != -1) {if (WIFEXITED(status)) {printf("子进程正常退出,退出代码:%d\n", WEXITSTATUS(status));}}printf("This is parent process\n");exit(0);}
}

运行结果

atreus@AtreusdeMacBook-Pro % gcc code.c -o code
atreus@AtreusdeMacBook-Pro % ./code
This is child process
子进程正常退出,退出代码:6
This is parent process
atreus@AtreusdeMacBook-Pro %

五、无名管道

pipe()函数用于创建一个无名管道:

#include <unistd.h>int pipe(int pipefd[2]);

参数pipefd是一个长度为2的整型数组,用于存放调用该函数所创建的管道的文件描述符。其中 pipefd[0]存放管道读取端的文件描述符,pipefd[1]存放管道写入端的文件描述符。调用成功时,返回值为0;调用失败时,返回值为-1。

由于管道里面是字节流,父子进程同时读写会导致内容混在一起,对于读管道的一方,解析起来就比较困难。常规的使用方法是父子进程一方只能写入,另一方只能读出,管道变成一个单向的通道,以方便使用。因此管道在创建成功后,要在分别关闭读端和写端后再使用。

管道的特点:
①管道只允许具有血缘关系的进程间通信,如父子进程间通信;
②管道只允许单向通信;
③读管道时,如果管道没有数据,读操作会被阻塞;写数据时,如果管道被写满,写操作也会被阻塞。

无名管道实现父子进程间通信

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define MAX_SIZE 100int main() {pid_t fork_pid;int pipe_fd[2];char buffer[MAX_SIZE];if (pipe(pipe_fd) < 0) {//创建一个无名管道perror("Pipe error");exit(1);}fork_pid = fork();if (fork_pid > 0) {/* 父进程写管道 */while (1) {close(pipe_fd[0]);//父进程关闭管道读端memset(buffer, 0, sizeof(buffer));printf("Write pipe:");scanf("%[^\n]%*c", buffer);write(pipe_fd[1], buffer, strlen(buffer));//父进程写管道if (strcmp(buffer, "end") == 0) break;//结束通信usleep(100000);}wait(NULL);} else if (fork_pid == 0) {/* 子进程读管道 */while (1) {close(pipe_fd[1]);//子进程关闭管道写端memset(buffer, 0, sizeof(buffer));read(pipe_fd[0], buffer, sizeof(buffer));//子进程读管道printf("Read pipe:%s\n", buffer);if (strcmp(buffer, "end") == 0) break;}} else {/* 子进程创建失败 */perror("Fork error");exit(1);}exit(0);
}

运行结果

atreus@atreus-virtual-machine:~/code/220317$ make
gcc -Wall main.c -o main
atreus@atreus-virtual-machine:~/code/220317$ ./main
Write pipe:Hello world!
Read pipe:Hello world!
Write pipe:Hello
Read pipe:Hello
Write pipe:end
Read pipe:end
atreus@atreus-virtual-machine:~/code/220317$

六、有名管道

匿名管道应用的一个限制就是只能在具有亲缘关系的进程间通信,如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。

mkfifo()用来创建一个有名管道:

#include <sys/types.h>
#include <sys/stat.h>int mkfifo(const char *pathname, mode_t mode);

前一个参数是管道文件的文件名,后一个参数为文件权限。

注意事项:
①为了保证管道一定被创建,最好两个进程都包含创建管道的代码,谁先运行谁先创建,后运行的直接打开使用;
②不能以O_RDWR模式打开命名管道文件,其行为是未定义的,管道是单向的,不能同时读写。

有名管道实现非血缘进程间通信
写进程write.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define MAX_SIZE 100
#define FIFO_FILE "./fifo"int main() {char buffer[MAX_SIZE];int ret;if (ret = mkfifo(FIFO_FILE, 0644) < 0) {//创建一个有名管道if (ret == -1 && errno != EEXIST) {perror("Make fifo error");exit(1);}}int fd = open(FIFO_FILE, O_WRONLY);while (1) {memset(buffer, 0, sizeof(buffer));printf("Write fifo:");scanf("%[^\n]%*c", buffer);write(fd, buffer, strlen(buffer));//父进程写管道if (strcmp(buffer, "end") == 0) break;//结束通信}exit(0);
}

读进程read.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define MAX_SIZE 100
#define FIFO_FILE "./fifo"int main() {char buffer[MAX_SIZE];int ret;if (ret = mkfifo(FIFO_FILE, 0644) < 0) {//创建一个有名管道if (ret == -1 && errno != EEXIST) {perror("Make fifo error");exit(1);}}int fd = open(FIFO_FILE, O_RDONLY);while (1) {memset(buffer, 0, sizeof(buffer));read(fd, buffer, sizeof(buffer));//子进程读管道printf("Read fifo:%s\n", buffer);if (strcmp(buffer, "end") == 0) break;//结束通信}exit(0);
}

执行结果

七、消息队列

消息队列传送的是有格式的数据,可以实现多进程网状交叉通信以及大规模数据的通信。消息队列建立前必须为其指定一个键值key,通过ftok()(file to key)函数生成这个键值:

#include <sys/types.h>
#include <sys/ipc.h>key_t ftok(const char *pathname, int proj_id);

ftok.c

#include <sys/types.h>
#include <sys/ipc.h>#include <stdio.h>int main() {key_t key = ftok("./", 8);printf("key is 0x%x.\n", key);key_t key_ = ftok("./", 16);printf("key_ is 0x%x.\n", key_);key_t key__ = ftok("./ftok.c", 8);printf("key__ is 0x%x.\n", key__);return 0;
}

执行结果

atreus@atreus-virtual-machine:~/code/220317$ ls -li ./
总用量 36
393231 -rwxrwxr-x 1 atreus atreus 16736 3月  22 12:45 ftok
393257 -rw-rw-r-- 1 atreus atreus   314 3月  22 12:46 ftok.c
393270 -rw-rw-r-- 1 atreus atreus    38 3月  21 09:54 makefile
393619 -rw-rw-r-- 1 atreus atreus   942 3月  22 12:31 receive.c
393276 -rw-rw-r-- 1 atreus atreus  1007 3月  22 12:31 send.c
atreus@atreus-virtual-machine:~/code/220317$ ls -li ../
总用量 4
393265 drwxrwxrwx 3 atreus atreus 4096 3月  22 12:45 220317
atreus@atreus-virtual-machine:~/code/220317$ gcc ftok.c -o ftok
atreus@atreus-virtual-machine:~/code/220317$ ./ftok
key is 0x8050031.
key_ is 0x10050031.
key__ is 0x8050029.
atreus@atreus-virtual-machine:~/code/220317$

其中pathname用于指定一个已经存在的文件,proj_id为子序号,实际上只取其低8位。通过上述代码和执行结果可以大致看出这个key值的生成过程:高位是proj_id转化为十六进制,低位是pathname文件的索引结点转化为16进制(393265D = 60031H)后修改最高位,因此通过同一个消息队列通信的进程必须指定相同的文件和子序号。


在获取到key值后通过msgget()函数获取消息队列标识符:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>int msgget(key_t key, int msgflg);

其中msgflg是一个权限标志,标识消息队列的访问权限,权限标志可以与IPC_CREAT进行或运算,表示指定消息队列不存在时新创建一个消息队列。函数执行成功时返回一个以key命名的消息队列的标识符(非零整数),失败时返回-1。


msgsnd()函数和msgrcv()函数分别用于发送和接收消息:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);struct msgbuf {long mtype;       /* message type, must be > 0 */char mtext[1];    /* message data */
};

参数如下:

  • msqid:由msgget返回的消息队列标识符。
  • msgp:一个指向准备发送或接收的消息的指针,消息必须按照上图中msgbuf的形式定义。
  • msgsz:发送或接收的消息中的数据部分的长度,即不包括长整型消息类型成员变量。
  • msgtyp: 实现接收优先级,如果等于零,就获取队列中的第一个消息;如果它大于零,将获取具有相同消息类型的第一个信息;如果它小于零,就获取类型等于或小于msgtype的绝对值的第一个消息。
  • msgflg:用于控制当前消息队列满或没有符合接收条件的消息时的操作,为0则表示阻塞发送或接收操作。

在调用成功时,msgsnd返回0,msgrcv返回接收的字节数。发生错误时返回-1。


msgctl()可以用来删除消息队列或进行其他操作:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>int msgctl(int msqid, int cmd, struct msqid_ds *buf);

其中cmd参数是将要采取的动作,buf是一个指向消息队列模式和访问权限的结构的指针,如果是想要删除消息队列,参数分别设置为IPC_RMID0


消息队列实现通信
send.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include <errno.h>
#include <wait.h>#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define MAX_SIZE 1024
#define MSG_FILE "./"typedef struct msgbuf {long mtype;char mtext[MAX_SIZE];
} msgbuf;int main() {int msgid;msgbuf *snd_msg;key_t key = ftok(MSG_FILE, 8);printf("Send key is 0x%x.\n", key);if ((msgid = msgget(key, 0644 | IPC_CREAT)) < 0) {perror("Msgget error");exit(1);}/* 发送消息 */snd_msg = (msgbuf *) malloc(sizeof(msgbuf));while (1) {memset(snd_msg, 0, sizeof(msgbuf));printf("Send message>>>");scanf("%[^\n]%*c", snd_msg->mtext);snd_msg->mtype = 211;msgsnd(msgid, snd_msg, sizeof(snd_msg->mtext), 0);if (!strcmp(snd_msg->mtext, "end")) break;usleep(1);}free(snd_msg);/* 删除消息队列 */if(msgctl(msgid, IPC_RMID, 0) == -1) {perror("Msgctl error");exit(1);}return 0;
}

receive.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include <errno.h>
#include <wait.h>#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define MAX_SIZE 1024
#define MSG_FILE "./"typedef struct msgbuf {long mtype;char mtext[MAX_SIZE];
} msgbuf;int main() {int msgid;msgbuf *rcv_msg;key_t key = ftok(MSG_FILE, 8);printf("Receive key is 0x%x.\n", key);if ((msgid = msgget(key, 0644 | IPC_CREAT)) < 0) {perror("Msgget error");exit(1);}/* 接收消息 */rcv_msg = (msgbuf *) malloc(sizeof(msgbuf));while (1) {memset(rcv_msg, 0, sizeof(msgbuf));msgrcv(msgid, rcv_msg, sizeof(rcv_msg->mtext), 211, 0);//接收消息类型为1的消息printf("Receive message:%s\n", rcv_msg->mtext);if (!strcmp(rcv_msg->mtext, "end")) break;}free(rcv_msg);/* 删除消息队列 */if(msgctl(msgid, IPC_RMID, 0) == -1) {perror("Msgctl error");exit(1);}return 0;
}

执行结果

八、共享内存

管道和消息队列的使用需要频繁地访问内核,共享内存可以减少进入内核的次数,从而提高通信效率。与消息队列类似,共享内存在创建之前也需要通过ftok()生成一个键值,然后通过shmget()函数获取共享内存标识符。


在得到共享内存标识符后,可以通过shmat()函数把共享内存区映射到调用进程的地址空间中,通过shmdt()函数解除映射:

#include <sys/types.h>
#include <sys/shm.h>void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);

其中shmid是共享内存标识符,shmflag一般设置为0,表示读写模式,在映射函数中,shmaddr指定共享内存出现在进程内存地址的什么位置,指定为NULL的话内核会自己决定,在解除映射函数中,shmaddr指向连接的共享内存地址。


同样的,在使用结束后要通过shmctl()函数将共享内存删除,效果等同于ipcrm -m shmid命令


共享内存实现进程间通信
send.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <errno.h>
#include <wait.h>#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define MAX_SIZE 4096
#define SHM_FILE "./"int main() {int shmid;char buffer[MAX_SIZE];/* 创建共享内存 */key_t key = ftok(SHM_FILE, 8);printf("Send key is 0x%x.\n", key);if ((shmid = shmget(key, MAX_SIZE, 0644 | IPC_CREAT)) < 0) {perror("Shmget error");exit(1);}/* 映射共享内存 */void *shm = shmat(shmid, NULL, 0);if (shm == (void *)-1) {perror("Shmat error");exit(1);}memset(buffer, 0, sizeof(buffer));/* 发送消息 */printf("Send message>>>");scanf("%[^\n]%*c", buffer);memcpy(shm, buffer, strlen(buffer));/* 解除共享内存映射 */shmdt(buffer);return 0;
}

receive.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <errno.h>
#include <wait.h>#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define MAX_SIZE 4096
#define SHM_FILE "./"int main() {int shmid;char buffer[MAX_SIZE];key_t key = ftok(SHM_FILE, 8);printf("Send key is 0x%x.\n", key);if ((shmid = shmget(key, MAX_SIZE, 0644 | IPC_CREAT)) < 0) {perror("Shmget error");exit(1);}/* 映射共享内存 */void *shm = shmat(shmid, NULL, 0);if (shm == (void *)-1) {perror("Shmat error");exit(1);}memset(buffer, 0, sizeof(buffer));/* 接收消息 */memcpy(buffer, shm, sizeof(buffer));printf("Receive message:%s.\n", buffer);/* 解除共享内存映射 */shmdt(buffer);/* 删除共享内存 */if(shmctl(shmid, IPC_RMID, 0) == -1) {perror("Shmctl error");exit(1);}return 0;
}

执行结果

atreus@atreus-virtual-machine:~/code/220317$ ipcs -m------------ 共享内存段 --------------
键        shmid      拥有者  权限     字节     连接数  状态      atreus@atreus-virtual-machine:~/code/220317$ gcc send.c -o s
atreus@atreus-virtual-machine:~/code/220317$ gcc receive.c -o r
atreus@atreus-virtual-machine:~/code/220317$ ./s
Send key is 0x8050031.
Send message>>>Hello world!
atreus@atreus-virtual-machine:~/code/220317$ ipcs -m------------ 共享内存段 --------------
键        shmid      拥有者  权限     字节     连接数  状态
0x08050031 23         atreus     644        4096       0                       atreus@atreus-virtual-machine:~/code/220317$ ./r
Send key is 0x8050031.
Receive message:Hello world!.
atreus@atreus-virtual-machine:~/code/220317$ ipcs -m------------ 共享内存段 --------------
键        shmid      拥有者  权限     字节     连接数  状态      atreus@atreus-virtual-machine:~/code/220317$

九、信号量

信号量在通过ftok()函数获取键值,semget()函数获取信号量之后,需要先通过semctl()将信号量初始化:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>int semctl(int semid, int semnum, int cmd, ...);

这是一个系统调用:

  • semid是信号量集的标识符。
  • semnum是操作信号在信号集中的编号,第一个信号的编号是0。
  • cmd是要执行的操作,一般取SETVAL设置信号量集中的一个单独的信号量的值,或取GETVAL返回信号量集中的一个单个的信号量的值,在上述两种操作中最后一个参数置0。

初始化后由semop()函数执行P操作和V操作:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>int semop(int semid, struct sembuf *sops, size_t nsops);
struct sembuf
{unsigned short int sem_num;    /* semaphore number */short int sem_op;         /* semaphore operation */short int sem_flg;         /* operation flag */
};

nsops指出要操作的信号个数,sops指向一个sembuf结构体数组,这个结构体的三个成员变量如下:

  • sem_num:操作信号在信号集中的编号,第一个信号的编号是0。
  • sem_op:如果其值为正数,该值会加到现有的信号内含值中,通常用于释放所控资源的使用权;如果其值为负数,而其绝对值又大于信号的现值,操作将会阻塞,通常用于获取资源的使用权;如果其值为0且没有设置IPC_NOWAIT,则调用该操作的进程或者线程将暂时睡眠,直到信号量的值为0。
  • sem_flg:信号操作标志,可能的选择有两种:
    IPC_NOWAIT:对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。
    SEM_UNDO:不论程序正常还是非正常结束,可以保证信号值会被重设为semop()调用前的值,这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。

通过信号量实现共享内存读写同步
send.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <unistd.h>
#include <errno.h>
#include <wait.h>#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define FILE "./"
#define MAX_SIZE 100int main() {int sem_id, shm_id;struct sembuf sem_buf;char buffer[MAX_SIZE];/* 创建键值 */key_t sem_key = ftok(FILE, 1);printf("The sem_key is 0x%x.\n", sem_key);key_t shm_key = ftok(FILE, 2);printf("The shm_key is 0x%x.\n", shm_key);/* 获取描述符 */if ((sem_id = semget(sem_key, 2, 0644 | IPC_CREAT)) < 0) {perror("Semget error");exit(1);}printf("The sem_id is %d.\n", sem_id);if ((shm_id = shmget(shm_key, 2, 0644 | IPC_CREAT)) < 0) {perror("Shmget error");exit(1);}printf("The shm_id is %d.\n", shm_id);/* 初始化信号量 */semctl(sem_id, 0, SETVAL, 1);//0-1semctl(sem_id, 1, SETVAL, 0);//1-0printf("Sem-0 = %d.\n", semctl(sem_id, 0, GETVAL, 0));printf("Sem-1 = %d.\n", semctl(sem_id, 1, GETVAL, 0));sem_buf.sem_flg = SEM_UNDO;/* 映射共享内存 */void *shm = shmat(shm_id, NULL, 0);if (shm == (void *)-1) {perror("Shmat error");exit(1);}/* 发送消息 */while (1) {/* P-0 */sem_buf.sem_num = 0;sem_buf.sem_op = -1;semop(sem_id, &sem_buf, 1);/* 发送 */memset(buffer, 0, sizeof(buffer));printf("Send message>>>");scanf("%[^\n]%*c", buffer);memcpy(shm, buffer, strlen(buffer));/* V-1 */sem_buf.sem_num = 1;sem_buf.sem_op = 1;semop(sem_id, &sem_buf, 1);if (strcmp(buffer, "end") == 0) break;}/* 解除共享内存映射 */shmdt(buffer);return 0;
}

receive.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <unistd.h>
#include <errno.h>
#include <wait.h>#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define FILE "./"
#define MAX_SIZE 100int main() {int sem_id, shm_id;struct sembuf sem_buf;char buffer[MAX_SIZE];/* 创建键值 */key_t sem_key = ftok(FILE, 1);printf("The sem_key is 0x%x.\n", sem_key);key_t shm_key = ftok(FILE, 2);printf("The shm_key is 0x%x.\n", shm_key);/* 获取描述符 */if ((sem_id = semget(sem_key, 2, 0644 | IPC_CREAT)) < 0) {perror("Semget error");exit(1);}printf("The sem_id is %d.\n", sem_id);if ((shm_id = shmget(shm_key, 2, 0644 | IPC_CREAT)) < 0) {perror("Shmget error");exit(1);}printf("The shm_id is %d.\n", shm_id);sem_buf.sem_flg = SEM_UNDO;/* 映射共享内存 */void *shm = shmat(shm_id, NULL, 0);if (shm == (void *)-1) {perror("Shmat error");exit(1);}/* 接收消息 */while (1) {/* P-1 */sem_buf.sem_num = 1;sem_buf.sem_op = -1;semop(sem_id, &sem_buf, 1);/* 接收 */memset(buffer, 0, sizeof(buffer));memcpy(buffer, shm, sizeof(buffer));memset(shm, 0, sizeof(buffer));printf("Receive message:%s.\n", buffer);/* V-0 */sem_buf.sem_num = 0;sem_buf.sem_op = 1;semop(sem_id, &sem_buf, 1);if (strcmp(buffer, "end") == 0) break;}/* 解除共享内存映射 */shmdt(buffer);/* 删除 */if(semctl(sem_id, IPC_RMID, 0) == -1) {perror("Semctl error");exit(1);}if(shmctl(shm_id, IPC_RMID, 0) == -1) {perror("Shmctl error");exit(1);}return 0;
}

执行结果

十、信号

信号有默认处理、忽略、执行用户需要执行的动作三种处理方式,常用信号如下:

信号编号 信号名 说明
2 SIGINT Ctrl+C
3 SIGQUIT Ctrl+Z
9 SIGKILL 此信号不能被阻塞、处理和忽略
11 SIGSEGV 一般由段错误产生
14 SIGALRM 时钟定时信号,alarm()函数使用此信号
15 SIGTERM kill命令缺省产生此信号

kill()函数用于向任意进程或进程组发信号:

#include <sys/types.h>
#include <signal.h>int kill(pid_t pid, int sig);

参数如下:

  • pid:用来指定接收信号的进程,有如下四种取值情况:
    >0:将信号传给进程识别码为pid的进程。
    =0:将信号传给和目前进程位于相同进程组的所有进程。
    =-1:将信号像广播一样传送给系统内所有进程。
    <0:将信号传给进程组识别码为pid绝对值的所有进程。
  • sig:待发送的信号。

signal()函数用于改变信号的处理方式:

#include <signal.h>typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);

参数如下:

  • signum指明要处理的信号编号。
  • handler指定一个函数指针,它有如下三种情况:
    SIG_DFL:默认的信号处理程序。
    SIG_IGN:忽视信号。
    ③指向一个自定义信号处理函数的指针。

signal函数的使用

#include <sys/types.h>
#include <signal.h>
#include <unistd.h>#include <stdio.h>void handle(int sig) {if (sig == SIGINT) {printf("\nSIGINT.\n");}if (sig == SIGALRM) {printf("\nSIGALRM.\n");}
}int main() {signal(SIGINT, handle);signal(SIGALRM, handle);alarm(10);//十秒后向本进程发送SIGALRM信号pause();//等待一个已经被用户处理的信号return 0;
}

执行结果

atreus@atreus-virtual-machine:~/code/220317$ make
gcc -Wall main.c -o m
atreus@atreus-virtual-machine:~/code/220317$ ./m
^C
SIGINT.
atreus@atreus-virtual-machine:~/code/220317$ ./mSIGALRM.
atreus@atreus-virtual-machine:~/code/220317$

LinuxC进程编程相关推荐

  1. LinuxC高级编程——进程

    LinuxC高级编程--进程 宗旨:技术的学习是有限的,分享的精神是无限的. 每个进程在内核中都有一个进程控制块( PCB)来维护进程相关的信息, Linux内核的 进程控制块是task_struct ...

  2. LinuxC高级编程——线程间同步

    LinuxC高级编程--线程间同步 宗旨:技术的学习是有限的,分享的精神是无限的. 1. 互斥锁mutex 多个线程同时访问共享数据时可能会冲突.对于多线程的程序,访问冲突的问题是很普遍的,解决的办法 ...

  3. LinuxC高级编程——线程

    LinuxC高级编程--线程 宗旨:技术的学习是有限的,分享的精神是无限的. 一.线程基础 main函数和信号处理函数是同一个进程地址空间中的多个控制流程,多线程也是如 此,但是比信号处理函数更加灵活 ...

  4. Linux进程实践(1) --Linux进程编程概述

    进程 VS. 程序 什么是程序? 程序是完成特定任务的一系列指令集合. 什么是进程? [1]从用户的角度来看:进程是程序的一次执行过程 [2]从操作系统的核心来看:进程是操作系统分配的内存.CPU时间 ...

  5. linux c一站式编程 答案,linuxc一站式编程答案.docx

    linuxc 一站式编程答案 [篇一: linux 下 c 语言编程基础知识] 在 linux 下进行 c 语言编程所需要的基础知识 .在这篇文章当中 , 我们 将 会学到以下内容 : 源程序编译 m ...

  6. Linux 驱动开发 _ 视频广告机开发、Linux 进程编程介绍

    介绍 Linux 下进程编程知识点,学习 fork 函数.管道.消息队列.共享内存.信号等同通信方式.并且介绍了广告机小项目的实现方法. 任务 1: 学习 Linux 下进程编程 1. 进程简介 进程 ...

  7. [linux专题]基于linux进程编程

    目录 1.linux进程 1.1 基本知识 2 进程编程 2.1 创建进程 2.2 创建守护进程 2.3 退出进程 2.4 执行进程程序 2.5 等待进程结束 2.6 进程id 1.linux进程 1 ...

  8. 嵌入式Linux编程基础ppt,嵌入式LinuxC高级编程.ppt

    <嵌入式LinuxC高级编程.ppt>由会员分享,可在线阅读,更多相关<嵌入式LinuxC高级编程.ppt(45页珍藏版)>请在装配图网上搜索. 1.嵌入式Linux C高级编 ...

  9. 实验六 Linux进程编程,Linux系统编程实验六:进程间通信

    <Linux系统编程实验六:进程间通信>由会员分享,可在线阅读,更多相关<Linux系统编程实验六:进程间通信(10页珍藏版)>请在人人文库网上搜索. 1.实验六:进程间通信l ...

最新文章

  1. docker初体验:docker部署nginx负载均衡集群
  2. JFileChooser和FileFilter的使用
  3. C#中new和override区别
  4. C语言的math相关的函数
  5. sql alter表字段处理
  6. linux文件什么权限比较安全,linux文件安全与权限
  7. 【python】numpy数组的维度增减方法
  8. 使用JedisCluster出现异常:java.lang.NumberFormatException
  9. 对,信中介不去信一头猪
  10. 451.根据字符出现频率排序
  11. 关于Linux下载工具
  12. html制作免费体验登录页面,Html制作简单而漂亮的登录页面
  13. TensorFlow学习笔记(3)——TensorFlow实现Word2Vec
  14. 思岚科技RPlidar A3激光雷达ROS源码详解
  15. seckill配置补发
  16. python -PyPDF2对pdf 进行拆分
  17. 社交鼻祖人人网被卖 曾意气风发比肩Facebook 一代人的回忆终结了
  18. 计算机应用程序是啥意思,为什么某些Mac应用程序需要“使用辅助功能控制此计算机?...
  19. php 网状结构,数据库模型-数据结构-网状模型
  20. 学习发展历史,真的一无所用吗?NO

热门文章

  1. SUMPRODUCT countif
  2. 解决锐捷校园网环境下VMware虚拟机无法上网问题
  3. [转]敏感信息识别方法探究
  4. 【乐逍遥网站设计】营销型企业网站设计制作6要素
  5. python怎么连接前端_前端调用Python后端API的小贴士
  6. mockito的入门与使用
  7. 关于VSCode以及DEV-C++在进行网络编程时出现的WS2_32链接问题
  8. 怎样设计访谈提纲_用户访谈提纲设计
  9. not find or load Qt Platform plugin “windows“ 报错
  10. listview pyqt5 添加_PyQt学习随笔:ListView控件增加列表项