CS252 Final
A | B: A的输出是B的输入,比如cat infile.txt | grep hello
- 在
C程序中,模拟bash的重定向和管道等行为,一定会用到fork()+dup2()+pipe()-
A | B-
pipe()创建一个管道 -
fork()创建两个子进程(执行A和B) -
dup2()把A的stdout->管道写端,B的stdin<-管道读端 -
exec()分别执行A和B命令, 一般是execvp(命令, 参数),execlp()等
-
-
< file-
open(file, O_RDONLY)打开文件 -
dup2(fd, STDIN_FILENO)把文件重新定向为标准输入(通常放在fork后的子进程中)
-
-
> file-
open(file, O_WRONLY | O_CREAT | O_TRUNC, 0644)打开文件 -
dup2(fd, STDOUT_FILENO)把文件重新定向为标准输出
-
-
>> file-
open(file, O_WRONLY | O_CREAT | O_APPEND, 0644)打开文件 -
dup2(fd, STDOUT_FILENO)把文件重新定向为标准输出 ```C //A, B是命令, argvA/argvB是参数组 void redirect(const char * A, char *const argvA[], const char * B, char *const argvB[]) { // A | B int fd[2]; if(pipe(fd) == -1){ perror(“pipe”); exit(1); };
-
int pid1 = fork(); if (pid1 == -1) { perror(“fork1”); exit(1); } if (pid1 == 0) { // handle A close(fd[0]); dup2(fd[1], 1); //STDOUT这个宏不存在,用1代替 close(fd[1]); //重新定向后就可以关闭 execvp(A, argvA); //一般后面直接加perror perror(“exec A”); exit(1); }
int pid2 = fork(); if (pid2 == -1) { perror(“fork2”); exit(1); } if (pid2 == 0){ // handle B close(fd[1]); dup2(fd[0], 0); //STDIN这个宏也不存在,用0代替 close(fd[0]); //重新定向后就可以关闭 execvp(B, argvB); perror(“exec B”); exit(1); }
//在父进程中,关闭所有pip端并等待两个子进程 close(fd[0]); //不关闭读端,写端就会一直挂着 close(fd[1]); waitpid(pid1, NULL, 0); waitpid(pid2, NULL, 0); } ```
-
// < file
void redirectToFile(const char * file) {
int fd = open(file, O_RDONLY); // 不需要管道,所以不是int fd[2], 只要用open(file, O_RDONLY)就可以
if (fd == -1) {
perror("file open failed");
exit(1);
}
if (dup2(fd, STDIN_FILENO) == -1) {
perror("dup2 failed");
exit(1);
}
close(fd); //最后依然是关闭
}
// > file
void redirect(const char * file) {
int fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
perror("open failed");
exit(1);
}
if (dup2(fd, STDOUT_FILENO) == -1) {
perror("dup2 failed");
exit(1);
}
close(fd);
}
// >> file
void redirect(const char * file) {
int fd = open(file, O_WRONLY | O_CREAT | O_APPEND, 0644);
if (fd == -1) {
perror("open failed");
exit(1);
}
if (dup2(fd, STDOUT_FILENO) == -1) {
perror("dup2 failed");
exit(1);
}
close(fd);
}
sort < file.txt: 读取file.txt的内容并进行排序,等价于cat file.txt | sort
-
sort从stdin读取指令:char *argv[] = {"sort", NULL}; execvp("sort", argv); -
sort从file.txt读取指令:char *argv[] = {"sort", "file.txt", NULL}; execvp("sort", argv); -
grep从stdin读取指令:char *argv[] = {"grep", "hello", NULL}; execvp("grep", argv); -
grep从file.txt读取指令:char *argv[] = {"grep", "hello", "file.txt", NULL}; execvp("grep", argv);
//sort < file.txt
void sortCommand(const char * file) {
int fd = open(file, O_RDONLY);
if (fd == -1) {
perror("open file failed");
exit(1);
}
if(dup2(fd, STDIN_FILENO) == -1){
perror("dup2 failed");
exit(1);
}
close(fd);
char *argv[] = {"sort", NULL};
execvp('sort', argv);
perror("sort error");
exit(1);
}
grep A: 从标准输入读取内容,并筛出包含A的行
void grep(char * A){
char * argv[] = {"grep", A, NULL};
execvp("grep", argv);
perror("exec fail");
exit(1);
}
command >> file.txt: 追加重定向,把command输出追加到file.txt的结尾(不会覆盖原本的内容)
void redirect(const char* command, const char* file){
int fd = open(file, O_WRONLY | O_CREAT | O_APPEND, 0644);
if (fd == -1) {
perror("open fail");
exit(1);
}
if (dup2(fd, STDOUT_FILENO) == -1){
perror("dup2 failed");
exit(1);
}
close(fd);//不要忘记close
char *argv[] = {(char *) command, NULL}; //不要写死,用command变量
execvp(command, argv);
perror("exec failed");
exit(1);
}
sort < infile.txt | grep hello >> output.txt: 读取file.txt的内容并排序,从中找到包含hello的行,添加到output.txt的结尾
- sort:
- 读取从STDIN 变成infile.txt
- 输出从STDOUT变成pipeline写端
- grep:
- 读取从STDIN变成pipeline读端
- 输出从STDOUT变成output.txt
//grepsort arg1 arg2 arg3 = sort < arg2 | grep arg1 >> arg3
//参数应该是, argv[0] = grepsort, argv[1] = arg1
void grepsort(char * argv[]){
int fd[2]; //一个管道即可,因为只有一个管道符号 |
if (pipe(fd) == -1){
perror("pipe failed");
exit(1);
}
int pid1 = fork();
//不要忘记检查fork
if (pid1 == -1) {
perror("fork1 failed");
exit(1);
}
//fork成功,还有父子进程之分
if (pid1 == 0){
close(fd[0]);
//子进程1: sort < infile.txt | -> 写入管道
int fd_in = open(argv[2], O_RDONLY);
if (fd_in == -1){
perror("open infile failed");
exit(1);
}
//这里的输入来自infile.txt, 输出是去管道,所以有两个dup2
if(dup2(fd_in, STDIN_FILENO) == -1){
// dup2(A, STDIN_FILENO): 标准输入来自A
perror("dup2 from infile.txt failed");
exit(1);
}
if(dup2(fd[1], STDOUT_FILENO) == -1){
//标准输出来自fd[1]
perror("dup2 to pipe failed");
exit(1);
}
close(fd_in);
close(fd[1]);
char * argv1[] = {"sort", NULL};
execvp("sort", argv1);
perror("sort exec failed");
exit(1);
}
int pid2 = fork();
if (pid2 == -1) {
perror("fork2 failed");
exit(1);
}
if (pid2 == 0) {
close(fd[1]);
int fd_out = open(argv[3], O_WRONLY | O_CREAT | O_APPEND, 0644);
if (fd_out == -1) {
perror("open output failed");
exit(1);
}
if(dup2(fd_out, STDOUT_FILENO) == -1){
perror("dup2 for writing failed");
exit(1);
}
if (dup2(fd[0], STDIN_FILENO) == -1){
perror("dup2 for pipe reading failed");
exit(1);
}
close(fd_out);
close(fd[0]);
char * argv2[] = {"grep", argv[1], NULL};
execvp("grep", argv2);
perror("grep exec failed");
exit(1);
}
close(fd[0]);
close(fd[1]);
wait(pid1, NULL, 0);
wait(pid2, NULL, 0);
}
print entries就是打印每一行的意思 entries = lines
sort < infile.txt | grep hello >> output.txt这本身就是一个bash命令,所以如果要用program来实现它,一定不是用bash,而是用C.
Question 2:
int runCommand(char * command, char * outputBuffer, int maxBufferSize){
int fd[2];
if (pipe(fd) == -1) {
perror("pipe failed");
exit(1);
}
int pid = fork();
if (pid == -1) {
perror("fork failed");
exit(1);
}
if (pid == 0){
//child process:
close(fd[0]);
if(dup2(fd[1], STDOUT_FILENO) == -1){
perror("dup2 failed");
exit(1);
}
close(fd[1]);
char * argv[] = {(char *)command, NULL};
execvp(command, argv);
perror("command exec failed");
exit(1);
}
close(fd[1]); //不写入管道任何东西,写入outputBuffer不等于写入管道
int bytesRead = 0;
int totalRead = 0;
while ((bytesRead = read(fd[0], outputBuffer + totalRead, maxBufferSize - 1 - totalRead)) > 0) {
totalRead += bytesRead;
if (totalRead >= maxBufferSize - 1){
break;
}
}
close(fd[0]);
//添加结束符
outputBuffer[totalRead] = '\0';
int status;
wait(pid, &status, 0);
return (bytesRead < 0 || !WIFEXITED(status)) ? -1 : 0;
}
Question3:
make them synchronized: 让他们变得线程安全
insert() -> 生产者; removeFirst() -> 消费者
semaphores: 信号量(资源计数器)
struct List * head = NULL;
pthread_mutex_t mutexLock;
sema_t fullSem;
sema_t emptySem;
main(){
pthread_mutext_init(&mutexLock, NULL);
sema_init(&fullSem, 20, USYNC_THREAD, NULL);
sema_init(&emptySem, 0, USYNC_THREAD, NULL);
}
Question 7:
What are four parameters that computer needs to get connected to the internet and what are they used for?
- IP Address: a unique identifier assigned to a device on a network
- Subnet Mask: determines which IP addresses are considered part of the same local network
- Default gateway(router): the IP address of a router that connects the local network to external networks
- DNS Server: translates human-readable domain name(like
www.google.com) into IP address.
Question 8:
How does the computer know when it can deliver a packet directly or when it have to pass a packet to a router?
Computer uses the Subnet Mask and IP Address to make this decision.
- Check the destination IP address
- Apply the subnet mask to both the destination IP and its own IP
- Compare the result:
- if they are in the same subnet, the destination is on the local network -> send directly
- not the same subnet, the destination is outside the local network -> send to the default gateway (router)
Question 9:
What is ARP(地址解析协议) and how does it work?
- ARP (Address Resolution Protocal) is a protocal used to map an IP address to a MAC address in a local network (LAN). It accepts an IP address of a computer in locally connected network and outputs the Ethernet address of the computer
Question 10:
What does DNS mean and what it is used for?
- Domain Name Server. Convert human-readable domain name into IP address.
Question 11:
What does DHCP mean and how does it work?
- Dynamic Host Configuration Protocal
Question 12:
What does UDP mean?
User Datagram Protocol. It is an unreliable protocol and message-oriented. It does not require a connection to send messages. Each message is encapsulated in an IP datagram and its size is limited to the network’s MTU. It is mainly used for broadcasting and for real-time data applications.
Question 13:
What does TCP mean? What are the 6 features of TCP?
Transmission Control Protocol
- Connection-Oriented: A Connection must be established before data transfer (via a 3-way handshake)
- Reliable Data Transfer: Ensures all packets are reveived using acknowledgments and retransmissions.
- Ordered Data Delivery: Packets arrive at the receiver in the same order as sent.
- Error Checking: Uses checksums to detec corrupted segments.
- Flow Control: uses a sliding window to prevent sender from overwhelming receiver
- Congestion Control - Adjust sending rate based on network congestion to avoid overload.
Enjoy Reading This Article?
Here are some more articles you might like to read next: