[Web Server](一)Tiny Web Server分析

卫衣 / 双肩包 / T 恤/羽绒服等任选,下单送 CSDN 年卡 程序员周边任选一款实物,直接赠送 CSDN 会员年卡+ Coding Plan,写代码学习装备一起拿下 立即抢购

写在前面:

计划写一个Web 服务器,在小组的群博上没有找到相关的文章,自己打算从开始记录下这个过程,一是整理清楚我的构建过程,二是也能让后面的同学做一下参考。

CSAPP上网络编程那一章最后实现了一个小但是功能较齐全的Web 服务器,叫做TINY。因为只是知道HTTP协议的一些概念,还不太清楚一个Web服务器的工作流程和代码组织结构,而书上给出了 Tiny Server 的完整实现,代码非常短,只有几百行,所以自己模仿着手撸了一遍,并试着分析了代码,运行了一下,给自己一个直观的认识。源代码放在 这里,加注释的代码放在这里。接下来分析下这个Tiny Web服务器。

PS:WEB基础就不写了,自己了解下基本的概念,那么看起代码来就足够了。

CSAPP上面的例子用到的一些通用的函数都放在csapp.h头文件中,并在csapp.c中给出实现。我们看到的大写首字母开头的函数,是在原功能函数上面加上了错误处理,比如

pid_t Fork(void) 
{  
     pid_t pid;

     if ((pid = fork()) < 0)
         unix_error("Fork error");
     return pid;
}  

(一) main 函数

监听命令行中传来的端口上的连接请求,通过 Open_listenfd 函数打开一个监听套接字,执行无限循环,不断接受连接请求,执行HTTP事务,执行完毕后关掉连接。

Tiny是个单线程的,Server在处理一个客户请求的时候无法接受别的客户,这在实际应用中是肯定不允许的。解决方法有

  1. 多进程:accept 之后 fork,父进程继续 accept,子进程来处理这 connfd。这样在高并发下,存在几个问题:
    问题1:每次来一个连接都 fork 开销太大。可以查一下调用 fork 时系统具体做了什么,注意一下复制父进程页表的操作。
    问题2:并发量上来后,进程调度器压力太大,进程切换开销非常大。
    问题3:高负载下,消耗太多内存,此外高并发下,进程间通信带来的开销也不能忽略。

  2. 多线程:accept 之后开线程来处理连接。这样解决了 fork 的问题,但是问题2和3还是无法解决。

  3. 线程池:线程数量固定。线程池简介和C++11实现 。这样可以解决以上几个问题。
int main(int argc, char **argv)
{
    int listenfd, connfd, clientlen;

    struct sockaddr_in clientaddr;

    if(argc != 2){
        fprintf(stderr, "Usage: %s <port>\n",argv[0]);
        exit(1);
    }

    //port = atoi(argv[1]);
    listenfd = Open_listenfd(argv[1]);

    while(1){
        clientlen = sizeof(clientaddr);
        connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
        doit(connfd);
        Close(connfd);
    }
}

(二)doit 函数

doit 函数处理一个 HTTP 事务。首先读取并解析请求行,用到 rio_readlineb 函数,请参考 用RIO包健壮地读写 。接下来分别解析出 method 、uri 、version,TINY只支持 GET 方法,如果是其他的方法,则调用 clienterror 函数 返回一个错误信息。

TINY不使用请求报头中的任何信息,接下来读取并忽略这些报头。

接下来解析 uri ,将 uri 解析为 文件名 和CGI 参数字符串。并得到请求的是静态内容还是动态内容。

如果没找到这个文件,那么发送一个错误信息给客户端并返回。

如果是请求静态内容,那么首先确认是普通文件并判断是否有读的权限,如果都OK,那么调用 serve_static 函数提供静态内容。类似,调用 serve_dynamic 函数提供动态内容。

/* $begin doit */
void doit(int fd) 
{
    int is_static;
    struct stat sbuf;
    char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
    char filename[MAXLINE], cgiargs[MAXLINE];
    rio_t rio;

    /* Read request line and headers */
    Rio_readinitb(&rio, fd);
    Rio_readlineb(&rio, buf, MAXLINE);                   //line:netp:doit:readrequest
    sscanf(buf, "%s %s %s", method, uri, version);       //line:netp:doit:parserequest
    if (strcasecmp(method, "GET")) {                     //line:netp:doit:beginrequesterr
       clienterror(fd, method, "501", "Not Implemented",
                "Tiny does not implement this method");
        return;
    }                                                    //line:netp:doit:endrequesterr
    read_requesthdrs(&rio);                              //line:netp:doit:readrequesthdrs

    /* Parse URI from GET request */
    is_static = parse_uri(uri, filename, cgiargs);       //line:netp:doit:staticcheck
    if (stat(filename, &sbuf) < 0) {                     //line:netp:doit:beginnotfound
    clienterror(fd, filename, "404", "Not found",
            "Tiny couldn't find this file");
    return;
    }                                                    //line:netp:doit:endnotfound

    if (is_static) { /* Serve static content */          
    if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode)) { //line:netp:doit:readable
        clienterror(fd, filename, "403", "Forbidden",
            "Tiny couldn't read the file");
        return;
    }
    serve_static(fd, filename, sbuf.st_size);        //line:netp:doit:servestatic
    }
    else { /* Serve dynamic content */
    if (!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode)) { //line:netp:doit:executable
        clienterror(fd, filename, "403", "Forbidden",
            "Tiny couldn't run the CGI program");
        return;
    }
    serve_dynamic(fd, filename, cgiargs);            //line:netp:doit:servedynamic
    }
}
/* $end doit */

(三)clienterror 函数

TINY没有完整的错误处理,但是可以检查一些明显的错误,并把它发送到客户端。在响应行中包含了相应的状态码和状态信息,响应主体中包含一个 HTML 文件,向浏览器用户解释错误。

/* $begin clienterror */
void clienterror(int fd, char *cause, char *errnum, 
         char *shortmsg, char *longmsg) 
{
    char buf[MAXLINE], body[MAXBUF];

    /* Build the HTTP response body */
    sprintf(body, "<html><title>Tiny Error</title>");
    sprintf(body, "%s<body bgcolor=""ffffff"">\r\n", body);
    sprintf(body, "%s%s: %s\r\n", body, errnum, shortmsg);
    sprintf(body, "%s<p>%s: %s\r\n", body, longmsg, cause);
    sprintf(body, "%s<hr><em>The Tiny Web server</em>\r\n", body);

    /* Print the HTTP response */
    sprintf(buf, "HTTP/1.0 %s %s\r\n", errnum, shortmsg);
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "Content-type: text/html\r\n");
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "Content-length: %d\r\n\r\n", (int)strlen(body));
    Rio_writen(fd, buf, strlen(buf));
    Rio_writen(fd, body, strlen(body));
}
/* $end clienterror */

(四)read_requesthdrs 函数

TINY不使用请求报头中的任何信息,此函数读取并在服务器端打印请求报头中的内容。终止请求报头的空文本行是由回车和换行符对组成的。

/* $begin read_requesthdrs */
void read_requesthdrs(rio_t *rp) 
{
    char buf[MAXLINE];

    Rio_readlineb(rp, buf, MAXLINE);
    while(strcmp(buf, "\r\n")) {          //line:netp:readhdrs:checkterm
    Rio_readlineb(rp, buf, MAXLINE);
    printf("%s", buf);
    }
    return;
}
/* $end read_requesthdrs */

(五)parse_uri 函数

Tiny假设静态内容的主目录就是当前目录,可执行文件的主目录是 ./cgi-bin/ 。任何包含字符串 cgi-bin 的 uri 都认为是对动态内容的请求。默认的文件名是 ./home.index。

如果是静态内容,会清楚CGI参数串,将 uri 转换为一个相对路径名,如果 uri 是以 / 结尾的,那么补充上默认的文件名。

如果是动态内容,提取出 CGI 参数,并把剩下的部分转换为相对文件名。

