C 和 Rust 代码片段之间的不同行为
Different behaviors between C and Rust code snippets
我有一个 C 代码片段,它使用 pipe()
和 fork()
在父进程和子进程之间进行通信。我想在 Rust 中复制它。 C中使用的POSIX API,如果Rust标准库中有对应的,优先考虑。
但是,这两个代码片段具有不同的行为。这些不同行为的根本原因是什么?
C 代码片段
// error handling is omitted for simplicity's sake
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define CHILD_MESS "Child: I wanna cookie\n"
#define PAR_MESS "Parent: testing...\n"
int main() {
int pipe_fd[2] = {-1, -1};
int len = 0;
char buf[100] = {'[=10=]'};
int read_len = 0;
pipe(pipe_fd);
switch (fork()) {
case 0: // in the child
len = strlen(CHILD_MESS);
while(1) {
write(pipe_fd[1], CHILD_MESS, len);
sleep(5);
}
break;
default: // in the parent
len = strlen(PAR_MESS);
while(1) {
write(pipe_fd[1], PAR_MESS, len);
sleep(1);
read_len = read(pipe_fd[0], buf, 100);
if (read_len <= 0) {
break;
}
write(1, buf, read_len);
}
}
return 0;
}
Rust 代码片段
use nix::unistd::{fork, pipe, ForkResult}; // needs extern crate `nix`
use std::fs::File;
use std::io::{Read, Write};
use std::os::unix::io::{FromRawFd, RawFd};
use std::thread::sleep;
use std::time::Duration;
const CHILD_MSG: &str = "Child: I wanna cookie\n";
const PAR_MSG: &str = "Parent: testing...\n";
fn main() {
let (read_end, write_end): (RawFd, RawFd) = pipe().unwrap();
let mut buf: [u8; 100] = [0; 100];
let mut read_end: File = unsafe { File::from_raw_fd(read_end) };
let mut write_end: File = unsafe { File::from_raw_fd(write_end) };
match unsafe { fork() } {
Ok(res) => match res {
ForkResult::Child => loop {
write_end.write_all(CHILD_MSG.as_bytes()).expect("write");
sleep(Duration::from_secs(5));
},
ForkResult::Parent { child: _ } => loop {
write_end.write_all(PAR_MSG.as_bytes()).expect("write");
sleep(Duration::from_secs(1));
let n = read_end.read(&mut buf).unwrap();
if n == 0 {
break;
}
print!("{}", std::str::from_utf8(&buf).unwrap());
},
},
_ => (),
}
}
预期的行为类似于:
$ gcc main.c && ./a.out
Parent: testing...
Child: I wanna cookie
Parent: testing...
Parent: testing...
Parent: testing...
Parent: testing... // Five seconds elapsed
Child: I wanna cookie
Parent: testing...
...
After execution:
One second elapsed: print `Parent: testing...\nChild: I wanna cookie\n`
Two seconds elapsed: print `Parent: testing...`
Three seconds elapsed: print `Parent: testing...`
...
Five seconds elapsed: print `Parent: testing...\nChild: I wanna cookie\n`
...
但是,对于 Rust 代码片段,我得到如下信息:
$ cargo run -q
Parent: testing...
Child: I wanna cookie
Parent: testing...
Child: I wanna cookie
...
After execution:
One second elapsed: print `Parent: testing...\nChild: I wanna cookie\n`
Two second elapsed: print `Parent: testing...\nChild: I wanna cookie\n`
Three seconds elapsed: print `Parent: testing...\nChild: I wanna cookie\n`
...
它每秒打印 Parent: testing...\nChild: I wanna cookie\n
我的环境:
$ uname -a
Linux pop-os 5.17.5-76051705-generic #202204271406~1651504840~22.04~63e51bd SMP PREEMPT Mon May 2 15: x86_64 x86_64 x86_64 GNU/Linux
$ ldd --version
ldd (Ubuntu GLIBC 2.35-0ubuntu3) 2.35
$ rustc --version
rustc 1.60.0 (7737e0b5c 2022-04-04)
它们之间的区别在于 print!("{}", std::str::from_utf8(&buf).unwrap());
会将整个 buf
刷新到标准输出,而 write(1, buf, read_len);
只会写入 read_len
个字节。
如果我们在 C 中将 write(1, buf, read_len);
更改为 write(1, buf, 100);
或在 Rust 中使用 stdout().write(&buf[0..n]).unwrap();
它们具有相同的行为。
我有一个 C 代码片段,它使用 pipe()
和 fork()
在父进程和子进程之间进行通信。我想在 Rust 中复制它。 C中使用的POSIX API,如果Rust标准库中有对应的,优先考虑。
但是,这两个代码片段具有不同的行为。这些不同行为的根本原因是什么?
C 代码片段
// error handling is omitted for simplicity's sake
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define CHILD_MESS "Child: I wanna cookie\n"
#define PAR_MESS "Parent: testing...\n"
int main() {
int pipe_fd[2] = {-1, -1};
int len = 0;
char buf[100] = {'[=10=]'};
int read_len = 0;
pipe(pipe_fd);
switch (fork()) {
case 0: // in the child
len = strlen(CHILD_MESS);
while(1) {
write(pipe_fd[1], CHILD_MESS, len);
sleep(5);
}
break;
default: // in the parent
len = strlen(PAR_MESS);
while(1) {
write(pipe_fd[1], PAR_MESS, len);
sleep(1);
read_len = read(pipe_fd[0], buf, 100);
if (read_len <= 0) {
break;
}
write(1, buf, read_len);
}
}
return 0;
}
Rust 代码片段
use nix::unistd::{fork, pipe, ForkResult}; // needs extern crate `nix`
use std::fs::File;
use std::io::{Read, Write};
use std::os::unix::io::{FromRawFd, RawFd};
use std::thread::sleep;
use std::time::Duration;
const CHILD_MSG: &str = "Child: I wanna cookie\n";
const PAR_MSG: &str = "Parent: testing...\n";
fn main() {
let (read_end, write_end): (RawFd, RawFd) = pipe().unwrap();
let mut buf: [u8; 100] = [0; 100];
let mut read_end: File = unsafe { File::from_raw_fd(read_end) };
let mut write_end: File = unsafe { File::from_raw_fd(write_end) };
match unsafe { fork() } {
Ok(res) => match res {
ForkResult::Child => loop {
write_end.write_all(CHILD_MSG.as_bytes()).expect("write");
sleep(Duration::from_secs(5));
},
ForkResult::Parent { child: _ } => loop {
write_end.write_all(PAR_MSG.as_bytes()).expect("write");
sleep(Duration::from_secs(1));
let n = read_end.read(&mut buf).unwrap();
if n == 0 {
break;
}
print!("{}", std::str::from_utf8(&buf).unwrap());
},
},
_ => (),
}
}
预期的行为类似于:
$ gcc main.c && ./a.out
Parent: testing...
Child: I wanna cookie
Parent: testing...
Parent: testing...
Parent: testing...
Parent: testing... // Five seconds elapsed
Child: I wanna cookie
Parent: testing...
...
After execution:
One second elapsed: print `Parent: testing...\nChild: I wanna cookie\n`
Two seconds elapsed: print `Parent: testing...`
Three seconds elapsed: print `Parent: testing...`
...
Five seconds elapsed: print `Parent: testing...\nChild: I wanna cookie\n`
...
但是,对于 Rust 代码片段,我得到如下信息:
$ cargo run -q
Parent: testing...
Child: I wanna cookie
Parent: testing...
Child: I wanna cookie
...
After execution:
One second elapsed: print `Parent: testing...\nChild: I wanna cookie\n`
Two second elapsed: print `Parent: testing...\nChild: I wanna cookie\n`
Three seconds elapsed: print `Parent: testing...\nChild: I wanna cookie\n`
...
它每秒打印 Parent: testing...\nChild: I wanna cookie\n
我的环境:
$ uname -a
Linux pop-os 5.17.5-76051705-generic #202204271406~1651504840~22.04~63e51bd SMP PREEMPT Mon May 2 15: x86_64 x86_64 x86_64 GNU/Linux
$ ldd --version
ldd (Ubuntu GLIBC 2.35-0ubuntu3) 2.35
$ rustc --version
rustc 1.60.0 (7737e0b5c 2022-04-04)
它们之间的区别在于 print!("{}", std::str::from_utf8(&buf).unwrap());
会将整个 buf
刷新到标准输出,而 write(1, buf, read_len);
只会写入 read_len
个字节。
如果我们在 C 中将 write(1, buf, read_len);
更改为 write(1, buf, 100);
或在 Rust 中使用 stdout().write(&buf[0..n]).unwrap();
它们具有相同的行为。