Jos pipe實現解析
在linux中,管道用到的非常頻繁,比如說計算某個文件中每個單詞出現的次數,sort A.file | uniq -c (A.file 中每一行都是一個單詞)。即將 sort的輸出導入到uniq進程的輸入中。那麼問題來了,sort的輸出流是如何重定向到uniq的輸入的呢?
在JOS課程中,管道是如何實現的呢?在pipe()
中做了什麼?
int pipe(int pfd[2])
{
int r;
struct Fd *fd0, *fd1;
void *va;
// allocate the file descriptor table entries
if ((r = fd_alloc(&fd0)) < 0
|| (r = sys_page_alloc(0, fd0, PTE_P|PTE_W|PTE_U|PTE_SHARE)) < 0)
goto err;
if ((r = fd_alloc(&fd1)) < 0
|| (r = sys_page_alloc(0, fd1, PTE_P|PTE_W|PTE_U|PTE_SHARE)) < 0)
goto err1;
// allocate the pipe structure as first data page in both
va = fd2data(fd0);
if ((r = sys_page_alloc(0, va, PTE_P|PTE_W|PTE_U|PTE_SHARE)) < 0)
goto err2;
if ((r = sys_page_map(0, va, 0, fd2data(fd1), PTE_P|PTE_W|PTE_U|PTE_SHARE)) < 0) {
cprintf("pipe map file data failed.\n");
goto err3;
}
// set up fd structures
fd0->fd_dev_id = devpipe.dev_id;
fd0->fd_omode = O_RDONLY;
fd1->fd_dev_id = devpipe.dev_id;
fd1->fd_omode = O_WRONLY;
if (debug)
cprintf("[%08x] pipecreate %08x\n", thisenv->env_id, uvpt[PGNUM(va)]);
pfd[0] = fd2num(fd0);
pfd[1] = fd2num(fd1);
return 0;
err3:
sys_page_unmap(0, va);
err2:
sys_page_unmap(0, fd1);
err1:
sys_page_unmap(0, fd0);
err:
return r;
}
首先,先申請文件描述符fd0, fd1,同時將兩者的file data部分map到相同的page中,這樣,對該page的讀寫在兩個進程和子進程中是一致的,類似於共享內存。
*為什麼在子進程也是一致的呢? *
因為在申請page時使用了 PTE_SHARE 屬性,在fork/spawn時,會直接將該page映射到子進程的addr中,因此,在子進程/父進程中,對addr的操作,兩個進程都可以感知。
另外一個重要的函數是dup(oldfd, newfd)
,它會將oldfd所對應的FD部分和FDData部分映射到newfd的相應部分。
// Make file descriptor 'newfdnum' a duplicate of file descriptor 'oldfdnum'.
// For instance, writing onto either file descriptor will affect the
// file and the file offset of the other.
// Closes any previously open file descriptor at 'newfdnum'.
// This is implemented using virtual memory tricks (of course!).
int
dup(int oldfdnum, int newfdnum)
{
int r;
char *ova, *nva;
pte_t pte;
struct Fd *oldfd, *newfd;
if ((r = fd_lookup(oldfdnum, &oldfd)) < 0)
return r;
close(newfdnum);
newfd = INDEX2FD(newfdnum);
ova = fd2data(oldfd);
nva = fd2data(newfd);
if ((uvpd[PDX(ova)] & PTE_P) && (uvpt[PGNUM(ova)] & PTE_P)){
if ((r = sys_page_map(0, ova, 0, nva, uvpt[PGNUM(ova)] & PTE_SYSCALL)) < 0)
goto err;
} else {
cprintf("dup file data not exist. oldfd %u to newfd %u\n", oldfdnum, newfdnum);
}
if ((r = sys_page_map(0, oldfd, 0, newfd, uvpt[PGNUM(oldfd)] & PTE_SYSCALL)) < 0)
goto err;
return newfdnum;
err:
sys_page_unmap(0, newfd);
sys_page_unmap(0, nva);
return r;
最後,來看下最終的代碼
1. 父進程申請了pipe,fd[0], fd[1] 代表標準輸入和輸出, fd[2]和fd[3]是pipe出來的文件描述符
2. 將相關頁麵進行dup和close操作後,各進程的虛擬地址與page的關係如下
3. 這樣,對fd_data頁麵的讀寫就可以從父進程到子進程共享了
if ((r = pipe(p)) < 0) {
cprintf("pipe: %e", r);
exit();
}
if (debug)
cprintf("PIPE: %d %d\n", p[0], p[1]);
if ((r = fork()) < 0) {
cprintf("fork: %e", r);
exit();
}
cprintf("[%08x] sh pipe fork res=[%08x]\n", sys_getenvid(), r);
if (r == 0) {
if (p[0] != 0) {
dup(p[0], 0);
close(p[0]);
}
close(p[1]);
goto again;
} else {
pipe_child = r;
if (p[1] != 1) {
dup(p[1], 1);
close(p[1]);
}
close(p[0]);
goto runit;
}
panic("| not implemented");
break;
綜上,pipe其實是將兩個文件描述符對應的data空間map到 標準輸入和標準輸出中,同時共享到子進程中。
最後更新:2017-05-01 23:00:53