/* $begin parse_uri */
int parse_uri(char *uri, char *filename, char *cgiargs) 
{
    char *ptr;

    if (!strstr(uri, "cgi-bin")) {  /* Static content */ //line:netp:parseuri:isstatic
    strcpy(cgiargs, "");                             //line:netp:parseuri:clearcgi
    strcpy(filename, ".");                           //line:netp:parseuri:beginconvert1
    strcat(filename, uri);                           //line:netp:parseuri:endconvert1
    if (uri[strlen(uri)-1] == '/')                   //line:netp:parseuri:slashcheck
        strcat(filename, "home.html");               //line:netp:parseuri:appenddefault
    return 1;
    }
    else {  /* Dynamic content */                        //line:netp:parseuri:isdynamic
    ptr = index(uri, '?');                           //line:netp:parseuri:beginextract
    if (ptr) {
        strcpy(cgiargs, ptr+1);
        *ptr = '\0';
    }
    else 
        strcpy(cgiargs, "");                         //line:netp:parseuri:endextract
    strcpy(filename, ".");                           //line:netp:parseuri:beginconvert2
    strcat(filename, uri);                           //line:netp:parseuri:endconvert2
    return 0;
    }
}
/* $end parse_uri */

(六)serve_static 函数

TINY提供四种不同类型的静态内容:HTML 文件、无格式的文本文件,以及编码为GIF和JPEG格式的图片。

serve_static函数发送一个HTTP响应,主体是所请求的本地文件的内容。首先根据文件名后缀来判断文件类型,并且发送响应行和响应报头,用空行来终止报头。

接下来是发送响应主体。这里用到了 mmap函数,将被请求文件映射到一个虚拟存储空间,此后就可以通过指针来操作这个文件,最后释放映射的虚拟存储器区域。关于 mmap 请参考:认真分析mmap:是什么 为什么 怎么用

/*
 * serve_static - copy a file back to the client 
 */
/* $begin serve_static */
void serve_static(int fd, char *filename, int filesize) 
{
    int srcfd;
    char *srcp, filetype[MAXLINE], buf[MAXBUF];

    /* Send response headers to client */
    get_filetype(filename, filetype);       //line:netp:servestatic:getfiletype
    sprintf(buf, "HTTP/1.0 200 OK\r\n");    //line:netp:servestatic:beginserve
    sprintf(buf, "%sServer: Tiny Web Server\r\n", buf);
    sprintf(buf, "%sContent-length: %d\r\n", buf, filesize);
    sprintf(buf, "%sContent-type: %s\r\n\r\n", buf, filetype);
    Rio_writen(fd, buf, strlen(buf));       //line:netp:servestatic:endserve

    /* Send response body to client */
    srcfd = Open(filename, O_RDONLY, 0);    //line:netp:servestatic:open
    srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);//line:netp:servestatic:mmap
    Close(srcfd);                           //line:netp:servestatic:close
    Rio_writen(fd, srcp, filesize);         //line:netp:servestatic:write
    Munmap(srcp, filesize);                 //line:netp:servestatic:munmap
}

/*
 * get_filetype - derive file type from file name
 */
void get_filetype(char *filename, char *filetype) 
{
    if (strstr(filename, ".html"))
    strcpy(filetype, "text/html");
    else if (strstr(filename, ".gif"))
    strcpy(filetype, "image/gif");
    else if (strstr(filename, ".jpg"))
    strcpy(filetype, "image/jpeg");
    else
    strcpy(filetype, "text/plain");
}  
/* $end serve_static */

(七)serve_dynamic 函数

TINY 派生一个子进程来运行一个 CGI 程序,来提供动态内容。

此函数一开始先向客户端发送表明成功的响应行。

然后子进程用来自请求 uri 的 CGI 参数初始化 QUERY_STRING 环境变量。

一个 CGI 程序将它的动态内容发送到标准输出,在子进程加载并运行 CGI 程序之前,使用 dup2 函数将标准输出重定向到和客户端相关联的已连接描述符。这样任何 CGI 程序写到标准输出的东西都会直接送到客户端。

然后加载并运行 CGI 程序,其间父进程阻塞在对 wait 的调用中,等待当子进程终止的时候,回收操作系统分配给子进程的资源。

/*
 * serve_dynamic - run a CGI program on behalf of the client
 */
/* $begin serve_dynamic */
void serve_dynamic(int fd, char *filename, char *cgiargs) 
{
    char buf[MAXLINE], *emptylist[] = { NULL };

    /* Return first part of HTTP response */
    sprintf(buf, "HTTP/1.0 200 OK\r\n"); 
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "Server: Tiny Web Server\r\n");
    Rio_writen(fd, buf, strlen(buf));

    if (Fork() == 0) { /* child */ //line:netp:servedynamic:fork
    /* Real server would set all CGI vars here */
    setenv("QUERY_STRING", cgiargs, 1); //line:netp:servedynamic:setenv
    Dup2(fd, STDOUT_FILENO);         /* Redirect stdout to client */ //line:netp:servedynamic:dup2
    Execve(filename, emptylist, environ); /* Run CGI program */ //line:netp:servedynamic:execve
    }
    Wait(NULL); /* Parent waits for and reaps child */ //line:netp:servedynamic:wait
}
/* $end serve_dynamic */

上面是对TINY的代码的分析,只用了几百行C代码就实现了一个简单但是有功效的Web服务器,它既可以提供静态内容,又可以提供动态内容。但是构建一个功能齐全并且健壮的 Web 服务器并不是那么简单,所以还有很多细节要考虑。

我们上面已经说过用线程池可以解决几个问题,但是还要考虑一下,在处理长连接的时候,当一个线程处理完一批数据后,会再次 read,但是可能没有数据,因为默认情况下 fd 是阻塞的,所以这个线程就会被阻塞,当阻塞的线程多了,更多任务来之后,还是无法处理。此时可以把 blocking I/O 换成 non-blocking I/O,当有数据可读时返回数据,如果没有数据可读就返回-1并设置 error 为 EAGAIN。那如何知道 fd 上什么时候有数据可读呢? 总不能一直在用户态做轮询吧……因此要用到 I/O多路复用,即事件驱动的方式,所以推荐方式,非阻塞和IO复用联合起来。

所以目前计划实现是这样的:IO多路复用 + non-blocking + threadpool的设计方案。

劳特巴赫 Trace32 调试使用教程 详细介绍如何使用trace32进行调试 阅读详情

相关推荐

飞思卡尔QorIQ安全启动实战:从密钥生成到熔丝烧录的完整指南

在嵌入式系统与物联网设备开发中,**安全启动**是构建设备信任根基的核心机制,其原理基于密码学与硬件信任根。它通过逐级验证的**信任链**模型,确保从芯片上电到操作系统加载的每一段代码都经过数字签名验证,从而有效防御固件篡改与恶意代码注入。这项技术的核心价值在于为工业控制、网络通信等对可靠性要求严苛的领域提供了硬件级的安全保障。其典型应用场景包括防止设备在供应链或现场部署中被植入后门。本文将聚焦于**飞思卡尔QorIQ P系列处理器**(如P1010、P3041)的安全启动实现,深入剖析其独特的**硬件信任

weixin_33841503的博客 320

劳特巴赫或PE刷程序.docx

劳特巴赫或PE刷程序

i.MX28 EVK开发板从开箱到启动:ARM9嵌入式系统实战指南

嵌入式系统开发始于对硬件平台的深入理解与正确配置。其核心在于处理器启动流程与板级支持包(BSP)的协同工作。启动流程定义了处理器上电后如何加载并执行第一段代码,而BSP则包含了操作系统与具体硬件主板之间的所有驱动和适配层,是系统稳定运行的基石。这种软硬件协同设计,对于工业控制、物联网网关等需要高可靠性和特定外设支持的应用场景至关重要。本文以经典的ARM9架构i.MX28处理器及其评估套件(EVK)为例,详细解析了从启动模式配置、电源管理到串口调试、系统启动的全过程,并提供了针对SD卡启动、Linux/Win

weixin_30460489的博客 605

劳特巴赫工具简介

希望对各位有帮助,是劳特巴赫软件的基础-介绍-平台Aurix

劳特巴赫trace32烧录方法

打开脚本,位置在: 安装路径\files\demo\powerpc\flash,选择里面的cmm文件。运行脚本,加载elf文件,点击运行即可。选择内核,点击start即可。点击continue执行即可。

qiu421的博客 1305

劳德巴赫(trace32)硬件环境搭建与canoe发送报文

TRACE32基础连接操作步骤,canoe硬件连接发送报文

s147849的博客 3418

【开发工具】劳特巴赫 Trace 32 的使用

描述了劳特巴赫的使用过程

qq_40555347的博客 6007

LauterBach介绍及软硬件安装教程

本博文介绍了Lauterbach的信息,软硬件安装教程

weixin_43722921的博客 6481

NXP S32G开发板Linux实战:从启动到网络加速全解析

