提交 33b2e6b9 编写于 作者: 鸿蒙内核源码分析's avatar 鸿蒙内核源码分析

对虚拟串口,USB,UART实现注释

    百万汉字注解 + 百篇博客分析 => 挖透鸿蒙内核源码
    鸿蒙研究站 | http://weharmonyos.com (国内)
              | https://weharmony.github.io (国外)
    oschina | https://my.oschina.net/weharmony
    博客园 | https://www.cnblogs.com/weharmony/
    知乎 | https://www.zhihu.com/people/weharmonyos
    csdn | https://blog.csdn.net/kuangyufei
    51cto | https://harmonyos.51cto.com/column/34
    掘金 | https://juejin.cn/user/756888642000808
    公众号 | 鸿蒙研究站 (weharmonyos)
上级 74d38b88
......@@ -649,7 +649,7 @@ int ShellTaskInit(ShellCB *shellCB)
///< 给控制台注册一个shell客户端任务
static int ShellKernelReg(unsigned int shellHandle)
{
return ioctl(STDIN_FILENO, CONSOLE_CONTROL_REG_USERTASK, shellHandle);//从标准输入中读取数据
return ioctl(STDIN_FILENO, CONSOLE_CONTROL_REG_USERTASK, shellHandle);//Shell Entry 任务将从标准输入中读取数据
}
void *ShellEntry(void *argv)
......
......@@ -100,7 +100,7 @@ INT32 GetFilepOps(const struct file *filep, struct file **privFilep, const struc
//通过 is_private 查找控制台设备的文件(现在是 *privFile)
/* to find console device's filep(now it is *privFilep) throught i_private */
struct drv_data *drv = (struct drv_data *)filep->f_vnode->data;
*privFilep = (struct file *)drv->priv;// file
*privFilep = (struct file *)drv->priv;// file 例如 g_serialFilep
if (((*privFilep)->f_vnode == NULL) || ((*privFilep)->f_vnode->data == NULL)) {
ret = EINVAL;
goto ERROUT;
......@@ -474,8 +474,8 @@ STATIC INT32 UserFilepRead(CONSOLE_CB *consoleCB, struct file *filep, const stru
if (fops->read == NULL) {
return -EFAULT;
}
/* Non-ICANON mode */
/* Non-ICANON mode | 非规范模式 所有的输入即时有效,用户不需要另外输入行结束符,不能进行行编辑*/
if ((consoleCB->consoleTermios.c_lflag & ICANON) == 0) {
ret = fops->read(filep, buffer, bufLen);
if (ret < 0) {
......@@ -483,6 +483,13 @@ STATIC INT32 UserFilepRead(CONSOLE_CB *consoleCB, struct file *filep, const stru
}
return ret;
}
/**
在规范模式下,输入数据基于行进行处理。
在用户输入一个行结束符(回车符、EOF等)之前,系统调用read()读不到用户输入的任何字符。
除了EOF之外的行结束符(回车符等),与普通字符一样会被read()读到缓冲区中。
在规范模式中,可以进行行编辑,而且一次调用read()最多只能读取一行数据。
如果read()请求读取的数据字节少于当前行可读取的字节,则read()只读取被请求的字节数,剩下的字节下次再读。
*/
/* ICANON mode: store data to console buffer, read data and stored data into console fifo */
if (consoleCB->currentLen == 0) {//如果没有数据
while (1) {//存储数据到控制台buf中
......@@ -1143,8 +1150,7 @@ STATIC INT32 OsConsoleDevInit(CONSOLE_CB *consoleCB, const CHAR *deviceName)
goto ERROUT;
}
//更新驱动程序,其中再次对 达到操作/dev/ttyS0的目的
ret = register_driver(consoleCB->name, &g_consoleDevOps, DEFFILEMODE, filep);//注册字符设备驱动程序
ret = register_driver(consoleCB->name, &g_consoleDevOps, DEFFILEMODE, filep);//注册控制台设备驱动程序
if (ret != LOS_OK) {
goto ERROUT;
}
......@@ -1370,7 +1376,7 @@ INT32 system_console_init(const CHAR *deviceName)//deviceName: /dev/serial /dev/
return VFS_ERROR;
}
consoleCB = OsConsoleCreate((UINT32)consoleID, deviceName);//创建一个控制台控制块
consoleCB = OsConsoleCreate((UINT32)consoleID, deviceName);//创建一个控制台
if (consoleCB == NULL) {
PRINT_ERR("%s, %d\n", __FUNCTION__, __LINE__);
return VFS_ERROR;
......
......@@ -118,25 +118,34 @@ typedef struct {
VOID *shellHandle; ///< shell句柄,本质是 shell控制块 ShellCB
#endif
UINT32 sendTaskID; ///< 创建任务通过事件接收数据, 见于OsConsoleBufInit
/*--以下为 一家子 start---------*/
CirBufSendCB *cirBufSendCB; ///< 循环缓冲发送控制块
UINT8 fifo[CONSOLE_FIFO_SIZE]; ///< 控制台缓冲区大小 1K
UINT8 fifo[CONSOLE_FIFO_SIZE]; ///< termios 规范模式(ICANON mode )下使用 size:1K
UINT32 fifoOut; ///< 对fifo的标记,输出位置
UINT32 fifoIn; ///< 对fifo的标记,输入位置
UINT32 currentLen; ///< 当前fifo位置
/*---以上为 一家子 end------- https://man7.org/linux/man-pages/man3/tcflow.3.html */
struct termios consoleTermios; ///< 行规程
} CONSOLE_CB;
/**
* @brief termios 结构是在POSIX规范中定义的标准接口,它类似于系统V中的termio接口,通过设置termios类型的数据结构中的值和使用一小组函数调用,
你就可以对终端接口进行控制。可以被调整来影响终端的值按照不同的模式被分为如下几组:
1.输入模式
2.输出模式
3.控制模式
4.本地模式
5.特殊控制模式
https://blog.csdn.net/wumenglu1018/article/details/53098794
* @brief https://man7.org/linux/man-pages/man3/tcflow.3.html
termios 是在POSIX规范中定义的标准接口,表示终端设备,包括虚拟终端、串口等。串口通过termios进行配置。
struct termios
{
unsigned short c_iflag; // 输入模式标志
unsigned short c_oflag; // 输出模式标志
unsigned short c_cflag; // 控制模式标志
unsigned short c_lflag; // 本地模式标志 例如: 设置非规范模式 tios.c_lflag = ~(ICANON | ECHO | ECHOE | ISIG);
unsigned char c_line; // 线路规程
unsigned char c_cc[NCC]; // 控制特性
speed_t c_ispeed; // 输入速度
speed_t c_ospeed; // 输出速度
}
终端有三种工作模式:
规范模式(canonical mode)、
非规范模式(non-canonical mode)
原始模式(raw mode)。
https://www.jianshu.com/p/fe5812469801
https://blog.csdn.net/wumenglu1018/article/details/53098794
*/
extern INT32 system_console_init(const CHAR *deviceName);
......
......@@ -67,7 +67,7 @@ UINT32 SerialTypeGet(VOID)
}
///设置串口类型
STATIC VOID SerialTypeSet(const CHAR *deviceName)
{
{///dev/uartdev-0
if (!strncmp(deviceName, SERIAL_UARTDEV, strlen(SERIAL_UARTDEV))) {
g_serialType = SERIAL_TYPE_UART_DEV;
} else if (!strncmp(deviceName, SERIAL_TTYGS0, strlen(SERIAL_TTYGS0))) {
......@@ -126,12 +126,17 @@ STATIC ssize_t SerialRead(struct file *filep, CHAR *buffer, size_t bufLen)
const struct file_operations_vfs *fileOps = NULL;
ret = GetFilepOps(filep, &privFilep, &fileOps);//获取COM口在内核的file实例
/*以 register_driver(SERIAL, &g_serialDevOps, DEFFILEMODE, &g_serialFilep);为例
privFilep = g_serialFilep
fileOps = g_serialDevOps
*/
if (ret != ENOERR) {
ret = -EINVAL;
goto ERROUT;
}
ret = FilepRead(privFilep, fileOps, buffer, bufLen);//从COM口读buf
ret = FilepRead(privFilep, fileOps, buffer, bufLen);//从USB或者UART 读buf
if (ret < 0) {
goto ERROUT;
}
......@@ -224,7 +229,7 @@ STATIC const struct file_operations_vfs g_serialDevOps = {
#endif
NULL,
};
//虚拟串口初始化,注册驱动程序
//虚拟串口初始化,注册驱动程序 ,例如 : deviceName = "/dev/uartdev-0"
INT32 virtual_serial_init(const CHAR *deviceName)
{
INT32 ret;
......@@ -235,7 +240,7 @@ INT32 virtual_serial_init(const CHAR *deviceName)
goto ERROUT;
}
SerialTypeSet(deviceName);//例如: /dev/uartdev-0 <--> /dev/console1
SerialTypeSet(deviceName);//例如: /dev/uartdev-0 为 UART串口
VnodeHold();
ret = VnodeLookup(deviceName, &vnode, V_DUMMY);//由deviceName查询vnode节点
......@@ -245,10 +250,10 @@ INT32 virtual_serial_init(const CHAR *deviceName)
}
//接着是 vnode < -- > file 的绑定操作
(VOID)memset_s(&g_serialFilep, sizeof(struct file), 0, sizeof(struct file));
g_serialFilep.f_oflags = O_RDWR;
g_serialFilep.f_vnode = vnode;
g_serialFilep.ops = ((struct drv_data *)vnode->data)->ops;
g_serialFilep.f_oflags = O_RDWR;//可读可写
g_serialFilep.f_vnode = vnode; //
g_serialFilep.ops = ((struct drv_data *)vnode->data)->ops;//这里代表 访问 /dev/serial 意味着是访问 /dev/uartdev-0
if (g_serialFilep.ops->open != NULL) {//用于检测是否有默认的驱动程序
(VOID)g_serialFilep.ops->open(&g_serialFilep);
} else {
......@@ -256,8 +261,9 @@ INT32 virtual_serial_init(const CHAR *deviceName)
PRINTK("virtual_serial_init %s open is NULL\n", deviceName);
goto ERROUT;
}
(VOID)register_driver(SERIAL, &g_serialDevOps, DEFFILEMODE, &g_serialFilep);//这是真正的注册串口的驱动程序
(VOID)register_driver(SERIAL, &g_serialDevOps, DEFFILEMODE, &g_serialFilep);//注册虚拟串口驱动程序
//g_serialFilep作为私有数据给了 (drv_data)data->priv = g_serialFilep
//
VnodeDrop();
return ENOERR;
......
......@@ -43,11 +43,20 @@
extern "C" {
#endif /* __cplusplus */
#endif /* __cplusplus */
/*
鸿蒙将 PIN、I2C、SPI、USB、UART 等作为外设设备,统一通过设备注册完成。
实现了按名称访问的设备管理子系统,可按照统一的 API 界面访问硬件设备。
在设备驱动接口上,根据嵌入式系统的特点,对不同的设备可以挂接相应的事件。
当设备事件触发时,由驱动程序通知给上层的应用程序。
*/
#ifdef LOSCFG_FS_VFS //将设备虚拟为文件统一来操作,对鸿蒙来说一切皆为文件
#define SERIAL "/dev/serial"
#define SERIAL_TTYGS0 "/dev/ttyGS0" //串口0 /dev/console映射到此
#define SERIAL_UARTDEV "/dev/uartdev"
#define SERIAL "/dev/serial" ///< 虚拟串口设备
#define SERIAL_TTYGS0 "/dev/ttyGS0" ///< USB类型的串口
#define SERIAL_UARTDEV "/dev/uartdev" ///< Uart类型的串口
//UART(Universal Asynchronous Receiver/Transmitter)通用异步收发传输器,UART 作为异步串口通信协议的一种,
//工作原理是将传输数据的每个字符一位接一位地传输。是在应用程序开发过程中使用频率最高的数据总线。
#define SERIAL_TYPE_UART_DEV 1
#define SERIAL_TYPE_USBTTY_DEV 2
......
......@@ -53,17 +53,17 @@ extern "C" {
#define BLOCK_ENABLE 1
//远程登录接发数据结构体.管理缓冲区
typedef struct {
UINT32 rxIndex; /* index for receiving user's commands */
UINT32 rxOutIndex; /* index for taking out commands by a shell task to run */
UINT32 fifoNum; /* unused size of the cmdBuf *///剩余buf大小
UINT32 rxIndex; /* index for receiving user's commands | 接收用户命令的索引位置 */
UINT32 rxOutIndex; /* index for taking out commands by a shell task to run | 用于通过 shell 任务取出命令以运行的索引 */
UINT32 fifoNum; /* unused size of the cmdBuf | 剩余buf大小*/
UINT32 lock; //锁用于保证buf数据一致性
CHAR rxBuf[FIFO_MAX]; /* the real buffer to store user's commands */
CHAR rxBuf[FIFO_MAX]; /* the real buffer to store user's commands | 存储用户命令的真实缓冲区 */
} TELNTE_FIFO_S;
//远程登录设备结构体
typedef struct {
INT32 clientFd; ///< 客户端文件句柄
UINT32 id;
BOOL eventPend; ///< 事件是否挂起
BOOL eventPend; ///< 任务是否处于挂起
EVENT_CB_S eventTelnet; ///< 远程登录事件
wait_queue_head_t wait;
TELNTE_FIFO_S *cmdFifo; /* use a FIFO to store user's commands | 使用先进先出保存用户的命令*/
......
......@@ -50,7 +50,7 @@
#include "fs/driver.h"
/* event: there are more commands left in the FIFO to run */
#define TELNET_EVENT_MORE_CMD 0x01 ///< 还有很多命令在FIFO中等待行的事件
#define TELNET_EVENT_MORE_CMD 0x01 ///< 还有很多命令在FIFO中等待行的事件
#define TELNET_DEV_DRV_MODE 0666 ///< 文件权限 chmod = 666
STATIC TELNET_DEV_S g_telnetDev; ///< 远程登录设备
......@@ -86,6 +86,8 @@ STATIC INLINE TELNET_DEV_S *GetTelnetDevByFile(const struct file *file, BOOL isO
/*
* Description : When receive user's input commands, first copy commands to the FIFO of the telnet device.
* Then, notify a command resolver task (an individual shell task) to take out and run commands.
当接收到用户输入的命令时,首先将命令复制到telnet设备的FIFO中,然后通知一个命令解析器任务
(一个单独的shell任务)取出并运行命令。
* Return : -1 --- On failure
* Non-negative integer --- length of written commands
*/
......@@ -103,16 +105,16 @@ INT32 TelnetTx(const CHAR *buf, UINT32 bufLen)
}
/* size limited */
if (bufLen > telnetDev->cmdFifo->fifoNum) {
bufLen = telnetDev->cmdFifo->fifoNum;
if (bufLen > telnetDev->cmdFifo->fifoNum) {//一次拿不完数据的情况
bufLen = telnetDev->cmdFifo->fifoNum;//只能装满
}
if (bufLen == 0) {
if (bufLen == 0) { //参数要先判断 @note_thinking
TelnetUnlock();
return 0;
}
/* copy commands to the fifo of the telnet device */
/* copy commands to the fifo of the telnet device | 复制命令到telnet设备的fifo*/
for (i = 0; i < bufLen; i++) {
telnetDev->cmdFifo->rxBuf[telnetDev->cmdFifo->rxIndex] = *buf;
telnetDev->cmdFifo->rxIndex++;
......@@ -194,6 +196,7 @@ STATIC INT32 TelnetClose(struct file *file)
* Description : When a command resolver task trys to read the telnet device,
* this method is called, and it will take out user's commands from the FIFO to run.
* 当命令解析器任务尝试读取 telnet 设备时,调用这个方法,它会从FIFO中取出用户的命令来运行。
* 读取远程终端输入的命令,比如: # task
* Return : -1 --- On failure
* Non-negative integer --- length of commands taken out from the FIFO of the telnet device.
*/
......@@ -210,7 +213,7 @@ STATIC ssize_t TelnetRead(struct file *file, CHAR *buf, size_t bufLen)
return -1;
}
if (telnetDev->eventPend) {//挂起时,读取
if (telnetDev->eventPend) {//挂起时,说明没有数据可读,等待事件发生
TelnetUnlock();
(VOID)LOS_EventRead(g_event, TELNET_EVENT_MORE_CMD, LOS_WAITMODE_OR, LOS_WAIT_FOREVER);//等待读取 TELNET_EVENT_MORE_CMD 事件
TelnetLock();
......@@ -219,7 +222,7 @@ STATIC ssize_t TelnetRead(struct file *file, CHAR *buf, size_t bufLen)
if (bufLen > (FIFO_MAX - telnetDev->cmdFifo->fifoNum)) {
bufLen = FIFO_MAX - telnetDev->cmdFifo->fifoNum;
}
//把远程终端过来的数据接走, 一般由 Shell Entry 任务中的 read(fd,&buf)读走数据
for (i = 0; i < bufLen; i++) {
*buf++ = telnetDev->cmdFifo->rxBuf[telnetDev->cmdFifo->rxOutIndex++];
if (telnetDev->cmdFifo->rxOutIndex >= FIFO_MAX) {
......@@ -227,9 +230,9 @@ STATIC ssize_t TelnetRead(struct file *file, CHAR *buf, size_t bufLen)
}
}
telnetDev->cmdFifo->fifoNum += bufLen;
/* check if no more commands left to run */
/* check if no more commands left to run | 检查是否没有更多命令可以运行 */
if (telnetDev->cmdFifo->fifoNum == FIFO_MAX) {
(VOID)LOS_EventClear(&telnetDev->eventTelnet, ~TELNET_EVENT_MORE_CMD);
(VOID)LOS_EventClear(&telnetDev->eventTelnet, ~TELNET_EVENT_MORE_CMD);//清除读取内容事件
}
TelnetUnlock();
......@@ -260,25 +263,25 @@ STATIC ssize_t TelnetWrite(struct file *file, const CHAR *buf, const size_t bufL
return ret;
}
if (!OsPreemptable()) {
if (!OsPreemptable()) {//@note_thinking 这里为何要有这个判断?
TelnetUnlock();
return ret;
}
if (telnetDev->clientFd != 0) {
#ifdef LOSCFG_BASE_CORE_SWTMR_ENABLE
/* DO NOT call blocking API in software timer task */
/* DO NOT call blocking API in software timer task | 不要在软件定时器任务中调用阻塞 API */
if (((LosTaskCB*)OsCurrTaskGet())->taskEntry == (TSK_ENTRY_FUNC)OsSwtmrTask) {
TelnetUnlock();
return ret;
}
#endif
ret = send(telnetDev->clientFd, buf, bufLen, 0);
ret = send(telnetDev->clientFd, buf, bufLen, 0);//向 socket 发送
}
TelnetUnlock();
return ret;
}
/// 远程登录控制操作
STATIC INT32 TelnetIoctl(struct file *file, const INT32 cmd, unsigned long arg)
{
TELNET_DEV_S *telnetDev = NULL;
......@@ -332,7 +335,7 @@ STATIC INT32 TelnetPoll(struct file *file, poll_table *table)
TelnetUnlock();
return 0;
}
//远程登录操作命令,一般供 Shell 使用
STATIC const struct file_operations_vfs g_telnetOps = {
TelnetOpen,
TelnetClose,
......@@ -357,7 +360,8 @@ INT32 TelnetedUnregister(VOID)
return 0;
}
/* Once the telnet server started, setup the telnet device file. */
/* Once the telnet server started, setup the telnet device file.
| telnet 服务器启动后,设置 telnet 设备文件*/
INT32 TelnetedRegister(VOID)
{
INT32 ret;
......@@ -365,7 +369,7 @@ INT32 TelnetedRegister(VOID)
g_telnetDev.id = 0;
g_telnetDev.cmdFifo = NULL;
g_telnetDev.eventPend = TRUE;
//注册 telnet 驱动, g_telnetDev为私有数据
ret = register_driver(TELNET, &g_telnetOps, TELNET_DEV_DRV_MODE, &g_telnetDev);
if (ret != 0) {
PRINT_ERR("Telnet register driver error.\n");
......@@ -373,7 +377,8 @@ INT32 TelnetedRegister(VOID)
return ret;
}
/* When a telnet client connection established, update the output console for tasks. */
/* When a telnet client connection established, update the output console for tasks.
| 建立 telnet 客户端连接后,更新任务的输出控制台 */
INT32 TelnetDevInit(INT32 clientFd)
{
INT32 ret;
......@@ -382,12 +387,12 @@ INT32 TelnetDevInit(INT32 clientFd)
PRINT_ERR("Invalid telnet clientFd.\n");
return -1;
}
ret = system_console_init(TELNET);//创建虚拟设备
ret = system_console_init(TELNET);//创建一个带远程登录功能的控制台
if (ret != 0) {
PRINT_ERR("Telnet console init error.\n");
return ret;
}
ret = ioctl(STDIN_FILENO, CFG_TELNET_SET_FD, clientFd);
ret = ioctl(STDIN_FILENO, CFG_TELNET_SET_FD, clientFd);//绑定FD,相当于shell和控制台绑定
if (ret != 0) {
PRINT_ERR("Telnet device ioctl error.\n");
(VOID)system_console_deinit(TELNET);
......
......@@ -137,15 +137,15 @@
#define TELNET_ACCEPT_INTERVAL 200
/* client settings | 客户端设置*/
#define TELNET_CLIENT_POLL_TIMEOUT 2000 //超时时间设置
#define TELNET_CLIENT_POLL_TIMEOUT 2000 //一次客户端链接的 超时时间,
#define TELNET_CLIENT_READ_BUF_SIZE 256 //读buf大小
#define TELNET_CLIENT_READ_FILTER_BUF_SIZE (8 * 1024) ///< buf大小
/* limitation: only support 1 telnet client connection */
STATIC volatile INT32 g_telnetClientFd = -1; /* client fd */
STATIC volatile INT32 g_telnetClientFd = -1; /* client fd | 客户端文件句柄,仅支持一个客户端链接 */
/* limitation: only support 1 telnet server */
STATIC volatile INT32 g_telnetListenFd = -1; /* listen fd of telnetd */
STATIC volatile INT32 g_telnetListenFd = -1; /* listen fd of telnetd | 服务端文件句柄,仅支持一个服务端*/
/* each bit for a client connection, although only support 1 connection for now */
STATIC volatile UINT32 g_telnetMask = 0; //记录有任务打开了远程登录
......@@ -312,8 +312,15 @@ STATIC INT32 TelnetdInit(UINT16 port)
INT32 listenFd;
INT32 reuseAddr = 1;
struct sockaddr_in inTelnetAddr;
listenFd = socket(AF_INET, SOCK_STREAM, 0);
/**
ai_family参数指定调用者期待返回的套接口地址结构的类型。它的值包括三种:AF_INET,AF_INET6和AF_UNSPEC
AF_INET: 不能返回任何IPV6相关的地址信息
AF_INET6: 不能返回任何IPV4地址信息
AF_UNSPEC: 返回的是适用于指定主机名和服务名且适合任何协议族的地址。如果某个主机既有AAAA记录(IPV6)地址,
同时又有A记录(IPV4)地址,那么AAAA记录将作为sockaddr_in6结构返回,而A记录则作为sockaddr_in结构返回
*/
//SOCK_STREAM 表示TCP协议
listenFd = socket(AF_INET, SOCK_STREAM, 0);//打开一个socker fd
if (listenFd == -1) {
PRINT_ERR("TelnetdInit : socket error.\n");
goto ERR_OUT;
......@@ -391,6 +398,14 @@ STATIC INT32 TelnetClientPrepare(INT32 clientFd)
return 0;
}
/*!
* @brief TelnetClientLoop 处理远程客户端的请求任务的入口函数
*
* @param arg
* @return
*
* @see
*/
STATIC VOID *TelnetClientLoop(VOID *arg)
{
struct pollfd pollFd;
......@@ -412,28 +427,64 @@ STATIC VOID *TelnetClientLoop(VOID *arg)
while (1) {
pollFd.fd = clientFd;
pollFd.events = POLLIN | POLLRDHUP;
pollFd.events = POLLIN | POLLRDHUP;//监听读数据和挂起事件
pollFd.revents = 0;
ret = poll(&pollFd, 1, TELNET_CLIENT_POLL_TIMEOUT);
if (ret < 0) {
/*
POLLIN 普通或优先级带数据可读
POLLRDNORM 普通数据可读
POLLRDBAND 优先级带数据可读
POLLPRI 高优先级数据可读
POLLOUT 普通数据可写
POLLWRNORM 普通数据可写
POLLWRBAND 优先级带数据可写
POLLERR 发生错误
POLLHUP 发生挂起
POLLNVAL 描述字不是一个打开的文件
poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,
如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,
直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。
  这个过程经历了多次无谓的遍历。
  poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。
  poll与select的不同,通过一个pollfd数组向内核传递需要关注的事件,故没有描述符个数的限制,
pollfd中的events字段和revents分别用于标示关注的事件和发生的事件,故pollfd数组只需要被初始化一次
  poll的实现机制与select类似,其对应内核中的sys_poll,只不过poll向内核传递pollfd数组,
然后对pollfd中的每个描述符进行poll,相比处理fdset来说,poll效率更高。poll返回后,
需要对pollfd中的每个元素检查其revents值,来得指事件是否发生。
优点
1)poll() 不要求开发者计算最大文件描述符加一的大小。
2)poll() 在应付大数目的文件描述符的时候速度更快,相比于select。
3)它没有最大连接数的限制,原因是它是基于链表来存储的。
缺点
1)大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。
2)与select一样,poll返回后,需要轮询pollfd来获取就绪的描述符
*/
ret = poll(&pollFd, 1, TELNET_CLIENT_POLL_TIMEOUT);//等2秒钟返回
if (ret < 0) {//失败时,poll()返回-1
break;
/* ret < 0 各值
  EBADF   一个或多个结构体中指定的文件描述符无效。
  EFAULTfds   指针指向的地址超出进程的地址空间。
  EINTR     请求的事件之前产生一个信号,调用可以重新发起。
  EINVALnfds  参数超出PLIMIT_NOFILE值。
  ENOMEM   可用内存不足,无法完成请求
*/
}
if (ret == 0) {
if (ret == 0) {//如果在超时前没有任何事件发生,poll()返回0
continue;
}
/* connection reset, maybe keepalive failed or reset by peer */
/* connection reset, maybe keepalive failed or reset by peer | 连接重置,可能keepalive失败或被peer重置*/
if ((UINT16)pollFd.revents & (POLLERR | POLLHUP | POLLRDHUP)) {
break;
}
if ((UINT16)pollFd.revents & POLLIN) {
nRead = read(clientFd, buf, sizeof(buf));
if ((UINT16)pollFd.revents & POLLIN) {//数据事件
nRead = read(clientFd, buf, sizeof(buf));//读外面过来的数据
if (nRead <= 0) {
/* telnet client shutdown */
break;
}
cmdBuf = ReadFilter(buf, (UINT32)nRead, &len);
cmdBuf = ReadFilter(buf, (UINT32)nRead, &len);//对数据过滤
if (len > 0) {
(VOID)TelnetTx((CHAR *)cmdBuf, len);
}
......@@ -450,16 +501,16 @@ STATIC VOID *TelnetClientLoop(VOID *arg)
STATIC VOID TelnetClientTaskAttr(pthread_attr_t *threadAttr)
{
(VOID)pthread_attr_init(threadAttr);
threadAttr->inheritsched = PTHREAD_EXPLICIT_SCHED;
threadAttr->schedparam.sched_priority = TELNET_TASK_PRIORITY;
threadAttr->detachstate = PTHREAD_CREATE_DETACHED;
(VOID)pthread_attr_setstacksize(threadAttr, TELNET_TASK_STACK_SIZE);
(VOID)pthread_attr_init(threadAttr);//初始化线程属性
threadAttr->inheritsched = PTHREAD_EXPLICIT_SCHED;//
threadAttr->schedparam.sched_priority = TELNET_TASK_PRIORITY;//线程优先级
threadAttr->detachstate = PTHREAD_CREATE_DETACHED;//任务分离模式
(VOID)pthread_attr_setstacksize(threadAttr, TELNET_TASK_STACK_SIZE);//设置任务内核栈大小
}
/*
* Description : Handle the client connection request.
* Create a client connection if permitted.
* Description : Handle the client connection request. | 处理客户端连接请求
* Create a client connection if permitted. | 如果允许,创建客户端连接
* Return : 0 --- please continue the server accept loop
* : -1 --- please stop the server accept loop.
*/
......@@ -490,7 +541,7 @@ STATIC INT32 TelnetdAcceptClient(INT32 clientFd, const struct sockaddr_in *inTel
}
g_telnetClientFd = clientFd;
//创建一个线程处理客户端的请求
if (pthread_create(&tmp, &useAttr, TelnetClientLoop, (VOID *)(UINTPTR)clientFd) != 0) {
PRINT_ERR("Failed to create client handle task\n");
g_telnetClientFd = -1;
......@@ -509,6 +560,7 @@ ERROUT:
/*
* Waiting for client's connection. Only allow 1 connection, and others will be discarded.
* | 等待客户端的连接。 只允许1个连接,其他将被丢弃
*/
STATIC VOID TelnetdAcceptLoop(INT32 listenFd)
{
......@@ -523,8 +575,8 @@ STATIC VOID TelnetdAcceptLoop(INT32 listenFd)
TelnetUnlock();
(VOID)memset_s(&inTelnetAddr, sizeof(inTelnetAddr), 0, sizeof(inTelnetAddr));
clientFd = accept(listenFd, (struct sockaddr *)&inTelnetAddr, (socklen_t *)&len);
if (TelnetdAcceptClient(clientFd, &inTelnetAddr) == 0) {
clientFd = accept(listenFd, (struct sockaddr *)&inTelnetAddr, (socklen_t *)&len);//接收数据
if (TelnetdAcceptClient(clientFd, &inTelnetAddr) == 0) {//
/*
* Sleep sometime before next loop: mostly we already have one connection here,
* and the next connection will be declined. So don't waste our cpu.
......@@ -539,10 +591,14 @@ STATIC VOID TelnetdAcceptLoop(INT32 listenFd)
}
/*!
* @brief TelnetdMain
* 输入 telnet on
* @brief TelnetdMain
@verbatim
telnet启动要确保网络驱动及网络协议栈已经初始化完成,且板子的网卡是link up状态。
暂时无法支持多个客户端(telnet + IP)同时连接开发板。
输入 telnet on
OHOS # telnet on
OHOS # start telnet server successfully, waiting for connection.
@endverbatim
* @return
*
* @see
......@@ -552,14 +608,14 @@ STATIC INT32 TelnetdMain(VOID)
INT32 sock;
INT32 ret;
//1.初始化
sock = TelnetdInit(TELNETD_PORT);
sock = TelnetdInit(TELNETD_PORT);//创建 socket ,socket的本质就是打开了一个虚拟文件
if (sock == -1) {
PRINT_ERR("telnet init error.\n");
return -1;
}
//2.注册
TelnetLock();
ret = TelnetedRegister();
ret = TelnetedRegister();//注册驱动程序,ops
TelnetUnlock();
if (ret != 0) {
......@@ -577,7 +633,7 @@ STATIC INT32 TelnetdMain(VOID)
* Try to create telnetd task.
*/
/*!
* @brief TelnetdTaskInit 创建 telnet 任务
* @brief TelnetdTaskInit 创建 telnet 服务端任务
* \n 1. telnet启动要确保网络驱动及网络协议栈已经初始化完成,且板子的网卡是link up状态。
* \n 2. 暂时无法支持多个客户端(telnet + IP)同时连接开发板。
\n 须知: telnet属于调测功能,默认配置为关闭,正式产品中禁止使用该功能。
......@@ -601,7 +657,7 @@ STATIC VOID TelnetdTaskInit(VOID)
return;
}
ret = LOS_TaskCreate((UINT32 *)&g_telnetTaskId, &initParam);//创建远程登录任务并发起调度
ret = LOS_TaskCreate((UINT32 *)&g_telnetTaskId, &initParam);//创建远程登录服务端任务并发起调度
if (ret != LOS_OK) {
PRINT_ERR("failed to create telnet server task!\n");
}
......@@ -640,12 +696,12 @@ INT32 TelnetCmd(UINT32 argc, const CHAR **argv)
if (strcmp(argv[0], "on") == 0) {
/* telnet on: try to start telnet server task */
TelnetdTaskInit();
TelnetdTaskInit(); //启动远程登录 服务端任务
return 0;
}
if (strcmp(argv[0], "off") == 0) {
/* telnet off: try to stop clients, then stop server task */
TelnetdTaskDeinit();
TelnetdTaskDeinit();//关闭所有的客户端,并关闭服务端任务
return 0;
}
......
......@@ -453,9 +453,9 @@ LITE_OS_SEC_TEXT_MINOR UINT32 ShellEntryInit(ShellCB *shellCB)
CHAR *name = NULL;
TSK_INIT_PARAM_S initParam = {0};
if (shellCB->consoleID == CONSOLE_SERIAL) {
if (shellCB->consoleID == CONSOLE_SERIAL) {//带串口功能的控制台
name = SERIAL_ENTRY_TASK_NAME;
} else if (shellCB->consoleID == CONSOLE_TELNET) {
} else if (shellCB->consoleID == CONSOLE_TELNET) {//带远程登录功能的控制台
name = TELNET_ENTRY_TASK_NAME;
} else {
return LOS_NOK;
......
git add -A
git commit -m ' 同步官方源码,增加 LMS模块
git commit -m ' 对虚拟串口,USB,UART实现注释
百万汉字注解 + 百篇博客分析 => 挖透鸿蒙内核源码
鸿蒙研究站 | http://weharmonyos.com (国内)
| https://weharmony.github.io (国外)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册