\documentclass[12pt, a4paper]{article}
\usepackage[UTF8]{ctex}
\usepackage{geometry}
\usepackage{amsmath}
\usepackage{amsthm}
\usepackage{amssymb}
\usepackage{enumitem}
\usepackage{float}
\usepackage{graphicx}
\usepackage{extarrows}
\usepackage{booktabs}
\usepackage{diagbox}
\usepackage{siunitx}
\usepackage{subcaption}
\usepackage{fontspec}
\usepackage{listings}
\usepackage{xcolor}
\usepackage{siunitx}
\usepackage[ruled,vlined]{algorithm2e}

\sisetup{
    group-separator={,},
}

\definecolor{vscodeBlue}{RGB}{0,0,255}     % 关键字/语句
\definecolor{vscodeRed}{RGB}{163,21,21}    % 字符串
\definecolor{vscodeGreen}{RGB}{0,128,0}    % 注释

\newfontfamily\codefont{Fira Code}
\lstset{
    basicstyle          =   \footnotesize\codefont,
    % ---
    tabsize             =   4,
    showstringspaces    =   false,
    numbers             =   left,
    numberstyle         =   \codefont,
    % ---
    breaklines          =   true,
    captionpos          =   t,      
    % ---
    frame               =   l,
    flexiblecolumns,
    commentstyle=\color{vscodeGreen},     % 注释样式
    keywordstyle=\color{vscodeBlue},      % 关键字样式
    stringstyle=\color{vscodeRed},        % 字符串样式
    rangebeginprefix    =   //\ BEGIN_LST_,
    rangeendprefix      =   //\ END_LST_
}

\renewcommand{\qedsymbol}{}

\lstMakeShortInline|

\geometry{a4paper,scale=0.8}

\allowdisplaybreaks[4] %允许公式跨页

\setenumerate[1]{label=\arabic{*}. }
\setenumerate[2]{label=(\arabic{*})}

\title{\Huge{{\bf 操作系统大作业实验6报告}}}
\author{高艺轩 2022010639}
\date{}

\begin{document}
\maketitle

\section{实验题目}
选择的题目为“管道驱动程序开发”。

\section{问题描述}
管道是现代操作系统中重要的进程间通信（IPC）机制之一，Linux和Windows操作系统都支持管道。

管道在本质上就是在进程之间以字节流方式传送信息的通信通道，每个管道具有两个端，一端用于输入，一端用于输出，如下图所示。在相互通信的两个进程中，一个进程将信息送入管道的输入端，另一个进程就可以从管道的输出端读取该信息。显然，管道独立于用户进程，所以只能在内核态下实现。

在本实验中，请通过编写设备驱动程序mypipe实现自己的管道，并通过该管道实现进程间通信。

你需要编写一个设备驱动程序mypipe实现管道，该驱动程序创建两个设备实例，一个针对管道的输入端，另一个针对管道的输出端。另外，你还需要编写两个测试程序，一个程序向管道的输入端写入数据，另一个程序从管道的输出端读出数据，从而实现两个进程间通过你自己实现的管道进行数据通信。

\section{思路与程序结构}
实现Linux管道驱动的核心是编写一个character device驱动，我们将它通过模块的形式挂载到内核中。实验内核版本为|6.14.3-arch1-1|。作为一个管道程序，我们需要向系统注册两个设备，一个作为输出，一个作为输入；同时，需要开辟一片缓冲区，用于暂时接收从管道入口发来的信息，在这里我选择使用了环形缓存（ring buffer）。

首先，定义一些变量用于存储状态：
\lstinputlisting[language=c, linerange=DEF]{../wendy_mod/wendy.c}

作为一个设备驱动模块，我们要做的最主要的事情就是提供|module_init|和|module_exit|两个回调函数。我们的驱动程序将在这两个|module_init|中初始化自身，向操作系统注册我们创建的设备以及注册另一个重要的回调函数结构体|mypipe_fops|，同时在|module_exit|中做相反的操作。模块初始化与销毁的代码如下：
\lstinputlisting[language=c, linerange=INIT]{../wendy_mod/wendy.c}

在|module_init|中我们注册了回调函数结构体|cdev_init(&pdata.cdev, &mypipe_fops);|这其中的|mypipe_fops|定义为：
\lstinputlisting[language=c, linerange=FOP]{../wendy_mod/wendy.c}
|open|，|release|，|read|，|write|可以粗略地看作对应了用户态下的|fopen|，|fclose|，|fread|，|fwrite|四个操作。我们需要实现这四个回调函数才能正确地完成目标。对于|open|与|release|，由于我们实现的是一个虚拟设备，无需与物理硬件交互，所以不需要进行特别的操作；为了保证只有一个程序可以向管道写入，同时保证在写入程序关闭管道时向读取端提示已经写入端已经关闭，需要处理|pdata.is_active|量来标识当前是否有程序正在占用管道的写入端。
\lstinputlisting[language=c, linerange=OPEN_RELEASE]{../wendy_mod/wendy.c}
对于|read|与|write|函数，首先需要判断|minor|号是否匹配：我们不允许在入口调用|read|，也不允许在出口调用|write|。之后的操作是类似的，以|read|为例。首先尝试获取缓冲区的锁，如果当前的数据长度为0，那么要么是对面端已经关闭了管道而且我们已经读取了所有的数据，要么是管道对面还有等着写入的数据暂时没有写入。对于前者，我们可以直接返回|0|代表已经读取完毕，对于后者，我们可以将自己放入等待区，等待|pdata.data_len > 0|的事件发生时重启此系统调用。如果当前的数据长度不为0，那么我们就可以从环形缓存中依次读取数据放入用户提供的缓冲区中，并且提醒可能的正在等待的写入端我们已经腾出了空间。|write|的操作是完全类似的。
\lstinputlisting[language=c, linerange=READ_WRITE]{../wendy_mod/wendy.c}

\section{程序运行情况}
首先，编写简单的写入（Listings \ref{code:pwrite}）与读出程序（Listings \ref{code:pread}）进行测试：
\begin{figure}[H]
    \centering
    \includegraphics[width=\linewidth]{../exps/read_write.png}
    \caption{简单读入读出测试}
\end{figure}

之后，尝试利用管道传输超出缓冲区容量的数据（Listings \ref{code:tester}），读取大约\SI{620}{KB}：
\begin{figure}[H]
    \centering
    \includegraphics[width=\linewidth]{../exps/longread_start.png}
    \caption{程序输出的开头}
\end{figure}

\begin{figure}[H]
    \centering
    \includegraphics[width=\linewidth]{../exps/longread_end.png}
    \caption{程序输出的结尾}
\end{figure}

\section{实验总结}
在本次实验中，我第一次实现了一个简单的Linux驱动。在阅读了一些教程后，对代码结构有了基本的认识，实际写起来也并不觉得过于困难，这次时间很大地提升了我的编程水平。


\appendix
\section{完整源代码}
\lstinputlisting[language=c, title=管道驱动程序代码, caption={内核驱动模块代码}]{../wendy_mod/wendy.c}
\lstinputlisting[language=c, title=pipe\_write.c, caption={简单写入程序代码}, label={code:pwrite}]{../pipe_write.c}
\lstinputlisting[language=c, title=pipe\_read.c, caption={简单读出程序代码}, label={code:pread}]{../pipe_read.c}
\lstinputlisting[language=c++, title=pipe\_tester.cpp, caption={测试程序代码}, label={code:tester}]{../pipe_tester.cpp}

\end{document}