二十二、Linux 进程与信号---进程创建(续)
22.2 父子进程操作文件
文件操作由两种模式:
IO 系统调用操作文件
标准C IO 操作文件
看代码:
1 #include <unistd.h> 2 #include <string.h> 3 #include <fcntl.h> 4 #include <stdio.h> 5 #include <stdlib.h> 6 7 int g_val = 30;//全局变量,存放在数据段 8 9 int main(void) 10 { 11 int a_val = 30;//局部变量,调用的时候存放在栈中 12 static int s_val = 30;//静态变量,存放在数据段 13 printf("pid: %d", getpid()); 14 15 FILE *fp = fopen("s.txt", "w"); 16 int fd = open("s_fd.txt", O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU | S_IRWXG); 17 18 char *s = "hello world"; 19 ssize_t size = strlen(s) * sizeof(char); 20 21 /* fork 之前,为父进程调用 */ 22 fprintf(fp, "s: %s, pid: %d\n", s, getpid());//标准 IO 写入(带缓存),针对文件操作的是全缓存 23 write(fd, s, size); //内核提供的 IO 系统调用(不带缓存) 24 25 pid_t pid; 26 pid = fork();//创建子进程 27 //在 fork 后,会运行两个进程(父进程和子进程) 28 if(pid < 0) { 29 perror("fork error"); 30 } else if(pid > 0) { 31 //父进程(在父进程中返回的是子进程的 pid) 32 //父进程执行的代码 33 g_val = 40; 34 a_val = 40; 35 s_val = 40; 36 37 printf("I am parent process pid is %d, ppid is %d, fork return is %d\n", 38 getpid(), getppid(), pid); 39 printf("g_val: %p, a_val: %p, s_val: %p\n", &g_val, &a_val, &s_val); 40 } else { 41 //子进程(在子进程中 fork 返回的是0) 42 //子进程执行的代码 43 g_val = 50; 44 a_val = 50; 45 s_val = 50; 46 printf("I am child process pid is %d, ppid is %d, fork return is %d\n", 47 getpid(), getppid(), pid); 48 printf("g_val: %p, a_val: %p, s_val: %p\n", &g_val, &a_val, &s_val); 49 } 50 51 //这里的代码是父子进程都要执行的代码,写入父子进程各自的缓存当中 52 fprintf(fp, " pid: %d, g_val: %d, a_val: %d, s_val: %d\n", getpid(), g_val, a_val, s_val); 53 sleep(1); 54 55 return 0; 56 }
编译运行后,两个文件都生成了。
父进程文件 s.txt
子进程文件 s_fd.txt
系统调用不经过缓存,执行 write 后就直接写进了文件当中,标准IO是写入缓存了。
创建的缓存是在堆当中的,我们的代码是在 fork 之前,那么缓存就在父进程的虚拟空间的堆当中,当 fork 之后,子进程会 COPY 一份父进程的堆空间。
同样 fork 之后也由一份写入缓存的 fprintf,此时是各自写入各自的缓存,在结束的时候父子进程都会清缓存,都会写入 fp 当中
22.3 操作文件时的内核结构体变化
- 子进程只继承父进程的文件描述符表,不继承但共享文件表项和 i-node
- 父进程创建一个子进程后,文件表项中的引用计数器加1 变成 2,当父进程作 close 操作后,计数器减 1,子进程还是可以使用文件表项(即子进程还是可以操作文件),只有当计数器为 0 时,才会释放文件表项。
运行 fork:
例子:父进程调节文件偏移量,子进程写入
process_append.c
1 #include <unistd.h> 2 #include <fcntl.h> 3 #include <stdio.h> 4 #include <stdlib.h> 5 #include <string.h> 6 7 int main(int argc, char *argv[]) 8 { 9 if(argc < 2) 10 { 11 fprintf(stderr, "usage: %s file\n", argv[0]); 12 exit(1); 13 } 14 15 int fd = open(argv[1], O_WRONLY); 16 if(fd < 0) 17 { 18 perror("open error"); 19 exit(1); 20 } 21 22 pid_t pid = fork(); 23 if(pid < 0) 24 { 25 perror("fork error"); 26 exit(1); 27 } 28 else if(pid > 0) 29 {//父进程将文件偏移量调整到文件尾部 30 if(lseek(fd, 0L, SEEK_END) < 0) { 31 perror("lseek error"); 32 exit(1); 33 } 34 } 35 else 36 {//子进程从文件尾部追加内容 37 char *str = "hello child"; 38 ssize_t size = strlen(str) * sizeof(char); 39 40 sleep(3);//保证父进程调节偏移量成功 41 42 //从用户角度去看,子进程会复制一份父进程的文件描述符,都指向同一个文件 43 //从内核角度区看,文件描述符表复制了一份,文件描述符表指向了同一个文件表项,都指向同一个文件 44 //此处的 fd 是从父进程中复制过来的 45 //但和父进程中的 fd 都是指向同一个文件的 46 if(write(fd, str, size) != size) { 47 perror("write error"); 48 exit(1); 49 } 50 } 51 52 printf("pid ; %d finish\n", getpid()); 53 sleep(1); 54 55 //父子进程都要去关闭文件描述符 56 close(fd); 57 58 return 0; 59 }
编译运行: