参考引用
- UNIX 环境高级编程 (第3版)
- 黑马程序员-Linux 系统编程
局限性
创建方式
// unistd.h 中所定义的接口通常都是针对系统调用的封装,如 fork、pipe 以及各种 I/O 原语(read、write、close 等)
#include
int pipe(int fd[2]);
// 返回值:成功为 0,出错为 -1
通常,进程会先调用 pipe,接着调用 fork,从而创建从父进程到子进程的 IPC 通道
#include
#include
#include
#include
#include
void sys_err(const char* str) {
perror(str);
exit(1);
}
int main(int argc, char* argv[]) {
int fd[2];
int ret;
pid_t pid;
char* str = "hello pipe\n";
char buf[1024];
ret = pipe(fd);
if (ret == -1) {
sys_err("pipe error");
}
pid = fork();
if (pid > 0) {
close(fd[0]); // 父进程关闭读端
sleep(3); // 如果不想让终端提示和输出混杂在一起,就在父进程写入内容之后 sleep
write(fd[1], str, strlen(str));
close(fd[1]);
} else if (pid == 0) {
close(fd[1]); // 子进程关闭写端
ret = read(fd[0], buf, sizeof(buf));
printf("child read ret = %d\n", ret);
write(STDOUT_FILENO, buf, ret);
close(fd[0]);
}
return 0;
}
$ gcc pipe.c -o pipe
$ ./pipe
child read ret = 11
hello pipe
$ ls | wc -l # 列出当前所在目录下的文件个数
37
$ ls -l | wc -l # 列出当前所在目录下的文件个数 +1
38
#include
#include
#include
#include
#include
void sys_err(const char* str) {
perror(str);
exit(1);
}
int main(int argc, char* argv[]) {
int fd[2];
int ret;
pid_t pid;
ret = pipe(fd); // 父进程先创建一个管道,持有管道的读端和写端
if (ret == -1) {
sys_err("pipe error");
}
pid = fork(); // 子进程同样持有管道的读和写端
if (pid == -1) {
sys_err("fork error");
} else if (pid > 0) { // 父进程读, 关闭写端
close(fd[1]);
dup2(fd[0], STDIN_FILENO); // 重定向 stdin 到管道的读端
execlp("wc", "wc", "-l", NULL); // 执行 wc -l 程序
sys_err("execlp wc error");
} else if (pid == 0) {
close(fd[0]);
dup2(fd[1], STDOUT_FILENO); // 重定向 stdout 到 管道写端
execlp("ls", "ls", NULL); // 子进程执行 ls 命令
sys_err("execlp ls error");
}
return 0;
}
$ gcc pipe2.c -o pipe2
$ ./pipe2
39
$ ls | wc -l
39
#include
#include
#include
#include
#include
#include
void sys_err(const char* str) {
perror(str);
exit(1);
}
int main(int argc, char* argv[]) {
int fd[2];
int ret, i;
pid_t pid;
ret = pipe(fd);
if (ret == -1) {
sys_err("pipe error");
}
// 循环创建 2 个子进程创建兄弟进程
for (i = 0; i < 2; i++) { // 表达式 2 出口,仅限父进程使用
pid = fork();
if (pid == -1) {
sys_err("fork error");
}
if (pid == 0) { // 子进程出口
break;
}
}
if (i == 2) { // 父进程,不参与管道使用
close(fd[0]); // 关闭管道的读端/写端
close(fd[1]);
wait(NULL); // 回收子进程
wait(NULL);
} else if (i == 0) {
close(fd[0]); // 兄长
dup2(fd[1], STDOUT_FILENO); // 重定向 stdout
execlp("ls", "ls", NULL);
sys_err("execlp ls rror");
} else if (i == 1) { // 弟弟
close(fd[1]);
dup2(fd[0], STDIN_FILENO); // 重定向 stdin
execlp("wc", "wc", "-l", NULL);
sys_err("execlp wc error");
}
return 0;
}
$ gcc pipe_bro.c -o pipe_bro
$ ./pipe_bro
42
$ ls | wc -l
42
多个读写端操作管道
#include
#include
#include
#include
#include
int main(int argc, char* argv[]) {
pid_t pid;
int fd[2], i, n;
char buf[1024];
int ret = pipe(fd);
if(ret == -1){
perror("pipe error");
exit(1);
}
for(i = 0; i < 2; i++){
if((pid = fork()) == 0)
break;
else if(pid == -1){
perror("pipe error");
exit(1);
}
}
if (i == 0) {
close(fd[0]);
write(fd[1], "1.hello\n", strlen("1.hello\n"));
} else if(i == 1) {
close(fd[0]);
write(fd[1], "2.world\n", strlen("2.world\n"));
} else {
close(fd[1]); // 父进程关闭写端,留读端读取数据
sleep(1);
n = read(fd[0], buf, 1024); // 从管道中读数据
write(STDOUT_FILENO, buf, n);
for(i = 0; i < 2; i++) // 两个儿子 wait 两次
wait(NULL); // 父进程必须等一下,不然可能俩子进程只写了一个,父进程就读完跑了
}
return 0;
}
$ gcc pipe_mul.c -o pipe_mul
$ ./pipe_mul
1.hello
2.world
管道缓冲区大小
$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 31623
max locked memory (kbytes, -l) 65536
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 31623
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
FIFO 有时被称为命名管道
创建 FIFO 类似于创建文件,FIFO 的路径名存在于文件系统中
#include
#include
#include
// mkfifo 函数中 mode 参数的规格说明与 open 函数中 mode 的相同
int mkfifo(const char *pathname, mode_t mode);
int mkfifoat(int dirfd, const char *pathname, mode_t mode);
// 返回值:成功返回 0,出错返回 -1
当用 mkfifo 或者 mkfifoat 创建 FIFO 时,要用 open 来打开它
当 open 一个 FIFO 时,非阻塞标志 (O_NONBLOCK) 会产生下列影响
一个给定的 FIFO 有多个写进程是常见的
FIFO 有以下两种用途
#include
#include
#include
#include
#include
#include
#include
void sys_err(const char *str) {
perror(str);
exit(1);
}
int main(int argc, char *argv[]) {
int ret = mkfifo("mytestfifo", 0664);
if (ret == -1)
sys_err("mkfifo error");
return 0;
}
$ gcc makefifo.c -o makefifo
$ ./makefifo
$ ll
prw-rw-r-- 1 yxd yxd 0 9月 21 15:45 mytestfifo|
FIFO 实现非血缘关系进程间通信
// fifo_w 写 FIFO
#include
#include
#include
#include
#include
#include
#include
#include
void sys_err(const char *str) {
perror(str);
exit(1);
}
int main(int argc, char *argv[]) {
int fd, i;
char buf[4096];
if (argc < 2) {
printf("Enter like this: ./a.out fifoname\n");
return -1;
}
fd = open(argv[1], O_WRONLY); // 打开管道文件
if (fd < 0) {
sys_err("open error");
}
i = 0;
while (1) {
sprintf(buf, "hello itcast %d\n", i++);
write(fd, buf, strlen(buf)); // 向管道写数据
sleep(1);
}
close(fd);
return 0;
}
// fifo_r 读 FIFO
#include
#include
#include
#include
#include
#include
#include
#include
void sys_err(const char *str) {
perror(str);
exit(1);
}
int main(int argc, char *argv[]) {
int fd, len;
char buf[4096];
if (argc < 2) {
printf("./a.out fifoname\n");
return -1;
}
fd = open(argv[1], O_RDONLY); // 打开管道文件
if (fd < 0) {
sys_err("open error");
}
while (1) {
len = read(fd, buf, sizeof(buf)); // 从管道的读端获取数据
write(STDOUT_FILENO, buf, len);
sleep(1); // 多个读端时应增加睡眠秒数,放大效果
}
close(fd);
return 0;
}
$ gcc fifo_w.c -o fifo_w
$ ./fifo_w mytestfifo
$ gcc fifo_r.c -o fifo_r
$ ./fifo_r mytestfifo
hello itcast 0
hello itcast 1
hello itcast 2
hello itcast 3
...
存储映射 I/O (Memory-mapped I/O) 能将一个磁盘文件映射到存储空间中的一个缓冲区上
使用这种方法,首先应通知内核,将一个指定文件映射到一个存储区域中。这个映射可以通过 mmap 函数实现
#include
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);
函数返回值
函数参数解析
对指定映射存储区的保护要求不能超过文件 open 模式访问权限。例如:若该文件是只读打开的,那么对映射存储区就不能指定 PROT_WRITE
#include
int munmap(void *addr, size_t length);
#include
#include
#include
#include
#include
#include
#include
void sys_err(char* str) {
perror(str);
exit(1);
}
int main(int argc, char* argv[]) {
char* p = NULL;
int fd;
fd = open("testmap", O_RDWR | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
sys_err("open error");
}
// 使用 ftruncate 函数将文件大小设置为 10 字节
// 然后使用 lseek 函数将文件指针移到文件末尾,并获取文件大小
ftruncate(fd, 10); // 需要写权限,才能拓展文件大小
int len = lseek(fd, 0, SEEK_END);
// 使用 mmap 函数将文件映射到内存空间中
p = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (p == MAP_FAILED) {
sys_err("mmap error");
}
// 向内存中的映射空间中写入字符串 "hello mmap"
strcpy(p, "hello mmap");
printf("------%s\n", p);
// 使用 munmap 函数解除内存映射
int ret = munmap(p, len);
if (ret == -1) {
sys_err("munmap error");
}
close(fd);
return 0;
}
$ gcc mmap.c -o mmap
$ ./mmap
------hello mmap
当映射文件大小为 0 时,不能创建映射区,用于映射的文件必须要有实际大小
mmap函数的保险调用方式
- fd = open(“文件名”, O_RDWR);
- mmap(NULL, 有效文件大小, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
#include
#include
#include
#include
#include
#include
int var = 100;
int main(void) {
int *p;
pid_t pid;
int fd;
fd = open("temp", O_RDWR | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
perror("open error");
exit(1);
}
ftruncate(fd, 4);
p = (int*)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
//p = (int*)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
if (p == MAP_FAILED) {
perror("mmap error");
exit(1);
}
close(fd);
pid = fork(); // 创建子进程
if (pid == 0) {
*p = 6000; // 写共享内存
var = 1000;
printf("child, *p = %d, var = %d\n", *p, var);
} else {
sleep(1);
printf("parent, *p = %d, var = %d\n", *p, var); // 读共享内存
wait(NULL);
int ret = munmap(p, 4); // 释放映射区
if (ret == -1) {
perror("munmap error");
exit(1);
}
}
return 0;
}
# 权限设置为 MAP_SHARED
$ gcc fork_mmap.c -o fork_mmap
$ ./fork_mmap
child, *p = 6000, var = 1000
parent, *p = 6000, var = 100
# 权限设置为 MAP_PRIVATE
$ gcc fork_mmap.c -o fork_mmap
$ ./fork_mmap
child, *p = 6000, var = 1000
parent, *p = 0, var = 100
// mmap_w.c 写
#include
#include
#include
#include
#include
#include
#include
struct student {
int id;
char name[256];
int age;
};
void sys_err(const char *str) {
perror(str);
exit(1);
}
int main(int argc, char* argv[]) {
int fd;
struct student stu = {1, "xiaoming", 18};
struct student *p;
fd = open("test_map", O_RDWR | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
sys_err("open error");
}
// 使用 ftruncate 函数设置文件大小,确保其大小与 student 结构体相同
ftruncate(fd, sizeof(stu));
// 使用 mmap 函数将文件映射到内存中,并返回一个指向映射区域的指针
p = mmap(NULL, sizeof(stu), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (p == MAP_FAILED) {
sys_err("mmap error");
}
// 关闭文件描述符,因为在内存映射完成后就不再需要操作文件了
close(fd);
// 进入一个无限循环,将 stu 结构体的内容拷贝到映射区域中,然后将 id 值增加,并休眠 2 秒
while (1) {
memcpy(p, &stu, sizeof(stu));
stu.id++;
sleep(2);
}
munmap(p, sizeof(stu));
return 0;
}
// mmap_r.c 读
#include
#include
#include
#include
#include
#include
#include
struct student {
int id;
char name[256];
int age;
};
void sys_err(const char *str) {
perror(str);
exit(1);
}
int main(int argc, char* argv[]) {
int fd;
struct student stu;
struct student *p;
fd = open("test_map", O_RDONLY);
if (fd == -1) {
sys_err("open error");
}
p = mmap(NULL, sizeof(stu), PROT_READ, MAP_SHARED, fd, 0);
if (p == MAP_FAILED) {
sys_err("mmap error");
}
close(fd);
while (1) {
printf("id = %d, name = %s, age = %d\n", p->id, p->name, p->age);
sleep(2);
}
munmap(p, sizeof(stu));
return 0;
}
$ gcc mmap_w.c -o mmap_w
$ gcc mmap_r.c -o mmap_r
$ ./mmap_w
$ ./mmap_r
id = 3, name = xiaoming, age = 18
id = 4, name = xiaoming, age = 18
id = 5, name = xiaoming, age = 18
id = 6, name = xiaoming, age = 18
...
#include
#include
#include
#include
#include
int main(void) {
int *p;
pid_t pid;
// NULL 表示由系统选择内存地址,4 表示申请的内存大小,PROT_READ | PROT_WRITE 表示此内存区域可读可写
// MAP_SHARED 表示内存区域可以被多个进程共享,MAP_ANON 表示不使用文件作为映射对象(匿名)
// -1 表示不指定文件描述符,0 表示从文件的起始位置开始映射
p = mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
if (p == MAP_FAILED) {
perror("mmap error");
exit(1);
}
// 使用 fork 函数创建了一个子进程。在子进程中,将共享内存中的值设置为 2000,并打印输出
// 在父进程中等待 1 秒后,打印输出共享内存中的值
pid = fork();
if (pid == 0) {
*p = 2000;
printf("child, *p = %d\n", *p);
} else {
sleep(1);
printf("parent, *p = %d\n", *p);
}
munmap(p, 4);
return 0;
}
$ gcc fork_mmap_anon.c -o fork_mmap_anon
$ ./fork_mmap_anon
child, *p = 2000
parent, *p = 2000
$ cat | cat | cat | wc -l
# 另开一个终端
$ ps ajx
# 父 自己 组 会话 终端
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
...
# 下行 bash 为一个会话,满足:PID = PGID = SID
3541 3551 3551 3551 pts/0 3583 Ss 1000 0:00 bash
3551 3583 3583 3551 pts/0 3583 S+ 1000 0:00 cat
3551 3584 3583 3551 pts/0 3583 S+ 1000 0:00 cat
3551 3585 3583 3551 pts/0 3583 S+ 1000 0:00 cat
3551 3586 3583 3551 pts/0 3583 S+ 1000 0:00 wc -l
...
$ kill -9 -3583 # 将整个进程组(3583)内的进程全部杀死
#include
#include
// 返回值 成功: 返回调用进程的会话ID;失败: -1,设置 errno
pid_t getsid(pid_t pid);
#include
#include
// 返回值 成功: 返回调用进程的会话ID; 失败: -1,设置 errno
pid_t setsid(void);
#include
#include
#include
int main(void) {
pid_t pid;
if ((pid = fork()) < 0) {
perror("fork");
exit(1);
} else if (pid == 0) {
printf("child process PID is %d\n", getpid());
// getpid 传 0 表示当前进程组 ID
printf("Group ID of child is %d\n", getpgid(0));
printf("Session ID of child is %d\n", getsid(0));
sleep(5);
setsid(); // 子进程非组长进程,故其成为新会话首进程,且成为组长进程。该进程组id即为会话进程
printf("Changed:\n");
printf("child process PID is %d\n", getpid());
printf("Group ID of child is %d\n", getpgid(0));
printf("Session ID of child is %d\n", getsid(0));
sleep(20);
exit(0);
}
return 0;
}
$ gcc session.c -o session
$ ./session
child process PID is 3764
Group ID of child is 3763
Session ID of child is 3551
$ Changed:
child process PID is 3764
Group ID of child is 3764
Session ID of child is 3764
# 子进程创建了一个新会话
$ ps ajx
1123 3764 3764 3764 ? -1 Ss 1000 0:00 ./session
#include
#include
#include
#include
#include
#include
#include
#include
void sys_err(const char *str) {
perror(str);
exit(1);
}
int main(int argc, char *argv[]) {
pid_t pid;
int ret, fd;
pid = fork();
if (pid > 0) // 父进程终止
exit(0);
pid = setsid(); //创建新会话
if (pid == -1)
sys_err("setsid error");
ret = chdir("/home/itcast/28_Linux"); // 改变工作目录位置
if (ret == -1)
sys_err("chdir error");
umask(0022); // 改变文件访问权限掩码
close(STDIN_FILENO); // 关闭文件描述符 0
fd = open("/dev/null", O_RDWR); // fd --> 0
if (fd == -1)
sys_err("open error");
dup2(fd, STDOUT_FILENO); // 重定向 stdout 和 stderr
dup2(fd, STDERR_FILENO);
while (1); // 模拟守护进程业务
return 0;
}