为babyos2实现pipe进程间通信方式。
一个pipe表现为两个文件描述符fd[2],对应两个打开的文件,一个只读,一个只写。当通过fork创建子进程的时候,会拷贝父进程打开的文件,所以也持有这两个打开的文件。
使用的时候,一个关闭读端,一个关闭写端。即父子进程各自只剩下一个打开的文件,例如父进程关闭写端,子进程关闭读端:
parent:
fd[0] <-> file {
TYPE_PIPE;
pipe;
readable;
};
child:
fd[1] <-> file {
TYPE_PIPE;
pipe;
writable;
}
父子进程剩下的文件中都有一个pipe的指针,这两个指针指向内核中同一个pipe结构。
对TYPE_PIPE类型的文件,读写时会调用pipe->read()或pipe->write()。
pipe中有一个缓冲区,及读写指针,锁等。write会往缓冲区写数据,read从里面读数据,由于父子进程的pipe指针对应同一个pipe结构,这样就实现了父子进程一个读一个写的操作。
对应缓冲区的操作是一个类似于生产者/消费者的问题。
userlib pipe:
int userlib_t::pipe(int fd[2])
{
uint32 ret = 0;
__asm__ volatile("int $0x80" : "=a" (ret) : "a" (SYS_PIPE), "b" (fd));
return ret;
}
sys_pipe:
int32 syscall_t::sys_pipe(trap_frame_t* frame)
{
int* fd = (int *) frame->ebx;
return os()->get_fs()->do_pipe(fd);
}
fs do_pipe:
int file_system_t::alloc_pipe(file_t*& file_read, file_t*& file_write)
{
pipe_t* pipe = NULL;
file_read = alloc_file();
if (file_read == NULL) {
goto failed;
}
file_write = alloc_file();
if (file_write == NULL) {
goto failed;
}
pipe = (pipe_t *) os()->get_obj_pool(PIPE_POOL)->alloc_from_pool();
if (pipe == NULL) {
goto failed;
}
pipe->init();
file_read->init(file_t::TYPE_PIPE, NULL, pipe, 0, 1, 0);
file_write->init(file_t::TYPE_PIPE, NULL, pipe, 0, 0, 1);
return 0;
failed:
if (file_read != NULL) {
close_file(file_read);
}
if (file_write != NULL) {
close_file(file_write);
}
if (pipe != NULL) {
os()->get_obj_pool(PIPE_POOL)->free_object((void *) pipe);
}
return -1;
}
int file_system_t::do_pipe(int fd[2])
{
file_t* file_read = NULL;
file_t* file_write = NULL;
int fd_read = -1, fd_write = -1;
if (alloc_pipe(file_read, file_write) < 0) {
return -1;
}
fd_read = current->alloc_fd(file_read);
if (fd_read < 0) {
goto failed;
}
fd_write = current->alloc_fd(file_write);
if (fd_write < 0) {
current->free_fd(fd_read);
goto failed;
}
fd[0] = fd_read;
fd[1] = fd_write;
return 0;
failed:
close_file(file_read);
close_file(file_write);
return -1;
}
read/write
int file_system_t::do_read(int fd, void* buffer, uint32 count)
{
file_t* file = current->get_file(fd);
if (file == NULL || file->m_readable == 0) {
return -1;
}
if (file->m_type == file_t::TYPE_PIPE) {
return file->m_pipe->read(buffer, count);
}
if (file->m_type == file_t::TYPE_INODE) {
int nbyte = 0;
if ((nbyte = read_inode(file->m_inode, (char *) buffer, file->m_offset, count)) > 0) {
file->m_offset += nbyte;
}
return nbyte;
}
return -1;
}
int file_system_t::do_write(int fd, void* buffer, uint32 count)
{
file_t* file = current->get_file(fd);
if (file == NULL || file->m_writeable == 0) {
return -1;
}
if (file->m_type == file_t::TYPE_PIPE) {
return file->m_pipe->write(buffer, count);
}
if (file->m_type == file_t::TYPE_INODE) {
int nbyte = 0;
if ((nbyte = write_inode(file->m_inode, (char *) buffer, file->m_offset, count)) > 0) {
file->m_offset += nbyte;
}
return nbyte;
}
return -1;
}
pipe:
/*
* guzhoudiaoke@126.com
* 2018-01-20
*/
#ifndef _PIPE_H_
#define _PIPE_H_
#include "types.h"
#include "sem.h"
#include "spinlock.h"
#define PIPE_BUF_SIZE 512
class pipe_t {
public:
void init();
int get_char(char& ch);
int put_char(char ch);
int32 read(void* buf, uint32 size);
int32 write(void* buf, uint32 size);
void close(bool write_end);
private:
char m_buffer[PIPE_BUF_SIZE];
uint32 m_read_index;
uint32 m_write_index;
spinlock_t m_lock;
semaphore_t m_space; // how many space can use to put
semaphore_t m_item; // how many item can get
bool m_readable;
bool m_writable;
};
#endif
/*
* guzhoudiaoke@126.com
* 2018-01-20
*/
#include "pipe.h"
#include "babyos.h"
void pipe_t::init()
{
m_read_index = 0;
m_write_index = 0;
m_lock.init();
m_space.init(PIPE_BUF_SIZE);
m_item.init(0);
m_readable = true;
m_writable = true;
}
int pipe_t::get_char(char& ch)
{
int ret = -1;
m_item.down();
m_lock.lock();
if (m_readable) {
ch = m_buffer[m_read_index];
m_read_index = (m_read_index + 1) % PIPE_BUF_SIZE;
ret = 0;
}
m_lock.unlock();
m_space.up();
return ret;
}
int pipe_t::put_char(char ch)
{
int ret = -1;
m_space.down();
m_lock.lock();
if (m_writable) {
m_buffer[m_write_index] = ch;
m_write_index = (m_write_index + 1) % PIPE_BUF_SIZE;
ret = 0;
}
m_lock.unlock();
m_item.up();
return ret;
}
int32 pipe_t::read(void* buf, uint32 size)
{
char* p = (char *) buf;
char ch;
for (uint32 i = 0; i < size; i++) {
if (get_char(ch) < 0) {
return -1;
}
*p++ = ch;
}
return size;
}
int32 pipe_t::write(void* buf, uint32 size)
{
char* p = (char *) buf;
for (uint32 i = 0; i < size; i++) {
if (put_char(*p++) < 0) {
return -1;
}
}
return size;
}
void pipe_t::close(bool write_end)
{
m_lock.lock();
if (write_end) {
m_writable = false;
m_item.up();
}
else {
m_readable = false;
m_space.up();
}
if (!m_readable && !m_writable) {
m_lock.unlock();
os()->get_obj_pool(PIPE_POOL)->free_object(this);
}
m_lock.unlock();
}
shell:
void test_pipe()
{
int fd[2];
if (userlib_t::pipe(fd) < 0) {
userlib_t::printf("failed to create pipe\n");
return;
}
userlib_t::printf("succ to create pipe: %d, %d\n", fd[0], fd[1]);
int32 pid = userlib_t::fork();
if (pid == 0) {
userlib_t::close(fd[0]);
char ch = 'a';
for (int i = 0; i < 10; i++, ch++) {
userlib_t::write(fd[1], &ch, 1);
userlib_t::printf("child write %c to pipe\n", ch);
userlib_t::sleep(1);
}
userlib_t::exit(0);
}
else {
userlib_t::close(fd[1]);
char ch = '\0';
for (int i = 0; i < 10; i++) {
userlib_t::read(fd[0], &ch, 1);
userlib_t::printf("parent read %c from pipe\n", ch);
}
userlib_t::wait(pid);
}
}
char cmd_line[MAX_CMD_LEN] = {0};
int main()
{
userlib_t::printf("This is printed by shell.\n");
while (true) {
puts("liuruyi $ ");
gets(cmd_line, MAX_CMD_LEN);
if (userlib_t::strncmp(cmd_line, "cd ", 3) == 0) {
if (userlib_t::chdir(cmd_line + 3) < 0) {
userlib_t::printf("can't cd %s\n", cmd_line+3);
}
continue;
}
/* used for test */
if (userlib_t::strncmp(cmd_line, "test ", 5) == 0) {
test_fork_exec_wait_exit(cmd_line + 5);
continue;
}
if (userlib_t::strncmp(cmd_line, "testpipe", 8) == 0) {
test_pipe();
continue;
}
do_cmd(cmd_line);
}
userlib_t::exit(0);
return 1;
}