#include <sys/wait.h>
pid = wait(&status); // wait(NULL)
WIFEXITED(status) 这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值。
WEXITSTATUS(status) 当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出,WEXITSTATUS(status)就会返回5;如果子进程调用exit(7),
WEXITSTATUS(status)就会返回7请注意,如果进程不是正常退出的,也就是说,WIFEXITED返回0,这个值就毫无意义。
int main()
{
int *status,w ;
if (fork()>0)
{/родитель/
w=wait(&status);
if (w==-1){printf(“there is no child\n”);}
else{
printf(“child with pid %d is finished\n”, w);
if (WIFEXITED(status))
printf(“child exits with code%d\n”,
WEXITSTATUS(status));
else printf(“child terminated by signal%d\n”,
WTERMSIG(status)”);
}
}
return 0;
}
if (WIFEXITED(status)&&WEXITSTATUS(status))
execl()函数:执行文件函数
头文件:
#include<unistd.h>
函数说明:execl()用来执行参数path 字符串所代表的文件路径, 接下来的参数代表执行该文件时传递过去的argv(0),argv[1], …,
最后一个参数必须用空指针(NULL)作结束
返回值:如果执行成功则函数不会返回,执行失败则直接返回-1, 失败原因存于errno 中.
范例
execl("/bin/ls",“ls”, “-al”, “/etc/passwd”, (char *)0);
执行:/*执行/bin/ls-al /etc/passwd */
-rw-r–r-- 1 root root 705 Sep 3 13 :52/etc/passwd
execlp()函数:从PATH环境变量中查找文件并执行
头文件:
#include<unistd.h>
函数说明:execlp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名, 找到后便执行该文件, 然后将第二个以后的参数当做该文件的argv[0],argv[1], …,
最后一个参数必须用空指针(NULL)作结束.
返回值:如果执行成功则函数不会返回,执行失败则直接返回-1, 失败原因存于errno 中.
范例
/* 执行ls -al /etc/passwd execlp()会依PATH 变量中的/bin 找到/bin/ls */
execlp(“ls”, “ls”,"-al", “/etc/passwd”, (char *)0);
执行:
-rw-r–r-- 1 root root 705 Sep 3 13 :52/etc/passwd
execl execle execlp
execv execve execvp
简单记忆法:
exec执行新进程
l 用参数列表的方式,最后一个参数时NULL
v 把2参数放在数组内,数组最后一个值是NULL
e 用心的环境变量,最后一个是存放新的环境变量的字符串数组
p 用文件名,非p用的时全路径
execl("/bin/ls",“ls”,"-l","/etc",(char *)0)
char *argv[] = {“ls”, “-l”, “/etc”, (char *)0}; execv("/bin/ls", argv);
execle("/bin/ls",“ls”,"-l", “/etc”,(char *)0,env);
char *argv[] = {"/bin/ls", “-l”, “/etc”, (char *)0}; execvp(“ls”, argv);
execlp(“ls”, “ls”, “-l”, “/etc”, (char *)0);
char *argv[] = {“ls”, “-l”, “/etc”, (char *)0}; execvp(“ls”, argv);
注意:
execlp(“wc”, “wc”,NULL);
perror(“exec wc”); exit(1);
记得写全execl调用失败的返回参数!方便后续操作
关于重定向问题
dup用来复制参数oldfd所指的文件描述符
当复制成功是,返回最小的尚未被使用过的文件描述符
dup2与dup区别是dup2可以用关闭进程中的参oldfd,将oldfd的副本复制到指定newfd新文件描述符中。(重定向)
若参数newfd已经被程序使用,则系统就会将newfd所指的文件关闭,
若newfd等于oldfd,则返回newfd,而不关闭newfd所指的文件。
//将输入到屏幕的数据重定向至文件夹
重写:// wc –w file1>file2
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include <stdlib.h>
int main(int argc, char* argv[]){
int oldfd, newfd;
oldfd = open(argv[2], O_RDWR|O_CREAT|O_TRUNC);
newfd = dup2(oldfd, 1);
execl("/bin/wc", “wc”, “-w”, argv[1], NULL);
close(newfd);
return 0;
}
信号
发送信号:
- kill函数,给其他进程 (包括他们自己) 发信号
int kill(pid_t pid, int sig); 发送一个sig信号给进程pid - alarm函数
unsigned int alarm(unsigned int secs);在secs秒后发送一个SIGALAM信号给调用进程
修改信号的默认行为:
signal(int signum, sighandler_t handler);
handler指明修改信号的方式:
1,SIG_IGN 忽略类型为signum的信号
2,SIG_DFL 恢复类型为signum的信号为其默认行为
3,一个信号处理函数的地址,改变类型为signum的信号的行为为该函数的行为。并在接收到该信号时,调用该函数
ps:signal在main函数的任意位置都能发挥效用
终止信号:
SIGINT 可终止shell中的前台作业
SIGTERM 可终止shell中的前后台作业,可以被阻塞
SIGKILL 可终止shell中的前后台作业,且不可以被阻塞
SIGCHLD 子进程终止后会给父进程发送的一个信号
阻塞信号
read出来的数组最好就把他write出来
自创信号SIGUSR1
手动构造信号的函数,手动发送信号(kill)
===================================
pipe() 实现进程间的读写,把控,和数据输出终点
管道是一种把两个进程之间的标准输入和标准输出连接起来的机制,从而提供一种让多个进程间通信的方法,当进程创建管道时,每次
都需要提供两个文件描述符来操作管道。其中一个对管道进行写操作,另一个对管道进行读操作。对管道的读写与一般的IO系统函数一
致,使用write()函数写入数据,使用read()读出数据。
一端只能用于读,由描述字fd[0]表示,称 其为管道读端,这是进程数据的输入来源read(fd[0], &cnf, sizeof(int));
一端则只能用于写,由描述字fd[1]来表示,称其为管道写端,这是进程数据的输出终点write(fd[1], &cnf, sizeof(int));
先write后read(写管道必须有数据,读管道才会运行)
读管道将全部数据读取完毕的时候,写管道才结束阻塞 开始写数据
子进程完成一轮管道读写后,再次管道读写会发生在父进程中!
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char *argv)
{
int fd[2];
pipe(fd);
if (fork()== 0)
{
dup2(fd[1], 1);
close(fd[1]);
close(fd[0]);
execlp(“ls”, ”ls”, NULL);
perror(“exec ls”); exit(1);
}
else{
dup2(fd[0], 0);
close(fd[0]);
close(fd[1]);
execl(“/usr/bin/wc”, ”wc”, NULL); / запустили программу wc */
perror(“exec wc”);exit(1);
}
}
linux命令“ | ”— 建立管道 eg:echo ‘yes’ | wc -l
在linux中,&和&&,|和||介绍如下:
& 表示任务在后台执行,如要在后台运行redis-server,则有 redis-server &
&& 表示前一条命令执行成功时,才执行后一条命令 ,如 echo '1‘ && echo ‘2’
| 表示管道,上一条命令的输出,作为下一条命令参数,如 echo ‘yes’ | wc -l
|| 表示上一条命令执行失败后,才执行下一条命令,如 cat nofile || echo “fail”
====================================
alarm()闹钟函数: 睡眠sleep指定秒数,给自己(目前进程)发送一个 SIGALARM信号去执行相应的信号处理函数
int alarm ( unsigned scnd)
alarm()函数的主要功能是设置信号传送闹钟,即用来设置信号SIGALRM在经过参数seconds秒数后发送给“目前”的进程。如果未设置信号SIGALARM的处理函数,那么alarm()默认处理:终止进程。
等价于 ==》
sleep(seconds);
kill(当前进程的id, handler);
FIFO文件 (命名管道 )
FIFO文件 该文件可以作为不相关的进程间通信的管道,供所有进程以一般读写命令读取数据。相当于pro版的pipe。pipe没有名字,且只能在有相同父进程的子进程间通信)
使用规则:
1.1 没有使用O_NONBLOCK 旗标时,打开FIFO 来读取的操作会等到其他进程打开FIFO文件来写入才正常返回。同样地,打开FIFO文件来写入的操作会等到其他进程打开FIFO 文件来读取后才正常返回。
如果某个进程打开一个FIFO文件进行读取,则它将阻塞,直到某个进程打开了用于写入的同一通道为止;
如果某个进程打开一个FIFO文件进行写入,它将被阻塞,直到某个进程打开相同的通道进行读取为止;
fd = open( “zieckey_fifo”, O_RDONLY );
fd = open( “zieckey_fifo”, O_WRONLY );
1.2 当使用O_NONBLOCK 旗标时,打开FIFO 文件来读取的操作会立刻返回,但是若还没有其他进程打开FIFO 文件来读取,则写入的操作会返回ENXIO 错误代码。
2 ,进程可以通过在open()调用中指定特殊标志来避免这种阻塞
(在不同的OS版本中,它可以具有不同的符号名称-O_NONBLOCK或O_NDELAY)。
在这种情况下,在上述情况下,调用open()将立即将控制权返回给该进程。
创建命名管道 mkfifo()
int mkfifo ( const char *pathname, mode_t mode );
mkfifo()建立的FIFO文件其他进程都可以用读写一般文件的方式存取。
当使用open()来打开 FIFO文件时,O_NONBLOCK旗标会有影响
注意:记得使用unlink 杀死该命名管道(FIFO文件)
unlink(“name_string”)删除一个名字(某些情况下删除这个名字所指向的文件)
====================================
ftok()函数 :寻找通信(共享内存,消息队列,信号量)中使用的中间介质
为文件烙上id
key_t ftok(const char *pathname, int proj_id);
其中参数fname是指定的文件名,这个文件必须是存在的而且可以访问的。id是子序号,它是一个8bit的整数。即范围是0~255。当函数执行成功,则会返回key_t键值,否则返回-1。
在一般的UNIX中,通常是将文件的索引节点取出,然后在前面加上子序号就得到key_t的值。
消息队列函数
msgget()函数:
“获得”消息队列标识符或“创建”一个消息队列对象,再并返回消息队列标识符
int msgget ( key_t key, int msgflg ):
参数说明:
key = 0 时:建立新的消息队列
key = 正数:视参数msgflg来确定操作(通常要求此值来源于ftok返回的IPC键值)
msgflg:
0:取消息队列标识符,若不存在则函数会报错
IPC_CREAT:当msgflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的消息队列,则新建一个消息队列;如果存在这样的消息队列,返回此消息队列的标识符
IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的消息队列,则新建一个消息队列;如果存在这样的消息队列则报错
msgctl ()函数
获取和设置消息队列的属性
int msgctl(int msqid, int cmd, struct msqid_ds *buf)
参数说明:
msqid:
消息队列标识符
cmd:
IPC_STAT:获得msgid的消息队列头数据到buf中
IPC_SET:设置消息队列的属性,要设置的属性需先存储在buf中,可设置的属性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes
buf:
消息队列管理结构体,请参见消息队列内核结构说明部分
msgsnd()函数
将消息写入到消息队列
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg)
参数说明:
1,msqid 消息队列标识符
2,msgp
发送给队列的消息。msgp可以是任何类型的结构体,但第一个字段必须为long类型,即表明此发送消息的类型,msgrcv根据此接收消息。
msgp定义的参照格式如下:
struct s_msg{ /msgp定义的参照格式/
long type; /* 必须大于0,消息类型 */
char mtext[256]; /消息正文,可以是其他任何类型/
} msgp;
3,msgsz
要发送消息的大小,不含消息类型占用的4个字节,即mtext的长度
4,msgflg
0:当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列
IPC_NOWAIT:当消息队列已满的时候,msgsnd函数不等待立即返回
IPC_NOERROR:若发送的消息大于size字节,则把该消息截断,截断部分将被丢弃,且不通知发送进程。
msgrcv()函数
从消息队列读取消息, 若成功则返回实际读取到的消息数据长度
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
参数说明:
msqid:
消息队列标识符
msgp:
存放消息的结构体,结构体类型要与msgsnd函数发送的类型相同
msgsz:
要接收消息的大小,不含消息类型占用的4个字节
msgtyp:
0:接收第一个消息
0:接收类型等于msgtyp的第一个消息
<0:接收类型等于或者小于msgtyp绝对值的第一个消息
msgflg:
0: 阻塞式接收消息,没有该类型的消息msgrcv函数一直阻塞等待
IPC_NOWAIT:如果没有返回条件的消息调用立即返回,此时错误码为ENOMSG
IPC_EXCEPT:与msgtype配合使用返回队列中第一个类型不为msgtype的消息
IPC_NOERROR:如果队列中满足条件的消息内容大于所请求的size字节,则把该消息截断,截断部分将被丢弃
==========
共享内存
一、什么是共享内存
顾名思义,共享内存就是允许两个不相关的进程访问同一个逻辑内存。共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存。
进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用C语言函数malloc()分配的内存一样。
而如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
特别提醒:共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。
所以我们通常需要用其他的机制来同步对共享内存的访问,例如前面说到的信号量。有关信号量的更多内容,可以查阅另一篇文章:Linux进程间通信 – 使用信号量
二、共享内存的使得
与信号量一样,在Linux中也提供了一组函数接口用于使用共享内存,而且使用共享共存的接口还与信号量的非常相似,而且比使用信号量的接口来得简单。它们声明在头文件 sys/shm.h 中。
1、shmget()函数
该函数用来创建共享内存,它的原型为:
int shmget(key_t key, size_t size, int shmflg);
第一个参数,与信号量的semget函数一样,程序需要提供一个参数key(非0整数),它有效地为共享内存段命名,shmget()函数成功时返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1.
不相关的进程可以通过该函数的返回值访问同一共享内存,它代表程序可能要使用的某个资源,程序对所有共享内存的访问都是间接的,程序先通过调用shmget()函数并提供一个键,
再由系统生成一个相应的共享内存标识符(shmget()函数的返回值),只有shmget()函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。
第二个参数,size以字节为单位指定需要共享的内存容量
第三个参数,shmflg是权限标志,它的作用与open函数的mode参数一样,如果要想在key标识的共享内存不存在时,创建它的话,可以与IPC_CREAT做或操作。
共享内存的权限标志与文件的读写权限一样,举例来说,0644,它表示允许一个进程创建的共享内存被内存创建者所拥有的进程向共享内存读取和写入数据,同时其他用户创建的进程只能读取共享内存。
2、shmat()函数 – at:attach
第一次创建完共享内存时,它还不能被任何进程访问,shmat()函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。它的原型如下:
void *shmat(int shm_id, const void *shm_addr, int shmflg);
第一个参数,shm_id是由shmget()函数返回的共享内存标识。
第二个参数,shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。
第三个参数,shm_flg是一组标志位,通常为0。
调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1.
3、shmdt()函数 – dt:detach
该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。它的原型如下:
int shmdt(const void *shmaddr);
参数shmaddr是shmat()函数返回的地址指针,调用成功时返回0,失败时返回-1.
4、shmctl()函数 – ctl:control
与信号量的semctl()函数一样,用来控制共享内存,它的原型如下:
int shmctl(int shm_id, int command, struct shmid_ds *buf);
第一个参数,shm_id是shmget()函数返回的共享内存标识符。
第二个参数,command是要采取的操作,它可以取下面的三个值 :
IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。
IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
IPC_RMID:删除共享内存段
第三个参数,buf是一个结构指针,它指向共享内存模式和访问权限的结构。
shmid_ds结构 至少包括以下成员:
struct shmid_ds
{
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
};
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
int main(int argc, char **argv){
key_t key;
int shmid;
char *a;
key = ftok(“shm”,‘1’);
shmid = shmget(key, 100, 0666|IPC_CREAT);
a = shmat(shmid, NULL, 0);
strcpy(a, “Hello World”);
sleep(10);
shmdt(a);
return 0;}
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main(int argc, char **argv){
key_t key;
int shmid,i;
char *a,c;
key = ftok(“shm”,‘1’);
shmid = shmget(key, 100, 0);
a = shmat(shmid, NULL, 0);
while(((c=a[i++])!=’\n’)&&(c!=EOF))
printf(“c=%c \n”,c);
shmdt(a);
return 0;}
==========
信号量函数
理解跳转:https://www.cnblogs.com/wuyepeng/p/9748552.html
一,semget()
得到一个信号量集标识符或创建一个信号量集对象, 成功:返回信号量集的标识符
int semget(key_t key, int nsems, int semflg)
key
= 0(IPC_PRIVATE):会建立新信号量集对象
= 大于0的32位整数:视参数semflg来确定操作,通常要求此值来源于ftok返回的IPC键值
nsems
创建信号量集中信号量的个数,该参数只在创建信号量集时有效
msgflg
0:取信号量集标识符,若不存在则函数会报错
IPC_CREAT:当semflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的信号量集,则新建一个信号量集;如果存在这样的信号量集,返回此信号量集的标识符
IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的信号量集,则新建一个消息队列;如果存在这样的信号量集则报错
二,semop()
完成对信号量的P操作或V操作, 成功:返回信号量集的标识符
int semop(int semid, struct sembuf *sops, unsigned nsops)
-
semid:信号量集标识符
-
sops:指向进行操作的信号量集结构体数组的首地址,此结构的具体说明如下:
//semop: 对semid中的1个信号,进行sembuf sops中的操作。即对信号集的第sem_num个信号量,进行操作属性为sem_flg的sem_op操作
//信号值是信号集的semnum位置上的值
struct sembuf {
short semnum; /信号量集合中的信号量编号,0代表第1个信号量/
short val;/*若val>0进行V操作信号量值加val,表示进程释放控制的资源 */
/若val<0进行P操作信号量值减val,若(semval-val)仍<0(semval为该信号量值),则调用进程阻塞,直到资源可用;若设置IPC_NOWAIT不会睡眠,进程直接返回EAGAIN错误/
/若val==0时阻塞等待信号量为0,调用进程进入睡眠状态,直到信号值为0;若设置IPC_NOWAIT,进程不会睡眠,直接返回EAGAIN错误/
short flag; /0 设置信号量的默认操作/
/IPC_NOWAIT设置信号量操作不等待/
/SEM_UNDO 选项会让内核记录一个与调用进程相关的UNDO记录,如果该进程崩溃,则根据这个进程的UNDO记录自动恢复相应信号量的计数值/
}; -
nsops:进行操作信号量的个数,即sops结构变量的个数,需大于或等于1。最常见设置此值等于1,只完成对一个信号量的操作
struct sembuf sem_get={0,-1,IPC_NOWAIT}; /将信号量对象中序号为0的信号量减1/
struct sembuf sem_get={0,1,IPC_NOWAIT}; /将信号量对象中序号为0的信号量加1/
struct sembuf sem_get={0,0,0}; /进程被阻塞,直到对应的信号量值为0/
三, semctl函数原型
semctl (得到一个信号量集标识符或创建一个信号量集对象)
int semctl(int semid, int semnum, int cmd, union semun arg)
semid:
信号量集标识符
semnum:
信号量集数组上的下标,表示某一个信号量
cmd:
IPC_STAT从信号量集上检索semid_ds结构,并存到semun联合体参数的成员buf的地址中
IPC_SET设置一个信号量集合的semid_ds结构中ipc_perm域的值,并从semun的buf中取出值
IPC_RMID从内核中删除信号量集合
GETALL从信号量集合中获得所有信号量的值,并把其整数值存到semun联合体成员的一个指针数组中
GETNCNT返回当前等待资源的进程个数
GETPID返回最后一个执行系统调用semop()进程的PID
GETVAL返回信号量集合内单个信号量的值
GETZCNT返回当前等待100%资源利用的进程个数
SETALL与GETALL正好相反
SETVAL用联合体中val成员的值设置信号量集合中单个信号量的值
arg:
union semun {
short val; /SETVAL用的值/
struct semid_ds* buf; /IPC_STAT、IPC_SET用的semid_ds结构/
unsigned short* array; /SETALL、GETALL用的数组值/
struct seminfo *buf; /为控制IPC_INFO提供的缓存/
} arg;
#define NMAX 256
int main(int argc, char **argv) {
key_t key;
int semid, shmid;
struct sembuf sops;
char shmaddr;
char str[NMAX];
key = ftok(“/usr/ter/exmpl”, ’S’);
semid = semget(key, 1, 0666 | IPC_CREAT);
shmid = shmget(key, NMAX, 0666 | IPC_CREAT);
shmaddr = shmat(shmid, NULL, 0);
semctl(semid,0,SETVAL, (int) 0); / инициализируем семафор значением 0 /
sops.sem_num = 0; sops.sem_flg = 0;
do {
printf(“Введите строку:”);
if (fgets(str, NMAX, stdin) == NULL)
{ strcpy(str, “Q”); }
strcpy(shmaddr, str);
/ предоставляем второму процессу возможность войти /
sops.sem_op = 3;
semop(semid, &sops, 1); / 等到第一个进程的信号量打开-下一次循环迭代 /
sops.sem_op = 0; / ожидание обнуления семафора /
semop(semid, &sops, 1);
} while (str[0] != ‘Q’); /目前第二个进程已从共享内存中读取并断开连接-您可以将其删除/
shmdt(shmaddr) ; / отключаемся от разделяемой памяти /
shmctl(shmid, IPC_RMID, NULL); / уничтожаем разделяемую память /
semctl(semid, 0, IPC_RMID, (int) 0); / уничтожаем семафор */
return 0;
}
2
int main(int argc, char **argv) {
key_t key; int semid, shmid;
struct sembuf sops;
char shmaddr;
char str[NMAX];
key = ftok(“/usr/ter/exmpl”,’S’);
semid = semget(key, 1, 0666 | IPC_CREAT);
shmid = shmget(key, NMAX, 0666 | IPC_CREAT);
/ аналогично предыдущему процессу - инициализации ресурсов */
shmaddr = shmat(shmid, NULL, 0);
sops.sem_num = 0; sops.sem_flg = 0;
do{
printf(“Waiting… \n”);
sops.sem_op = -2;/* будем ожидать, пока “значение семафора” + ”значение sem_op” не станет положительным, т.е. пока значение семафора не станет как минимум 3 (3-2=1 > 0) */
semop(semid, &sops, 1); //现在信号量为1
strcpy(str, shmaddr);
/ 关键部分-使用共享内存-目前,第一个进程无法访问共享内存 /
if (str[0] == ‘Q’) { shmdt(shmaddr); }
sops.sem_op=-1;
semop(semid, &sops, 1);
printf(“Read from shared memory: %s\n”, str);
} while (str[0] != ‘Q’);
return 0;
}
考试:
/dev /porc /sys/bus
SIGUSR1 / SIGKILL / SIGTSTP
fork()输出 1,2