来讲讲:Linux中写个复杂的网卡驱动程序
初学阶段只要把上课时候学习过的命令练熟就可以了.单靠学习各种命令而成为高手是不可能的。进修应当是一个先把成绩复杂化,再把成绩庞大化的历程。一入手下手就动手处置庞大的成绩,不免让人故意惊胆颤,左支右绌的感到。读Linux网卡驱动也是一样。那长长的源码同化着那些我们生疏的变量和标记,望而却步即是天经地义的了。不要忧虑,事变总有办理的举措,先把一些我们管不着的代码切割进来,留下必需的部分,把框架把握了,那其他的事变天然就瓜熟蒂落了,这是笔者的心得。
一样平常在利用的Linux网卡驱动代码动辄3000行摆布,这个代码量和它所表达出来的常识量无疑是复杂的,我们有无举措延长一下这个代码量,使我们的进修变的复杂些呢?经由笔者的不懈勉力,在仍旧可以使收集设备一般事情的条件下,把它缩减到了600多行,我们把临时还用不上的功效先割进来。如许一来,事变就复杂多了,真的就剩下一个框架了。
上面我们就来分析这个能够实行的框架。
限于篇幅,以下剖析用到的一切触及到内核中的函数代码,我都不予列出,但给出在哪一个详细文件中,请读者自行查阅。
起首,我们来看看设备的初始化。当我们准确编译完我们的程序后,我们就必要把天生的方针文件加载到内核中往,我们会先ifconfigeth0down和rmmod8139too来卸载正在利用的网卡驱动,然后insmod8139too.o把我们的驱动加载出来(个中8139too.o是我们编译天生的方针文件)。就像C程序有主函数main()一样,模块也有第一个实行的函数,即module_init(rtl8139_init_module);在我们的程序中,rtl8139_init_module()在insmod以后首先实行,它的代码以下:
staticint__initrtl8139_init_module(void)
{
returnpci_module_init(&rtl8139_pci_driver);
}
它间接挪用了pci_module_init(),这个函数代码在Linux/drivers/net/eepro100.c中,而且把rtl8139_pci_driver(这个布局是在我们的驱动代码里界说的,它是驱动程序和PCI设备接洽的纽带)的地点作为参数传给了它。rtl8139_pci_driver界说以下:
staticstructpci_driverrtl8139_pci_driver={
name:MODNAME,
id_table:rtl8139_pci_tbl,
probe:rtl8139_init_one,
remove:rtl8139_remove_one,
};
pci_module_init()在驱动代码里没有界说,你必定想到了,它是Linux内核供应给模块是一个尺度接口,那末这个接口都干了些甚么?笔者跟踪了这个函数,内里挪用了pci_register_driver(),这个函数代码在Linux/drivers/pci/pci.c中,pci_register_driver做了三件事变。
①是把带过去的参数rtl8139_pci_driver在内核中举行了注册。内核中有一个PCI设备的年夜的链表,这里卖力把这个PCI驱动挂到内里往。
②是检察总线上一切PCI设备(网卡设备属于PCI设备的一种)的设置空间,假如发明标识信息与rtl8139_pci_driver中的id_table不异,即rtl8139_pci_tbl,而它的界说以下:
staticstructpci_device_idrtl8139_pci_tbl[]__devinitdata={
{0x10ec,0x8129,PCI_ANY_ID,PCI_ANY_ID,0,0,1},
{PCI_ANY_ID,0x8139,0x10ec,0x8139,0,0,0},
{0,}
};
那末就申明这个驱动程序就是用来驱动这个设备的,因而挪用rtl8139_pci_driver中的probe函数即rtl8139_init_one,这个函数是在我们的驱动程序中界说了的,它是用来初始化全部设备和做一些筹办事情。这里必要注重一下pci_device_id是内审定义的用来分辨分歧PCI设备的一个布局,比方在我们这里0x10ec代表的是Realtek公司,我们扫描PCI设备设置空间假如发明有Realtek公司打造的设备时,二者就对上了。固然对上了公司号后还得看其他的设备号甚么的,都对上了才申明这个驱动是能够为这个设备服务的。
③是把这个rtl8139_pci_driver布局挂在这个设备的数据布局(pci_dev)上,暗示这个设备今后就有了本人的驱动了。而驱动也找到了它服务的工具了。
PCI是一个总线尺度,PCI总线上的设备就是PCI设备,这些设备有良多范例,固然也包含网卡设备,每个PCI设备在内核中笼统为一个数据布局pci_dev,它形貌了一个PCI设备的一切的特征,详细请查询相干文档,本文限于篇幅没法具体形貌。可是有几个中央和驱动程序的干系出格年夜,必需予以申明。PCI设备都恪守PCI尺度,这个部分一切的PCI设备都是一样的,每一个PCI设备都有一段存放器存储着设置空间,这一部分格局是一样的,好比第一个存放器老是临盆商号码,如Realtek就是10ec,而Intel则是另外一个数字,这些都是商家像尺度构造请求的,是一定分歧的。我就能够经由过程设置空间来分辨其临盆商,设备号,不管你甚么平台,x86也好,ppc也好,他们都是统一的尺度格局。固然光有这些PCI设置空间的一致格局仍是不敷的,好比说人类,都有鼻子和眼睛,但并非一切人的鼻子和眼睛都长的一样的。网卡设备是PCI设备必需恪守划定规矩,在设备里集成了PCI设置空间,但它是一个网卡就必需同时集成能把持网卡事情的存放器。而存放器的会见就成了一个成绩。在Linux内里我们是把这些存放器映照到主存假造空间上的,换句话说我们的CPU访存指令就能够会见到这些处于外设中的把持存放器。总结一下,PCI设备次要包含两类空间,一个是设置空间,它是操纵体系或BIOS把持外设的一致格局的空间,CPU指令不克不及会见,会见这个空间要借助BIOS功效,现实上Linux的会见设置空间的函数是经由过程CPU指令使令BIOS来完成读写会见的。
而另外一类是一般的把持存放器空间,这一部分映照完后CPU能够会见来把持设备事情。
如今我们回到下面pci_register_driver的第二步,假如找到相干设备和我们的pci_device_id布局数
12345下一页
每一个开发团队都对他的发行版做过测试后放出的.那些国际知名的大品牌更是如此。
来讲讲:Linux中写个复杂的网卡驱动程序
系统做了些什么,这需要时间去掌握,(背命令不是一件好的学习方法,相信我你一定会在你背完之前全部忘光),尽量掌握常用命令;组对上号了,申明我们找到服务工具了,则挪用rtl8139_init_one,它次要做了七件事:</P> ①创建net_device布局,让它在内核中代表这个收集设备。可是读者大概会问,pci_dev也是代表着这个设备,那末二者有甚么区分呢,正如我们下面会商的,网卡设备既要遵守PCI标准,也要担当起其作为网卡设备的职责,因而就分了两块,pci_dev用来卖力网卡的PCI标准,而这里要说的net_device则是卖力网卡的收集设备这个职责。
dev=init_etherdev(NULL,sizeof(*tp));
if(dev==NULL){
printk("unabletoallocnewethernet\n");
return-ENOMEM;
}
tp=dev->priv;
init_etherdev函数在Linux/drivers/net/net_init.c中,在这个函数平分配了net_device的内存并举行了开端的初始化。这里值得注重的是net_device中的一个成员priv,它代表着分歧网卡的公有数据,好比Intel的网卡和Realtek的网卡在内核中都是以net_device来代表。可是他们是有区分的,好比Intel和Realtek完成统一功效的办法纷歧样,这些都是靠着priv来表现。以是这里把拿出来同net_device等量齐观。分派内存时,net_device中除priv之外的成员都是流动的,而priv的巨细是可以恣意的,以是分派时要把priv的巨细传已往。
②开启这个设备(实际上是开启了设备的存放器映照到内存的功效)
rc=pci_enable_device(pdev);
if(rc)
gotoerr_out;
pci_enable_device也是一个内核开辟出来的接口,代码在drivers/pci/pci.c中,笔者跟踪发明这个函数次要就是把PCI设置空间的Command域的0位和1地位成了1,从而到达了开启设备的目标,由于rtl8139的官方datasheet中,申明了这两位的感化就是开启内存映照和I/O映照,假如不开的话,那我们以上会商的把把持存放器空间映照到内存空间的这一功效就被屏障了,这对我们长短常倒霉的,除此以外,pci_enable_device还做了些中止开启事情。
③取得各项资本
mmio_start=pci_resource_start(pdev,1);
mmio_end=pci_resource_end(pdev,1);
mmio_flags=pci_resource_flags(pdev,1);
mmio_len=pci_resource_len(pdev,1);
读者大概疑问我们的存放器被映照到内存中的甚么中央是甚么时分有谁决意的呢。是如许的,在硬件加电初始化时,BIOS固件统一反省了一切的PCI设备,并一致为他们分派了一个和其他互不抵触的地点,让他们的驱动程序能够向这些地点映照他们的存放器,这些地点被BIOS写进了各个设备的设置空间,由于这个举动是一个PCI的尺度的举动,以是天然写到各个设备的设置空间里而不是他们作风各别的把持存放器空间里。固然只要BIOS能够会见设置空间。当操纵体系初始化时,他为每一个PCI设备分派了pci_dev布局,而且把BIOS取得的并写到了设置空间中的地点读出来写到了pci_dev中的resource字段中。如许今后我们在读这些地点就不必要在会见设置空间了,间接跟pci_dev要就能够了,我们这里的四个函数就是间接从pci_dev读出了相干数据,代码在include/linux/pci.h中。界说以下:
#definepci_resource_start(dev,bar)((dev)->resource[(bar)].start)
#definepci_resource_end(dev,bar)((dev)->resource[(bar)].end)
这里必要申明一下,每一个PCI设备有0-5一共6个地点空间,我们一般只利用前两个,这里我们把参数1传给了bar就是利用内存映照的地点空间。
④把失掉的地点举行映照
ioaddr=ioremap(mmio_start,mmio_len);
if(ioaddr==NULL){
printk("cannotremapMMIO,aborting\n");
rc=-EIO;
gotoerr_out_free_res;
}
ioremap是内核供应的用来映照外设存放器到主存的函数,我们要映照的地点已从pci_dev中读了出来(上一步),如许就瓜熟蒂落的乐成映照了而不会和其他地点有抵触。映照完了有甚么效果呢,我举个例子,好比某个网卡有100个存放器,他们都是连在一块的,地位是流动的,到场每一个存放器占4个字节,那末一共400个字节的空间被映照到内存乐成后,ioaddr就是这段地点的开首(注重ioaddr是假造地点,而mmio_start是物理地点,它是BIOS失掉的,一定是物理地点,而回护形式下CPU不认物理地点,只认假造地点),ioaddr+0就是第一个存放器的地点,ioaddr+4就是第二个存放器地点(每一个存放器占4个字节),以此类推,我们就可以够在内存中会见到一切的存放器进而操控他们了。
<!---->
<!---->
⑤重启网卡设备
重启网卡设备是初始化网卡设备的一个主要部分,它的道理就是向存放器中写进命令就能够了(注重这里写存放器,而不是设置空间,由于跟PCI没有甚么干系),代码以下:
writeb((readb(ioaddr+ChipCmd)&ChipCmdClear)|CmdReset,ioaddr+ChipCmd);
是我们看到第二参数ioaddr+ChipCmd,ChipCmd是一个位移,使地点恰好对应的就是ChipCmd哪一个存放器,读者能够查阅官方datasheet失掉这个位移量,我们在程序中界说的这个值为:ChipCmd=0x37;与datasheet是符合的。我们把这个命令存放器中响应位(R
上一页12345下一页
每一个开发团队都对他的发行版做过测试后放出的.那些国际知名的大品牌更是如此。
来讲讲:Linux中写个复杂的网卡驱动程序
如果你让他去用linux搭建一个web服务器,做一个linux网关,他就什么都不会了.他们把时间都浪费在了版本的转换上了.ESET)置1就能够完成操纵。</P> ⑥取得MAC地点,并把它存储到net_device中。
for(i=0;i<6;i++){/*HardwareAddress*/
dev->dev_addr=readb(ioaddr+i);
dev->broadcast=0xff;
}
我们能够看到读的地点是ioaddr+0到ioaddr+5,读者检察官方datasheet会发明存放器地点空间的开首6个字节恰好存的是这个网卡设备的MAC地点,MAC地点是收集中标识网卡的物理地点,这个地点在从此的收发数据包时会用的上。
⑦向net_device中挂号一些次要的函数
dev->open=rtl8139_open;
dev->hard_start_xmit=rtl8139_start_xmit;
dev->stop=rtl8139_close;
因为dev(net_device)代表着设备,把这些函数注册完后,rtl8139_open就是用于翻开这个设备,rtl8139_start_xmit就是当使用程序要经由过程这个设备往表面发数据时被挪用,详细的实在这个函数是在收集协定层中挪用的,这就触及到Linux收集协定栈的内容,不再我们会商之列,我们只是卖力完成它。rtl8139_close用来关失落这个设备。
好了,到此我们把rtl8139_init_one函数先容完了,初始化个设备完了以后呢,我们经由过程ifconfigeth0up命令来把我们的设备激活。这个命令间接招致了我们方才注册的rtl8139_open的挪用。这个函数激活了设备。这个函数次要做了三件事。
①注册这个设备的中止处置函数。当网卡发送数据完成大概吸收到数据时,是用中止的情势来告诉的,好比无数据从网线传来,中止也关照了我们,那末必需要有一个处置这其中断的函数来完成数据的吸收。关于Linux的中止机制不是我们具体解说的范围,有乐趣的能够参考《Linux内核源代码情形分析》,可是有个十分主要的资本我们必需注重,那就是中止号的分派,和内存地点映照一样,中止号也是BIOS在初始化阶段分派并写进设备的设置空间的,然后Linux在创建pci_dev时从设置空间读出这其中断号然后写进pci_dev的irq成员中,以是我们注册中止程序必要中止号就是间接从pci_dev里取就能够了。
retval=request_irq(dev->irq,rtl8139_interrupt,SA_SHIRQ,dev->name,dev);
if(retval){
returnretval;
}
我们注册的中止处置函数是rtl8139_interrupt,也就是说当网卡产生中止(如数据抵达)时,中止把持器8259A把中止号发给CPU,CPU依据这其中断号找各处理程序,这里就是rtl8139_interrupt,然后实行。rtl8139_interrupt也是在我们的程序中界说好了的,这是驱动程序的一个主要的任务,也是一个基础的功效。request_irq的代码在arch/i386/kernel/irq.c中。
②分派发送和吸收的缓存空间
依据官方文档,发送一个数据包的历程是如许的:先从使用程序中把数据包拷贝到一段一连的内存中(这段内存就是我们这里要分派的缓存),然后把这段内存的地点写进网卡的数据发送地点存放器(TSAD)中,这个存放器的偏移量是TxAddr0=0x20。在把这个数据包的长度写进另外一个存放器(TSD)中,它的偏移量是TxStatus0=0x10。然后就把这段内存的数据发送到网卡外部的发送缓冲中(FIFO),最初由这个发送缓冲区把数据发送到网线上。
好了如今创立这么一个发送和吸收缓冲内存的目标已很明显了。
tp->tx_bufs=pci_alloc_consistent(tp->pci_dev,TX_BUF_TOT_LEN,&tp->tx_bufs_dma);
tp->rx_ring=pci_alloc_consistent(tp->pci_dev,RX_BUF_TOT_LEN,&tp->rx_ring_dma);
tp是net_device的priv的指针,tx_bufs是发送缓冲内存的首地点,rx_ring是吸收缓存内存的首地点,他们都是假造地点,而最初一个参数tx_bufs_dma和rx_ring_dma均是这一段内存的物理地点。为何统一个事物,既用假造地点来暗示它还要用物理地点呢,是如许的,CPU实行程序用到这个地点时,用假造地点,而网卡设备向这些内存中存取数据时用的是物理地点(由于网卡绝对CPU属于思想对照复杂型的)。pci_alloc_consistent的代码在Linux/arch/i386/kernel/pci-dma.c中。
③发送和吸收缓冲区初始化和网卡入手下手事情的操纵
RTL8139有4个发送形貌符(包含4个发送缓冲区的基地点存放器(TSAD0-TSAD3)和4个发送形态存放器(TSD0-TSD3)。也就是说我们分派的缓冲区要分红四个平分并把这四个空间的地点都写到相干存放器里往,上面这段代码完成了这个操纵。
for(i=0;i<NUM_TX_DESC;i++)
((structrtl8139_private*)dev->priv)->tx_buf=
&((structrtl8139_private*)dev->priv)->tx_bufs;
下面这段代码卖力把发送缓冲区假造空间举行了支解。
for(i=0;i<NUM_TX_DESC;i++)
{
writel(tp->tx_bufs_dma+(tp->tx_buftp->tx_bufs),ioaddr+TxAddr0+(i*4));
readl(ioaddr+TxAddr0+(i*4));
}
下面这段代码卖力把发送缓冲区物理空间举行了支解,并把它写到了相干存放器中,如许在网卡入手下手事情后就可以够敏捷定位和找到这些内存并存取他们的数据。
</p>上一页12345下一页
只要了解了Linux的基础之后,应该就可以很轻易的解决掉这方面的问题。而有些朋友们常常一接触Linux就是希望构架网站,根本没有想到要先了解一下Linux的基础。这是相当困难的。
来讲讲:Linux中写个复杂的网卡驱动程序
买一本命令参考手册是必要的,遇到不知道怎么用的命令可以随时查询,这要比查man文档快.特别适合英语不好。writel(tp->rx_ring_dma,ioaddr+RxBuf);
下面这行代码是把吸收缓冲区的物理地点写到了相干存放器中,如许网卡吸收到数据后就可以正确的把数据从网卡中搬运到这些内存空间中,守候CPU来领走他们。
writeb((readb(ioaddr+ChipCmd)&ChipCmdClear)|CmdRxEnb|CmdTxEnb,ioaddr+ChipCmd);
从头RESET设备后,我们要激活设备的发送和吸收的功效,下面这行代码就是向相干存放器中写进响应值,激活了设备的这些功效。
writel((TX_DMA_BURST<<TxDMAShift),ioaddr+TxConfig);
下面这行代码是向网卡的TxConfig(位移是0x44)存放器中写进TX_DMA_BURST<<TxDMAShift这个值,翻译过去就是6<<8,就是把第8到第10这三地位成110,查阅管法文档发明6就是110代表着一次DMA的数据量为1024字节。
别的在这个阶段设置了吸收数据的形式,和开启中止等等,限于篇幅由读者自行研讨。
上面进进数据收发阶段:
当一个收集使用程序要向收集发送数据时,它要使用Linux的收集协定栈来办理一系列成绩,找到网卡设备的代表net_device,由这个布局来找到并把持这个网卡设备来完成数据包的发送,详细是挪用net_device的hard_start_xmit成员函数,这是一个函数指针,在我们的驱动程序里它指向的是rtl8139_start_xmit,恰是由它来完成我们的发送事情的,上面我们就来分析这个函数。它一共做了四件事。
①反省这个要发送的数据包的长度,假如它达不到以太网帧的长度,必需接纳措施举行添补。
if(skb->len<ETH_ZLEN){//ifdata_len<60
if((skb->data+ETH_ZLEN)<=skb->end){
memset(skb->data+skb->len,0x20,(ETH_ZLEN-skb->len));
skb->len=(skb->len>=ETH_ZLEN)?skb->len:ETH_ZLEN;}
else{
printk("%s:(skb->data+ETH_ZLEN)>skb->end\n",__FUNCTION__);
}
}
skb->data和skb->end就决意了这个包的内容,假如这个包自己统共的长度(skb->end-skb->data)都达不到请求,那末想填也没中央填,就堕落前往了,不然的话就填上。
②把包的数据拷贝到我们已创建好的发送缓存中。
memcpy(tp->tx_buf,skb->data,skb->len);
个中skb->data就是数据包数据的地点,而tp->tx_buf就是我们的发送缓存地点,如许就完成了拷贝,健忘了这些内容的转头看看后面的先容。
③光有了地点和数据还不可,我们要让网卡晓得这个包的长度,才干包管数据未几很多准确的从缓存中截掏出来搬运到网卡中往,这是靠写发送形态存放器(TSD)来完成的。
writel(tp->tx_flag|(skb->len>=ETH_ZLEN?skb->len:ETH_ZLEN),ioaddr+TxStatus0+(entry*4));
我们把这个包的长度和一些把持信息一同写进了形态存放器,使网卡的事情有了根据。
④判别发送缓存是不是已满了,假如满了在发就掩盖数据了,要停发。
if((tp->cur_tx-NUM_TX_DESC)==tp->dirty_tx)
netif_stop_queue(dev);
谈完了发送,我们入手下手谈吸收,当无数据从网线上过去时,网卡发生一其中断,挪用的中止服务程序是rtl8139_interrupt,它次要做了三件事。
①从网卡的中止形态存放器中读出形态值举行剖析,status=readw(ioaddr+IntrStatus);
if((status&(PCIErr|PCSTimeout|RxUnderrun|RxOverflow|RxFIFOOver|TxErr|TxOK|RxErr|RxOK))==0)
gotoout;
下面代码申明假如下面这9种情形均没有的暗示没甚么优点理的了,加入。
②if(status&(RxOK|RxUnderrun|RxOverflow|RxFIFOOver))/*Rxinterrupt*/
rtl8139_rx_interrupt(dev,tp,ioaddr);
假如是以上4种情形,属于吸收旌旗灯号,挪用rtl8139_rx_interrupt举行吸收处置。
③if(status&(TxOK|TxErr)){
spin_lock(&tp->lock);
rtl8139_tx_interrupt(dev,tp,ioaddr);
spin_unlock(&tp->lock);
}
假如是传输完成的旌旗灯号,就挪用rtl8139_tx_interrupt举行发送善后处置。
上面我们先来看看吸收中止处置函数rtl8139_rx_interrupt,在这个函数中次要做了上面四件事
①这个函数是一个年夜轮回,轮回前提是只需吸收缓存不为空就还能够持续读取数据,轮回不会中断,读空了以后就跳出。
intring_offset=cur_rx%RX_BUF_LEN;
rx_status=le32_to_cpu(*(u32*)(rx_ring+ri
上一页12345下一页
学习linux,就意味着更快的开发效率,等更多关于软件本身或者说操作系统本身的理解。
来讲讲:Linux中写个复杂的网卡驱动程序
要明白学好linux不是一件一蹴而就的事,一定要能坚持使用它,特别是在使用初期。ng_offset));</P> rx_size=rx_status>>16;
下面三行代码是盘算出要吸收的包的长度。
②依据这个长度来分派包的数据布局
skb=dev_alloc_skb(pkt_size+2);
③假如分派乐成就把数据从吸收缓存中拷贝到这个包中
eth_copy_and_sum(skb,&rx_ring,pkt_size,0);
这个函数在include/linux/etherdevice.h中,本色仍是挪用了memcpy()。
staticinlinevoideth_copy_and_sum(structsk_buff*dest,unsignedchar*src,intlen,intbase)
{
memcpy(dest->data,src,len);
}
如今我们已熟知,&rx_ring就是吸收缓存,也是源地点,而skb->data就是包的数据地点,也是目标地点,一览无余。
④把这个包送到Linux协定栈往举行下一步处置
skb->protocol=eth_type_trans(skb,dev);
netif_rx(skb);
在netif_rx()函数实行完后,这个包的数据就离开了网卡驱动范围,而进进了Linux收集协定栈内里,把这些数据包的以太网帧头,IP头,TCP头都脱上去,最初把数据送给了使用程序,不外协定栈不再本文会商局限内。netif_rx函数在net/core/dev.c,中。
而rtl8139_remove_one则基础是rtl8139_init_one的逆历程。
到此,本文已将Linux驱动程序的框架勾画了出来
</p>上一页12345
文件处理命令:file、mkdir、grep、dd、find、mv、ls、diff、cat、ln 老实说,第一个程序是在C中编译好的,调试好了才在Linux下运行,感觉用vi比较麻烦,因为有错了不能调试,只是提示错误。 就这样,我们一边上OS理论课,一边上这个实验,这样挺互补的,老师讲课,一步一步地布置任务 下面笔者在论坛看到的一个好问题: “安装红旗4.0后,系统紫光输入法自带的双拼方案和我的习惯不一样,如何自定义双拼方案解决?谢谢?”这个问题很简练。 熟悉操作是日常学习Linux中的三大法宝。以下是作者学习Linux的一些个人经验,供参考: 编程学习及开发,Linux是免费,开源的操作系统,并且可开发工具相当多,如果您支持自由软件,一定要同广大热爱自由软件人士一同为其不懈努力。 对Linux命令熟悉后,你可以开始搭建一个小的Linux网络,这是最好的实践方法。Linux是网络的代名词,Linux网络服务功能非常强大,不论是邮件服务器、Web服务器、DNS服务器等都非常完善。 发问的时候一定要注意到某些礼节。因为Linux社区是一个松散的组织、也不承担回复每个帖子的义务。它不是技术支持。 如果上面的措施没有解决问题,此时你就需要Linux社区的帮助了。 Linux的使用者一般都是专业人士,他们有着很好的电脑背景且愿意协助他人。
页:
[1]