车载网络架构正从分布式ECU向域控制器与中央计算演进,传统CAN总线在带宽和算力上的瓶颈日益凸显,促使车规级多核SoC成为新一代网关的核心。嵌入式Linux作为域控设备的基础软件平台,其启动流程、BSP构建与驱动适配是开发者必须掌握的关键技能。基于ARM异构多核架构,NXP S32G通过A53与M7核分工,配合PFE和LLCE网络加速引擎,在车联网场景中实现低延迟数据转发与实时总线处理。围绕S32G网络开发套件,从硬件资源、固件启动链路、Yocto构建到PFE驱动验证,系统梳理了Linux运行环境的搭建与调

weixin_33827731的博客 499

NXP Layerscape安全启动实战:SRK表、ISBC/ESBC验证与错误码解析

安全启动是嵌入式系统,特别是基于NXP Layerscape等高性能处理器的设备中,实现固件完整性与身份认证的核心安全机制。其原理基于密码学中的非对称加密(如RSA)和哈希算法(如SHA-256),构建一条从硬件ROM到操作系统的逐级验证信任链。这项技术的核心价值在于,它能从硬件层面确保设备只执行经过授权方签名的可信代码,有效防御固件篡改、恶意代码植入等攻击,是工业控制、网络设备、汽车电子等领域满足功能安全与信息安全要求的基石。在工程实践中,开发者需要深度理解并正确配置SRK表(公钥哈希表)和两阶段验证流程

chiyingliao3565的博客 517

NXP Layerscape安全启动实战:CSF头解析、错误码排查与CST工具指南

安全启动是现代嵌入式系统,尤其是工业控制、汽车电子等高可靠性领域的关键硬件安全特性。其核心原理是构建一条从硬件信任根到应用软件的完整信任链,通过逐级验证数字签名,确保固件在加载执行前未被篡改。这项技术的核心价值在于为设备提供了启动阶段的安全保障,能有效防御固件植入、供应链攻击等威胁。在工程实践中,安全启动的实现涉及密钥管理、映像签名、硬件熔丝配置等多个环节,其中CSF(命令序列文件)头结构是承载签名与验证元数据的核心载体。开发者常需使用如NXP代码签名工具(CST)等专用工具链进行映像签名与安全配置。本文将

chiyingliao3565的博客 427

Lauterbach发布免费多核调试配置工具

为了帮助工程师进行多核SoC或多处理器设计,Lauterbach日前为其TRACE32调试工具提供免费的扩展版本T32Start,适用于汽车电子系统、工业控制系统等众多场合的配置调试。    T32Start给多核应用调试器的配置提供了实际帮助,它利用图形映射将复杂的调试系统图形化并简化配置。无论是用单JTAG接口或是用具有通用JTAG接口的多核系统实现,它都可以用于各种多处理器配置上。T32Start向开发者图解调试器模块与树形结构中的相应核连接的过程。在这种背景下,可以选择每一个情形中所使用的计算机接口,即USB、以太网、甚至老式的并行口和ISA卡。    此外,如果需要的话,可以自动获取

NXP Layerscape安全启动与信任链实现:从硬件熔丝到Linux内核的完整指南

在嵌入式系统安全领域,安全启动是构建设备信任根基的核心机制。其原理在于建立一条从硬件到软件的逐级验证链,确保系统加载的每一段代码都未被篡改。这项技术的核心价值在于为工业控制、网络通信等高可靠性场景提供了固件层面的安全保障。具体实现上,它通常基于非对称加密和硬件信任根,通过验证数字签名来确保引导加载程序和操作系统内核的完整性。NXP Layerscape处理器将这一理念与保密性机制深度结合,利用硬件熔丝存储根密钥,并通过CAAM模块的Blob封装技术,对关键镜像进行加密存储和运行时解密,有效防止了物理提取攻击

ozzzzzz的专栏 319

i.MX 8/8X异构计算平台:硬件虚拟化、多显示与OpenVX视觉加速实战

在嵌入式系统开发中,异构计算架构通过集成不同类型的处理核心(如CPU、GPU、DSP),实现了计算资源的专业化分工与高效协同,从而在性能、功耗和实时性之间取得平衡。其核心原理在于利用硬件虚拟化技术,在单颗SoC上创建多个隔离的安全域,允许不同的操作系统和任务独立运行,确保了系统的可靠性与功能安全。这种技术为工业控制、车载信息娱乐和边缘AI视觉等复杂场景提供了关键价值。具体到应用层面,多显示控制器支持离线合成与多路独立输出,能够驱动复杂的多屏人机交互界面;而OpenVX硬件加速框架则能自动将视觉算法计算图调度

cuifan5176的博客 893

从玄戒O3看自研SoC:命名逻辑、启动流程与开发者影响

SoC(片上系统)将CPU、GPU、NPU、ISP等模块集成于单芯片,是现代移动设备的核心计算平台。芯片从命名到真正运行,需经历设计、验证、流片及复杂的启动流程,其中BootROM、U-Boot、内核引导等环节对嵌入式开发至关重要。从“玄戒O3”这一命名切入,文章拆解了自研芯片的命名规则与产品定位,通过Python状态机模拟启动流程,并展示QEMU验证内核的方法。同时,围绕BSP定制、性能调优和生态兼容,探讨了新SoC对Android应用开发与嵌入式驱动开发者的实际影响。对于希望在芯片底层建立系统认知的开发

weixin_34252090的博客 355

NXP Layerscape平台安全启动与TF-A引导全流程实战解析

安全启动(Secure Boot)是嵌入式系统硬件级的安全基石,其核心原理在于构建一条从硬件信任根到操作系统的信任链(Chain of Trust),通过非对称加密算法验证每一级引导代码的完整性与真实性,从而防止启动阶段的恶意代码注入。这项技术对于保障网络通信、工业控制等关键设备的固件安全具有不可替代的价值。在ARM架构中,ARM Trusted Firmware(TF-A)作为运行在最高特权等级EL3的官方参考实现,是执行安全启动流程和构建可信执行环境的核心。本文将结合NXP Layerscape系列处理

362

NXP Layerscape安全启动机制深度解析与ESBC错误码实战排查指南

安全启动是嵌入式系统构建信任链的核心机制,它通过密码学原理确保设备从硬件信任根到应用软件的每一级代码都经过完整性和真实性验证。其技术价值在于为工业控制、网络通信等高安全要求场景提供了固件防篡改的基石。在NXP Layerscape这类高性能多核Arm平台上,安全启动通过内部安全启动代码和外部安全启动代码的协同工作实现。工程师在实际开发中常会遇到ESBC验证失败,并伴随诸如ERROR_ESBC_CLIENT_HASH_COMPARE_KEY或ERROR_ESBC_CLIENT_HASH_COMPARE_EM等

458

NXP Layerscape安全启动原理与ISBC/ESBC错误代码深度解析

嵌入式系统的安全启动是构建设备信任根的核心机制,其本质是通过密码学原理在启动时序上建立不可篡改的验证链。该机制通常基于非对称加密算法(如RSA)和数字签名技术,确保每一段引导代码的完整性与真实性。其技术价值在于从硬件层面锚定信任起点,防止恶意代码在启动初期植入,为后续操作系统和应用提供坚实的安全基础。在工业网关、网络处理器等高安全要求场景中,安全启动是满足功能安全认证和防御供应链攻击的关键。本文聚焦于NXP Layerscape处理器平台,深入剖析其ISBC(内部安全启动代码)和ESBC(外部安全启动代码)

540

JTAG硬件调试核心技术解析:从协议原理到实战应用

在嵌入式系统开发和硬件调试领域,边界扫描技术提供了一种标准化、非侵入式的芯片内部访问方法。其核心原理基于IEEE 1149.1标准定义的TAP状态机,通过TCK、TMS、TDI、TDO四根信号线实现芯片内部寄存器的串行访问。这项技术的工程价值在于贯穿了硬件产品的全生命周期,从芯片设计验证、PCB生产测试到软件调试和系统集成。在实际应用场景中,JTAG不仅用于基本的程序下载和在线调试,还能执行边界扫描测试检测PCB短路开路,进行量产编程与安全配置,并支持多核系统调试与性能分析。掌握JTAG调试技术,特别是理解

benben 467
上一篇: 系统级 I/O与缓冲机制
下一篇: 内存寻址之分页机制
Tanswer_
博客等级 码龄10年 231粉丝 91原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值