linux 设备驱动之8250串口驱动分析_[ 1.665684] serial: 8250/16550 driver, 5 ports, ir-程序员宅基地

技术标签: serial  串口  linux kernel  

linux 设备驱动之 8250 串口驱动  
------------------------------------------  
本文系本站原创,欢迎转载! 
转载请注明出处:http://ericxiao.cublog.cn/ 
------------------------------------------ 
一:前言 
前一段时间自己实践了一下8250芯片串口驱动的编写。今天就在此基础上分析一下linux kernel 自带的
串口驱动。毕竟只有对比专业的驱动代码才能更好的进步,同以往一样,基于linix kernel2.6.25.相应驱
动代码位于:linux-2.6.25/drivers/serial/8250.c。 
二:8250串口驱动初始化 
相应的初始化函数为serial8250_init().代码如下: 
static int __init serial8250_init(void) 

     int ret, i; 
  
     if (nr_uarts > UART_NR) 
         nr_uarts = UART_NR; 
  
     printk(KERN_INFO "Serial: 8250/16550 driver $Revision: 1.90 $ " 
         "%d ports, IRQ sharing %sabled\n", nr_uarts, 
         share_irqs ? "en" : "dis"); 
  
     for (i = 0; i < NR_IRQS; i++) 
         spin_lock_init(&irq_lists[i].lock); 
  
     ret = uart_register_driver(&serial8250_reg); 
     if (ret) 
         goto out; 
  
     serial8250_isa_devs = platform_device_alloc("serial8250", 
                                PLAT8250_DEV_LEGACY); 
     if (!serial8250_isa_devs) { 
         ret = -ENOMEM; 
         goto unreg_uart_drv; 
     } 
  
     ret = platform_device_add(serial8250_isa_devs); 
     if (ret) 
         goto put_dev; 
  
     serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev); 
  
     ret = platform_driver_register(&serial8250_isa_driver);      if (ret == 0) 
         goto out; 
  
     platform_device_del(serial8250_isa_devs); 
 put_dev: 
     platform_device_put(serial8250_isa_devs); 
 unreg_uart_drv: 
     uart_unregister_driver(&serial8250_reg); 
 out: 
     return ret; 

这段代码涉及到的知识要求,如platform ,uart等我们在之前都已经做过详细的分析。这里不再重复。在
代码中UART_NR:表示串口的个数。这个参数在编译内核的时候可以自己配置,默认为32。 
我们按照代码中的流程一步一步进行研究。 
1:注册uart_driver.  
对应uart-driver的结构为serial8250_reg.定义如下: 
static struct uart_driver serial8250_reg = { 
     .owner             = THIS_MODULE, 
     .driver_name       = "serial", 
     .dev_name     = "ttyS", 
     .major             = TTY_MAJOR, 
     .minor             = 64, 
     .nr           = UART_NR, 
     .cons              = SERIAL8250_CONSOLE, 
}; 
TTY_MAJOR定义如下: 
#define TTY_MAJOR      4 
从上面可以看出。串口对应的设备节点为/dev/ ttyS0 ~ /dev/ ttyS0(UART_NR).设备节点号为(4。
64)起始的UART_NR个节点.. 
  
2:初始化并注册platform_device 
相关代码如下: 
serial8250_isa_devs = platform_device_alloc("serial8250",    PAT8250_DEV_LEGACY); 
platform_device_add(serial8250_isa_devs); 
可以看出。serial8250_isa_devs.->name为serial8250。这个参数是在匹配platform_device和
platform_driver使用的. 
  
3:为uart-driver添加 port. 
相关代码如下: 
serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev) 
跟进这个函数看一下: 
static void __init 
serial8250_register_ports(struct uart_driver *drv, struct device *dev) 
{      int i; 
  
     serial8250_isa_init_ports(); 
  
     for (i = 0; i < nr_uarts; i++) { 
         struct uart_8250_port *up = &serial8250_ports[i]; 
  
         up->port.dev = dev; 
         uart_add_one_port(drv, &up->port); 
     } 

在这里函数里,初始化了port.然后将挂添加到uart-driver中。我们还注意到。生成的deivce节点,在
sysfs中是位于platform_deivce对应目录的下面. 
serial8250_isa_init_ports()代码如下所示: 
static void __init serial8250_isa_init_ports(void) 

     struct uart_8250_port *up; 
     static int first = 1; 
     int i; 
  
     if (!first) 
         return; 
     first = 0; 
  
     for (i = 0; i < nr_uarts; i++) { 
         struct uart_8250_port *up = &serial8250_ports[i]; 
  
         up->port.line = i; 
         spin_lock_init(&up->port.lock); 
  
         init_timer(&up->timer); 
         up->timer.function = serial8250_timeout; 
  
         /* 
          * ALPHA_KLUDGE_MCR needs to be killed. 
          */ 
         up->mcr_mask = ~ALPHA_KLUDGE_MCR; 
         up->mcr_force = ALPHA_KLUDGE_MCR; 
  
         up->port.ops = &serial8250_pops; 
     } 
  
     for (i = 0, up = serial8250_ports; 
          i < ARRAY_SIZE(old_serial_port) && i < nr_uarts;           i++, up++) { 
         up->port.iobase   = old_serial_port[i].port; 
         up->port.irq      = irq_canonicalize(old_serial_port[i].irq); 
         up->port.uartclk  = old_serial_port[i].baud_base * 16; 
         up->port.flags    = old_serial_port[i].flags; 
         up->port.hub6     = old_serial_port[i].hub6; 
         up->port.membase  = old_serial_port[i].iomem_base; 
         up->port.iotype   = old_serial_port[i].io_type; 
         up->port.regshift = old_serial_port[i].iomem_reg_shift; 
         if (share_irqs) 
              up->port.flags |= UPF_SHARE_IRQ; 
     } 

在这里,我们关注一下注要成员的初始化。Uart_port 的各项操作位于serial8250_pops中.iobase irq
等成员是从old_serial_por这个结构中得来的,这个结构如下所示: 
static const struct old_serial_port old_serial_port[] = { 
     SERIAL_PORT_DFNS /* defined in asm/serial.h */ 

#define SERIAL_PORT_DFNS             \ 
     /* UART CLK   PORT IRQ     FLAGS        */              \ 
     { 0, BASE_BAUD, 0x3F8, 4, STD_COM_FLAGS },     /* ttyS0 */   \ 
     { 0, BASE_BAUD, 0x2F8, 3, STD_COM_FLAGS },     /* ttyS1 */   \ 
     { 0, BASE_BAUD, 0x3E8, 4, STD_COM_FLAGS },     /* ttyS2 */   \ 
     { 0, BASE_BAUD, 0x2E8, 3, STD_COM4_FLAGS },    /* ttyS3 */ 
从上面看到。前两项对应了com1 com2的各项参数。如寄存器首始地址,Irq号等。后面两项不太清楚。
  
在上面的代码中,我们看到了uart_port 各项成员的初始化。在后面很多操作中需要用到这个成员。我们
等分析相关部份的时候,再到这个地方来看相关成员的值。 
  
4:注册platform_driver 
相关代码如下: 
platform_driver_register(&serial8250_isa_driver); 
serial8250_isa_driver定义如下: 
static struct platform_driver serial8250_isa_driver = { 
     .probe        = serial8250_probe, 
     .remove       = __devexit_p(serial8250_remove), 
     .suspend = serial8250_suspend, 
     .resume       = serial8250_resume, 
     .driver       = { 
         .name    = "serial8250", 
         .owner   = THIS_MODULE, 
     }, 

  为了以后把分析集中到具体的驱动部份.我们先把这个platform_driver引会的事件讲述完. 
经过前面有关platform的分析我们知道.这个platform的name为” serial8250”.刚好跟前面注册的
platform_device相匹配.会调用platform_driver-> probe.在这里,对应的接口为: 
serial8250_probe().代码如下: 
static int __devinit serial8250_probe(struct platform_device *dev) 

     struct plat_serial8250_port *p = dev->dev.platform_data; 
     struct uart_port port; 
     int ret, i; 
  
     memset(&port, 0, sizeof(struct uart_port)); 
  
     for (i = 0; p && p->flags != 0; p++, i++) { 
         port.iobase        = p->iobase; 
         port.membase       = p->membase; 
         port.irq      = p->irq; 
         port.uartclk       = p->uartclk; 
         port.regshift      = p->regshift; 
         port.iotype        = p->iotype; 
         port.flags         = p->flags; 
         port.mapbase       = p->mapbase; 
         port.hub6     = p->hub6; 
         port.private_data  = p->private_data; 
         port.dev      = &dev->dev; 
         if (share_irqs) 
              port.flags |= UPF_SHARE_IRQ; 
         ret = serial8250_register_port(&port); 
         if (ret < 0) { 
              dev_err(&dev->dev, "unable to register port at index %d " 
                   "(IO%lx MEM%llx IRQ%d): %d\n", i, 
                   p->iobase, (unsigned long long)p->mapbase, 
                   p->irq, ret); 
         } 
     } 
     return 0; 

从上述代码可以看出.会将dev->dev.platform_data所代表的port添加到uart_driver中.这个
dev->dev.platform_data究竟代表什么.我们在看到的时候再来研究它. 
现在,我们把精力集中到uart_port的操作上. 
  
三:config_port过程 
在初始化uart_port的过程中,在以下代码片段: 
serial8250_isa_init_ports(void) 
{      …… 
     …… 
for (i = 0, up = serial8250_ports; 
          i < ARRAY_SIZE(old_serial_port) && i < nr_uarts; 
          i++, up++) { 
         up->port.iobase   = old_serial_port[i].port; 
         up->port.irq      = irq_canonicalize(old_serial_port[i].irq); 
         up->port.uartclk  = old_serial_port[i].baud_base * 16; 
         up->port.flags    = old_serial_port[i].flags; 
         up->port.hub6     = old_serial_port[i].hub6; 
         up->port.membase  = old_serial_port[i].iomem_base; 
         up->port.iotype   = old_serial_port[i].io_type; 
         up->port.regshift = old_serial_port[i].iomem_reg_shift; 
         if (share_irqs) 
              up->port.flags |= UPF_SHARE_IRQ; 
     } 

而old_serial_port又定义如下: 
static const struct old_serial_port old_serial_port[] = { 
     SERIAL_PORT_DFNS /* defined in asm/serial.h */ 
}; 
#define SERIAL_PORT_DFNS             \ 
     /* UART CLK   PORT IRQ     FLAGS        */              \ 
     { 0, BASE_BAUD, 0x3F8, 4, STD_COM_FLAGS },     /* ttyS0 */   \ 
     { 0, BASE_BAUD, 0x2F8, 3, STD_COM_FLAGS },     /* ttyS1 */   \ 
     { 0, BASE_BAUD, 0x3E8, 4, STD_COM_FLAGS },     /* ttyS2 */   \ 
     { 0, BASE_BAUD, 0x2E8, 3, STD_COM4_FLAGS },    /* ttyS3 */ 
  
由此可见.port->flags被定义成了STD_COM_FLAGS,定义如下: 
#ifdef CONFIG_SERIAL_DETECT_IRQ 
#define STD_COM_FLAGS (ASYNC_BOOT_AUTOCONF | ASYNC_SKIP_TEST | 
ASYNC_AUTO_IRQ) 
#define STD_COM4_FLAGS (ASYNC_BOOT_AUTOCONF | ASYNC_AUTO_IRQ) 
#else 
#define STD_COM_FLAGS (ASYNC_BOOT_AUTOCONF | ASYNC_SKIP_TEST) 
#define STD_COM4_FLAGS ASYNC_BOOT_AUTOCONF 
#endif 
从这里看到,不管是否自己探测IRQ,都会定义ASYNC_BOOT_AUTOCONF.这样,在
uart_add_one_port()的时候.就会进入到port->config_port来配置端口.在8250中,对应的接口为: 
serial8250_config_port().代码如下: 
static void serial8250_config_port(struct uart_port *port, int flags) 

     struct uart_8250_port *up = (struct uart_8250_port *)port; 
     int probeflags = PROBE_ANY;      int ret; 
  
     /* 
      * Find the region that we can probe for.  This in turn 
      * tells us whether we can probe for the type of port. 
      */ 
     ret = serial8250_request_std_resource(up); 
     if (ret < 0) 
         return; 
  
     ret = serial8250_request_rsa_resource(up); 
     if (ret < 0) 
         probeflags &= ~PROBE_RSA; 
  
     if (flags & UART_CONFIG_TYPE) 
         autoconfig(up, probeflags); 
     if (up->port.type != PORT_UNKNOWN && flags & UART_CONFIG_IRQ) 
         autoconfig_irq(up); 
  
     if (up->port.type != PORT_RSA && probeflags & PROBE_RSA) 
         serial8250_release_rsa_resource(up); 
     if (up->port.type == PORT_UNKNOWN) 
         serial8250_release_std_resource(up); 

serial8250_request_std_resource和serial8250_request_rsa_resource都是分配操作的端口.回
顾在前面的分析中.port的相关参数会从old_serial_port中取得.而old_serial_port中又没有定义
port->iotype 和port-> regshift.也就是说对应这两项全为0.而 
#define UPIO_PORT      (0) 
即表示是要操作I/O端口. 
自己阅读这两个函数代表.会发现在serial8250_request_rsa_resource()中是会返回失败的. 
另外,在uart_add_one_port()在进行端口匹配时,会先置flags为UART_CONFIG_TYPE. 
这样,在本次操作中, if (flags & UART_CONFIG_TYPE)是会满足的.相应的就会进入autoconfig(). 
代码如下,这段代码比较长,分段分析如下: 
static void autoconfig(struct uart_8250_port *up, unsigned int probeflags) 

     unsigned char status1, scratch, scratch2, scratch3; 
     unsigned char save_lcr, save_mcr; 
     unsigned long flags; 
  
     if (!up->port.iobase && !up->port.mapbase && !up->port.membase) 
         return; 
  
     DEBUG_AUTOCONF("ttyS%d: autoconf (0x%04x, 0x%p): ", 
              up->port.line, up->port.iobase, up->port.membase);   
     /* 
      * We really do need global IRQs disabled here - we're going to 
      * be frobbing the chips IRQ enable register to see if it exists. 
      */ 
     spin_lock_irqsave(&up->port.lock, flags); 
  
     up->capabilities = 0; 
     up->bugs = 0; 
  
if (!(up->port.flags & UPF_BUGGY_UART)) { 
  
          /* 
          * Do a simple existence test first; if we fail this, 
          * there's no point trying anything else. 
          * 
          * 0x80 is used as a nonsense port to prevent against 
          * false positives due to ISA bus float.  The 
          * assumption is that 0x80 is a non-existent port; 
          * which should be safe since include/asm/io.h also 
          * makes this assumption. 
          * 
          * Note: this is safe as long as MCR bit 4 is clear 
          * and the device is in "PC" mode. 
          */ 
  
          scratch = serial_inp(up, UART_IER); 
         serial_outp(up, UART_IER, 0); 
#ifdef __i386__ 
         outb(0xff, 0x080); 
#endif 
         /* 
          * Mask out IER[7:4] bits for test as some UARTs (e.g. TL 
          * 16C754B) allow only to modify them if an EFR bit is set. 
          */ 
         scratch2 = serial_inp(up, UART_IER) & 0x0f; 
         serial_outp(up, UART_IER, 0x0F); 
#ifdef __i386__ 
         outb(0, 0x080); 
#endif 
         scratch3 = serial_inp(up, UART_IER) & 0x0f; 
         serial_outp(up, UART_IER, scratch); 
         if (scratch2 != 0 || scratch3 != 0x0F) { 
              /*                * We failed; there's nothing here 
               */ 
              DEBUG_AUTOCONF("IER test failed (%02x, %02x) ", 
                         scratch2, scratch3); 
              goto out; 
         } 
     } 
在这里,先对8250是否存在做一个简单的判断.先将IER 中的值取得,这样可以在测试之后恢复IER 中的
值.然后往IER 中写放0.再将 IER中的值取出.又往IER中写入0xOF.然后再将IER中的值取出.最后将
IER中的值恢复到原值.这样就可以根据写入的值和读出的值是否相等来判断该寄存器是否存在. 
  
     save_mcr = serial_in(up, UART_MCR); 
     save_lcr = serial_in(up, UART_LCR); 
在这里,先将MCR和 LCR中的值取出.因为在后面的操作中会使用这两个寄存器.方便使用完了恢复 
     /* 
      * Check to see if a UART is really there.  Certain broken 
      * internal modems based on the Rockwell chipset fail this 
      * test, because they apparently don't implement the loopback 
      * test mode.  So this test is skipped on the COM 1 through 
      * COM 4 ports.  This *should* be safe, since no board 
      * manufacturer would be stupid enough to design a board 
      * that conflicts with COM 1-4 --- we hope! 
      */ 
  
     if (!(up->port.flags & UPF_SKIP_TEST)) { 
         serial_outp(up, UART_MCR, UART_MCR_LOOP | 0x0A); 
         status1 = serial_inp(up, UART_MSR) & 0xF0; 
         serial_outp(up, UART_MCR, save_mcr); 
         if (status1 != 0x90) { 
              DEBUG_AUTOCONF("LOOP test failed (%02x) ", 
                          status1); 
              goto out; 
         } 
     } 
在这里,将MCR的自检位置位,并允许向中断控制器产生中断.而且产生RTS信号.这样MSR寄存器应该可
以检测到这个信号.如果没有检测到.自测失败!MCR寄存器已经操作完了,恢复MCR 寄存器的原值. 
     /* 
      * We're pretty sure there's a port here.  Lets find out what 
      * type of port it is.  The IIR top two bits allows us to find 
      * out if it's 8250 or 16450, 16550, 16550A or later.  This 
      * determines what we test for next. 
      * 
      * We also initialise the EFR (if any) to zero for later.  The 
      * EFR occupies the same register location as the FCR and IIR.       */ 
     serial_outp(up, UART_LCR, 0xBF); 
     serial_outp(up, UART_EFR, 0); 
     serial_outp(up, UART_LCR, 0); 
  
     serial_outp(up, UART_FCR, UART_FCR_ENABLE_FIFO); 
     scratch = serial_in(up, UART_IIR) >> 6; 
  
     DEBUG_AUTOCONF("iir=%d ", scratch); 
  
     switch (scratch) { 
     case 0: 
         autoconfig_8250(up); 
         break; 
     case 1: 
         up->port.type = PORT_UNKNOWN; 
         break; 
     case 2: 
         up->port.type = PORT_16550; 
         break; 
     case 3: 
         autoconfig_16550a(up); 
         break; 
     } 
在这里,先允许使用FIFO寄存器,然后通过IIR寄存的高二位来判断芯片的类型 
  
#ifdef CONFIG_SERIAL_8250_RSA 
     /* 
      * Only probe for RSA ports if we got the region. 
      */ 
     if (up->port.type == PORT_16550A && probeflags & PROBE_RSA) { 
         int i; 
  
         for (i = 0 ; i < probe_rsa_count; ++i) { 
              if (probe_rsa[i] == up->port.iobase && 
                  __enable_rsa(up)) { 
                   up->port.type = PORT_RSA; 
                   break; 
              } 
         } 
     } 
#endif 
  
#ifdef CONFIG_SERIAL_8250_AU1X00      /* if access method is AU, it is a 16550 with a quirk */ 
     if (up->port.type == PORT_16550A && up->port.iotype == UPIO_AU) 
         up->bugs |= UART_BUG_NOMSR; 
#endif 
  
     serial_outp(up, UART_LCR, save_lcr); 
  
     if (up->capabilities != uart_config[up->port.type].flags) { 
         printk(KERN_WARNING 
                "ttyS%d: detected caps %08x should be %08x\n", 
              up->port.line, up->capabilities, 
              uart_config[up->port.type].flags); 
     } 
  
     up->port.fifosize = uart_config[up->port.type].fifo_size; 
     up->capabilities = uart_config[up->port.type].flags; 
     up->tx_loadsz = uart_config[up->port.type].tx_loadsz; 
  
     if (up->port.type == PORT_UNKNOWN) 
         goto out; 
  
     /* 
      * Reset the UART. 
      */ 
#ifdef CONFIG_SERIAL_8250_RSA 
     if (up->port.type == PORT_RSA) 
         serial_outp(up, UART_RSA_FRR, 0); 
#endif 
     serial_outp(up, UART_MCR, save_mcr); 
     serial8250_clear_fifos(up); 
     serial_in(up, UART_RX); 
     if (up->capabilities & UART_CAP_UUE) 
         serial_outp(up, UART_IER, UART_IER_UUE); 
     else 
         serial_outp(up, UART_IER, 0); 
  
 out: 
     spin_unlock_irqrestore(&up->port.lock, flags); 
     DEBUG_AUTOCONF("type=%s\n", uart_config[up->port.type].name); 

最后,复位串口控制器 
  
我们假设使用的是8250串口芯片.在芯片类型判断的时候就会进入autoconfig_8250().代码如下: 
static void autoconfig_8250(struct uart_8250_port *up) { 
     unsigned char scratch, status1, status2; 
  
     up->port.type = PORT_8250; 
  
     scratch = serial_in(up, UART_SCR); 
     serial_outp(up, UART_SCR, 0xa5); 
     status1 = serial_in(up, UART_SCR); 
     serial_outp(up, UART_SCR, 0x5a); 
     status2 = serial_in(up, UART_SCR); 
     serial_outp(up, UART_SCR, scratch); 
  
     if (status1 == 0xa5 && status2 == 0x5a) 
         up->port.type = PORT_16450; 

如果存在SCR 寄存器,则芯片是16450类型的.这不是我们需要研究的芯片. 
  
回到serial8250_config_port()中,代码片段如下所示: 
static void serial8250_config_port(struct uart_port *port, int flags) 

     …… 
     …… 
     if (flags & UART_CONFIG_TYPE) 
         autoconfig(up, probeflags); 
     if (up->port.type != PORT_UNKNOWN && flags & UART_CONFIG_IRQ) 
         autoconfig_irq(up); 
  
     if (up->port.type != PORT_RSA && probeflags & PROBE_RSA) 
         serial8250_release_rsa_resource(up); 
     if (up->port.type == PORT_UNKNOWN) 
         serial8250_release_std_resource(up); 

如果定义了自己控测IRQ号(CONFIG_SERIAL_8250_DETECT_IRQ).一般情况下,编译内核的时候一
般都将其赋值为CONFIG_SERIAL_8250_DETECT_IRQ = y.此时就会进入autoconfig_irq().代码如
下: 
static void autoconfig_irq(struct uart_8250_port *up) 

     unsigned char save_mcr, save_ier; 
     unsigned char save_ICP = 0; 
     unsigned int ICP = 0; 
     unsigned long irqs; 
     int irq; 
  
     if (up->port.flags & UPF_FOURPORT) {          ICP = (up->port.iobase & 0xfe0) | 0x1f; 
         save_ICP = inb_p(ICP); 
         outb_p(0x80, ICP); 
         (void) inb_p(ICP); 
     } 
  
     /* forget possible initially masked and pending IRQ */ 
     probe_irq_off(probe_irq_on()); 
     save_mcr = serial_inp(up, UART_MCR); 
     save_ier = serial_inp(up, UART_IER); 
     serial_outp(up, UART_MCR, UART_MCR_OUT1 | UART_MCR_OUT2); 
  
     irqs = probe_irq_on(); 
     serial_outp(up, UART_MCR, 0); 
     udelay(10); 
     if (up->port.flags & UPF_FOURPORT) { 
         serial_outp(up, UART_MCR, 
                  UART_MCR_DTR | UART_MCR_RTS); 
     } else { 
         serial_outp(up, UART_MCR, 
                  UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2); 
     } 
     serial_outp(up, UART_IER, 0x0f); /* enable all intrs */ 
     (void)serial_inp(up, UART_LSR); 
     (void)serial_inp(up, UART_RX); 
     (void)serial_inp(up, UART_IIR); 
     (void)serial_inp(up, UART_MSR); 
     serial_outp(up, UART_TX, 0xFF); 
     udelay(20); 
     irq = probe_irq_off(irqs); 
  
     serial_outp(up, UART_MCR, save_mcr); 
     serial_outp(up, UART_IER, save_ier); 
  
     if (up->port.flags & UPF_FOURPORT) 
         outb_p(save_ICP, ICP); 
  
     up->port.irq = (irq > 0) ? irq : 0; 

在上述代码的操作中,先将8250相关中断允许寄存器全打开.然后调用驱动使用的函数, 当它不得不探测
来决定哪个中断线被设备在使用. probe_irq_on()将中断暂时关掉,然后配置MCR寄存器使之发送DTR
和RTS.之后再用probe_irq_off()来检测IRQ号.如果检测成功,则值赋值给port->irq. 
进行到这里,conifg_port动作就完成了. 
经过这个config_port过程后,我们发现,并没有对serial8250_isa_devs->dev-> platform_data赋值,也就是说platform_driver->probe函数并无实质性的处理.在第一次for循环的时,就会因条件不符而
退出. 
四: startup操作 
在前面分析uart驱动架构的时候,曾说过,在open的时候,会调用port->startup().在本次分析的驱动中,
对应接口为serial8250_startup().分段分析如下: 
static int serial8250_startup(struct uart_port *port) 

     struct uart_8250_port *up = (struct uart_8250_port *)port; 
     unsigned long flags; 
     unsigned char lsr, iir; 
     int retval; 
  
     up->capabilities = uart_config[up->port.type].flags; 
     up->mcr = 0; 
  
     if (up->port.type == PORT_16C950) { 
         /* Wake up and initialize UART */ 
         up->acr = 0; 
         serial_outp(up, UART_LCR, 0xBF); 
         serial_outp(up, UART_EFR, UART_EFR_ECB); 
         serial_outp(up, UART_IER, 0); 
         serial_outp(up, UART_LCR, 0); 
         serial_icr_write(up, UART_CSR, 0); /* Reset the UART */ 
         serial_outp(up, UART_LCR, 0xBF); 
         serial_outp(up, UART_EFR, UART_EFR_ECB); 
         serial_outp(up, UART_LCR, 0); 
     } 
  
#ifdef CONFIG_SERIAL_8250_RSA 
     /* 
      * If this is an RSA port, see if we can kick it up to the 
      * higher speed clock. 
      */ 
     enable_rsa(up); 
#endif 
  
     /* 
      * Clear the FIFO buffers and disable them. 
      * (they will be reenabled in set_termios()) 
      */ 
     serial8250_clear_fifos(up); 
上面的代码都不是对应8250芯片的情况 
     /* 
      * Clear the interrupt registers.       */ 
     (void) serial_inp(up, UART_LSR); 
     (void) serial_inp(up, UART_RX); 
     (void) serial_inp(up, UART_IIR); 
     (void) serial_inp(up, UART_MSR); 
复位LSR,RX,IIR,MSR寄存器 
  
     /* 
      * At this point, there's no way the LSR could still be 0xff; 
      * if it is, then bail out, because there's likely no UART 
      * here. 
      */ 
     if (!(up->port.flags & UPF_BUGGY_UART) && 
         (serial_inp(up, UART_LSR) == 0xff)) { 
         printk("ttyS%d: LSR safety check engaged!\n", up->port.line); 
         return -ENODEV; 
     } 
若LSR寄存器中的值为0xFF.异常 
     /* 
      * For a XR16C850, we need to set the trigger levels 
      */ 
     if (up->port.type == PORT_16850) { 
         unsigned char fctr; 
  
         serial_outp(up, UART_LCR, 0xbf); 
  
         fctr = serial_inp(up, UART_FCTR) & ~(UART_FCTR_RX|UART_FCTR_TX); 
         serial_outp(up, UART_FCTR, fctr | UART_FCTR_TRGD | UART_FCTR_RX); 
         serial_outp(up, UART_TRG, UART_TRG_96); 
         serial_outp(up, UART_FCTR, fctr | UART_FCTR_TRGD | UART_FCTR_TX); 
         serial_outp(up, UART_TRG, UART_TRG_96); 
  
         serial_outp(up, UART_LCR, 0); 
     } 
16850系列芯片的处理,忽略 
  
     if (is_real_interrupt(up->port.irq)) { 
         /* 
          * Test for UARTs that do not reassert THRE when the 
          * transmitter is idle and the interrupt has already 
          * been cleared.  Real 16550s should always reassert 
          * this interrupt whenever the transmitter is idle and 
          * the interrupt is enabled.  Delays are necessary to 
          * allow register changes to become visible.           */ 
         spin_lock_irqsave(&up->port.lock, flags); 
  
         wait_for_xmitr(up, UART_LSR_THRE); 
         serial_out_sync(up, UART_IER, UART_IER_THRI); 
          udelay(1); /* allow THRE to set */ 
         serial_in(up, UART_IIR); 
         serial_out(up, UART_IER, 0); 
         serial_out_sync(up, UART_IER, UART_IER_THRI); 
         udelay(1); /* allow a working UART time to re-assert THRE */ 
         iir = serial_in(up, UART_IIR); 
         serial_out(up, UART_IER, 0); 
  
         spin_unlock_irqrestore(&up->port.lock, flags); 
  
         /* 
          * If the interrupt is not reasserted, setup a timer to 
          * kick the UART on a regular basis. 
          */ 
         if (iir & UART_IIR_NO_INT) { 
              pr_debug("ttyS%d - using backup timer\n", port->line); 
              up->timer.function = serial8250_backup_timeout; 
              up->timer.data = (unsigned long)up; 
              mod_timer(&up->timer, jiffies + 
                   poll_timeout(up->port.timeout) + HZ / 5); 
         } 
     } 
如果中断号有效,还要进一步判断这个中断号是否有效.具体操作为,先等待8250发送寄存器空.然后允许
发送中断空的中断.然后判断IIR寄存器是否收到中断.如果有没有收到中断,则说明这根中断线无效.只能
采用轮询的方式.关于轮询方式,我们在之后再以独立章节的形式给出分析 
  
     /* 
      * If the "interrupt" for this port doesn't correspond with any 
      * hardware interrupt, we use a timer-based system.  The original 
      * driver used to do this with IRQ0. 
      */ 
     if (!is_real_interrupt(up->port.irq)) { 
         up->timer.data = (unsigned long)up; 
         mod_timer(&up->timer, jiffies + poll_timeout(up->port.timeout)); 
     } else { 
         retval = serial_link_irq_chain(up); 
         if (retval) 
              return retval; 
     } 如果没有设置中断号,则采用轮询方式.如果中断后有效.流程转入serial_link_irq_chain().在这个里面.
会注册中断处理函数.  
     /* 
      * Now, initialize the UART 
      */ 
     serial_outp(up, UART_LCR, UART_LCR_WLEN8); 
  
     spin_lock_irqsave(&up->port.lock, flags); 
     if (up->port.flags & UPF_FOURPORT) { 
         if (!is_real_interrupt(up->port.irq)) 
              up->port.mctrl |= TIOCM_OUT1; 
     } else 
         /* 
          * Most PC uarts need OUT2 raised to enable interrupts. 
          */ 
         if (is_real_interrupt(up->port.irq)) 
              up->port.mctrl |= TIOCM_OUT2; 
  
     serial8250_set_mctrl(&up->port, up->port.mctrl); 
  
     /* 
      * Do a quick test to see if we receive an 
      * interrupt when we enable the TX irq. 
      */ 
     serial_outp(up, UART_IER, UART_IER_THRI); 
     lsr = serial_in(up, UART_LSR); 
     iir = serial_in(up, UART_IIR); 
     serial_outp(up, UART_IER, 0); 
  
     if (lsr & UART_LSR_TEMT && iir & UART_IIR_NO_INT) { 
         if (!(up->bugs & UART_BUG_TXEN)) { 
              up->bugs |= UART_BUG_TXEN; 
              pr_debug("ttyS%d - enabling bad tx status workarounds\n", 
                    port->line); 
         } 
     } else { 
         up->bugs &= ~UART_BUG_TXEN; 
     } 
  
     spin_unlock_irqrestore(&up->port.lock, flags); 
  
     /* 
      * Clear the interrupt registers again for luck, and clear the 
      * saved flags to avoid getting false values from polling       * routines or the previous session. 
      */ 
     serial_inp(up, UART_LSR); 
     serial_inp(up, UART_RX); 
     serial_inp(up, UART_IIR); 
     serial_inp(up, UART_MSR); 
     up->lsr_saved_flags = 0; 
     up->msr_saved_flags = 0; 
  
     /* 
      * Finally, enable interrupts.  Note: Modem status interrupts 
      * are set via set_termios(), which will be occurring imminently 
      * anyway, so we don't enable them here. 
      */ 
     up->ier = UART_IER_RLSI | UART_IER_RDI; 
     serial_outp(up, UART_IER, up->ier); 
  
     if (up->port.flags & UPF_FOURPORT) { 
         unsigned int icp; 
         /* 
          * Enable interrupts on the AST Fourport board 
          */ 
         icp = (up->port.iobase & 0xfe0) | 0x01f; 
         outb_p(0x80, icp); 
         (void) inb_p(icp); 
     } 
  
     return 0; 

最后,就是对8250芯片的初始化了.包括:在LCR中设置数据格式,在MCR中设置允许中断到8259.在IER
中设置相关允许位. 
另外在open 的时候,还会调用port-> enable_ms ()接口,在本例中对应为: 
serial8250_enable_ms().代码如下: 
static void serial8250_enable_ms(struct uart_port *port) 

     struct uart_8250_port *up = (struct uart_8250_port *)port; 
  
     /* no MSR capabilities */ 
     if (up->bugs & UART_BUG_NOMSR) 
         return; 
  
     up->ier |= UART_IER_MSI; 
     serial_out(up, UART_IER, up->ier); 
} 即允许moden中断 
  
五:数据发送的操作 
在uart驱动架构中分析过,在发送数据的时候,uart层先会将数据放入circ_buffer.最后再调用port-> 
start_tx(). 
在这里,这个接口对应为serial8250_start_tx().代码如下: 
static void serial8250_start_tx(struct uart_port *port) 

     struct uart_8250_port *up = (struct uart_8250_port *)port; 
  
     if (!(up->ier & UART_IER_THRI)) { 
         up->ier |= UART_IER_THRI; 
         serial_out(up, UART_IER, up->ier); 
  
         if (up->bugs & UART_BUG_TXEN) { 
              unsigned char lsr, iir; 
              lsr = serial_in(up, UART_LSR); 
              up->lsr_saved_flags |= lsr & LSR_SAVE_FLAGS; 
              iir = serial_in(up, UART_IIR) & 0x0f; 
              if ((up->port.type == PORT_RM9000) ? 
                   (lsr & UART_LSR_THRE && 
                   (iir == UART_IIR_NO_INT || iir == UART_IIR_THRI)) : 
                   (lsr & UART_LSR_TEMT && iir & UART_IIR_NO_INT)) 
                   transmit_chars(up); 
         } 
     } 
  
     /* 
      * Re-enable the transmitter if we disabled it. 
      */ 
     if (up->port.type == PORT_16C950 && up->acr & UART_ACR_TXDIS) { 
         up->acr &= ~UART_ACR_TXDIS; 
         serial_icr_write(up, UART_ACR, up->acr); 
     } 

这个函数非常简单.如果没有定义发送空中断.则在IER中打开这个中断.关于TXEN上的bug修复和
16C950类型的芯片不是我们所关注的部份. 
那,这里只是打开了这个中断.写数据到芯片的这个过程是在什么地方完成的呢? 
是在中断处理中.如果是发送空的中断,就将circ buffer中的数据写出发送寄存器.跟踪一下代码.中断处理
函数为serial8250_interrupt(). 
static irqreturn_t serial8250_interrupt(int irq, void *dev_id) 

     struct irq_info *i = dev_id; 
     struct list_head *l, *end = NULL;      int pass_counter = 0, handled = 0; 
  
     DEBUG_INTR("serial8250_interrupt(%d)...", irq); 
  
     spin_lock(&i->lock); 
  
     l = i->head; 
     do { 
         struct uart_8250_port *up; 
         unsigned int iir; 
  
         up = list_entry(l, struct uart_8250_port, list); 
  
         iir = serial_in(up, UART_IIR); 
         if (!(iir & UART_IIR_NO_INT)) { 
              serial8250_handle_port(up); 
  
              handled = 1; 
  
              end = NULL; 
         } else if (up->port.iotype == UPIO_DWAPB && 
                (iir & UART_IIR_BUSY) == UART_IIR_BUSY) { 
              /* The DesignWare APB UART has an Busy Detect (0x07) 
               * interrupt meaning an LCR write attempt occured while the 
               * UART was busy. The interrupt must be cleared by reading 
               * the UART status register (USR) and the LCR re-written. */ 
              unsigned int status; 
              status = *(volatile u32 *)up->port.private_data; 
              serial_out(up, UART_LCR, up->lcr); 
  
              handled = 1; 
  
              end = NULL; 
         } else if (end == NULL) 
              end = l; 
  
         l = l->next; 
  
         if (l == i->head && pass_counter++ > PASS_LIMIT) { 
              /* If we hit this, we're dead. */ 
              printk(KERN_ERR "serial8250: too much work for " 
                   "irq%d\n", irq); 
              break; 
         }      } while (l != end); 
  
     spin_unlock(&i->lock); 
  
     DEBUG_INTR("end.\n"); 
  
     return IRQ_RETVAL(handled); 

这里可能有个疑问的地方,挂在这个链表上的到底是什么.这我们要从serial_link_irq_chain()来说起.该
函数代码如下: 
static int serial_link_irq_chain(struct uart_8250_port *up) 

     struct irq_info *i = irq_lists + up->port.irq; 
     int ret, irq_flags = up->port.flags & UPF_SHARE_IRQ ? IRQF_SHARED : 0; 
  
     spin_lock_irq(&i->lock); 
  
     if (i->head) { 
         list_add(&up->list, i->head); 
         spin_unlock_irq(&i->lock); 
  
         ret = 0; 
     } else { 
         INIT_LIST_HEAD(&up->list); 
         i->head = &up->list; 
         spin_unlock_irq(&i->lock); 
  
         ret = request_irq(up->port.irq, serial8250_interrupt, 
                     irq_flags, "serial", i); 
         if (ret < 0) 
              serial_do_unlink(i, up); 
     } 
  
     return ret; 

从这里看到,注册中断处理函数的参数i 就是对应irq_lists + up->port.irq.即对应在irq_lists数组中的
port->irq项.随后,将注册的uart_8250_port添加到了这个链表. 
奇怪了,为什么要这么做了?我们返回old_serial_port的定义看看: 
static const struct old_serial_port old_serial_port[] = { 
     SERIAL_PORT_DFNS /* defined in asm/serial.h */ 
}; 
#define SERIAL_PORT_DFNS             \ 
     /* UART CLK   PORT IRQ     FLAGS        */              \ 
     { 0, BASE_BAUD, 0x3F8, 4, STD_COM_FLAGS },     /* ttyS0 */   \      { 0, BASE_BAUD, 0x2F8, 3, STD_COM_FLAGS },     /* ttyS1 */   \ 
     { 0, BASE_BAUD, 0x3E8, 4, STD_COM_FLAGS },     /* ttyS2 */   \ 
     { 0, BASE_BAUD, 0x2E8, 3, STD_COM4_FLAGS },    /* ttyS3 */ 
在这里,注意到同一个IRQ号会对应两个port. IRQ中发生中断的时候,怎么去判断是哪一个port所引起
的.当然方法有多种多样.在这里,8250驱动的作者是将不同的port链入到IRQ对应的链表来完成的.这样,
如果IRQ产生了中断了,就判断挂在该链表中的port,看中断是否由它产生. 
  
经过这个分析之后,我们应该很清楚serial8250_interrupt()中的处理流程了.对应产生IRQ的port,流程
会转入serial8250_handle_port()中.代码如下: 
static inline void 
serial8250_handle_port(struct uart_8250_port *up) 

     unsigned int status; 
     unsigned long flags; 
  
     spin_lock_irqsave(&up->port.lock, flags); 
  
     status = serial_inp(up, UART_LSR); 
  
     DEBUG_INTR("status = %x...", status); 
  
     if (status & UART_LSR_DR) 
         receive_chars(up, &status); 
     check_modem_status(up); 
     if (status & UART_LSR_THRE) 
         transmit_chars(up); 
  
     spin_unlock_irqrestore(&up->port.lock, flags); 

对于产生中断的情况下,判断发送缓存区是否为空,如果为空,就可以发送数据了.对应的处理在
transmit_chars(up).如果接收缓存区满,就那接收数据,这是在receive_chars()中处理的.对于接收数
据,我们在下一节再分析. 
transmit_chars()代码如下: 
static void transmit_chars(struct uart_8250_port *up) 

     struct circ_buf *xmit = &up->port.info->xmit; 
     int count; 
  
     if (up->port.x_char) { 
         serial_outp(up, UART_TX, up->port.x_char); 
         up->port.icount.tx++; 
         up->port.x_char = 0; 
         return; 
     }      if (uart_tx_stopped(&up->port)) { 
         serial8250_stop_tx(&up->port); 
         return; 
     } 
     if (uart_circ_empty(xmit)) { 
         __stop_tx(up); 
         return; 
     } 
  
     count = up->tx_loadsz; 
     do { 
         serial_out(up, UART_TX, xmit->buf[xmit->tail]); 
         xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); 
         up->port.icount.tx++; 
         if (uart_circ_empty(xmit)) 
              break; 
     } while (--count > 0); 
  
     if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) 
         uart_write_wakeup(&up->port); 
  
     DEBUG_INTR("THRE..."); 
  
     if (uart_circ_empty(xmit)) 
         __stop_tx(up); 

从上面的代码看出.会从xmit中取出数据,然后将其写入到发送寄存器中.特别的,在8250芯片的情况下, 
up->tx_loadsz 等于1.也就是说,一次只能传送1个字节. 
如果缓存区的数据传输玩了之后,就会调用__stop_tx().代码如下: 
static inline void __stop_tx(struct uart_8250_port *p) 

     if (p->ier & UART_IER_THRI) { 
         p->ier &= ~UART_IER_THRI; 
         serial_out(p, UART_IER, p->ier); 
     } 

对应的,在IER 中,将发送缓存区空的中断关掉. 
  
六:数据读取操作 
在前面的tty 驱动架构分析中,曾说过,在tty_driver中并末提供read接口.上层的read操作是直接到
ldsic的缓存区中读数据的.那ldsic的数据是怎么送入进去的呢?继续看中断处理中的数据接收流程.即为: 
receive_chars().代码片段如下: 
static void 
receive_chars(struct uart_8250_port *up, unsigned int *status) { 
     …… 
     …… 
     uart_insert_char(&up->port, lsr, UART_LSR_OE, ch, flag); 

最后流据会转入uart_inset_char().这个函数是uart层提供的一个接口,代码如下: 
static inline void 
uart_insert_char(struct uart_port *port, unsigned int status, 
          unsigned int overrun, unsigned int ch, unsigned int flag) 

     struct tty_struct *tty = port->info->tty; 
  
     if ((status & port->ignore_status_mask & ~overrun) == 0) 
         tty_insert_flip_char(tty, ch, flag); 
  
     /* 
      * Overrun is special.  Since it's reported immediately, 
      * it doesn't affect the current character. 
      */ 
     if (status & ~port->ignore_status_mask & overrun) 
         tty_insert_flip_char(tty, 0, TTY_OVERRUN); 

Tty_insert_filp()函数的代码我们在之前已经分析过,这里不再赘述.就这样,数据就直接交给了ldisc. 
  
七:轮询操作 
在前面已经分析到,如果没有定义irq或者没有控测到irq号,就会采用轮询.在代码,采用定时器的方式.去判
断是否有数据到来,或者将数据写入8250.定时器对应的运行函数为serial8250_backup_timeout().代
码如下: 
static void serial8250_backup_timeout(unsigned long data) 

     struct uart_8250_port *up = (struct uart_8250_port *)data; 
     unsigned int iir, ier = 0, lsr; 
     unsigned long flags; 
  
     /* 
      * Must disable interrupts or else we risk racing with the interrupt 
      * based handler. 
      */ 
     if (is_real_interrupt(up->port.irq)) { 
         ier = serial_in(up, UART_IER); 
         serial_out(up, UART_IER, 0); 
     } 
  
     iir = serial_in(up, UART_IIR);   
     /* 
      * This should be a safe test for anyone who doesn't trust the 
      * IIR bits on their UART, but it's specifically designed for 
      * the "Diva" UART used on the management processor on many HP 
      * ia64 and parisc boxes. 
      */ 
     spin_lock_irqsave(&up->port.lock, flags); 
     lsr = serial_in(up, UART_LSR); 
     up->lsr_saved_flags |= lsr & LSR_SAVE_FLAGS; 
     spin_unlock_irqrestore(&up->port.lock, flags); 
     if ((iir & UART_IIR_NO_INT) && (up->ier & UART_IER_THRI) && 
         (!uart_circ_empty(&up->port.info->xmit) || up->port.x_char) && 
         (lsr & UART_LSR_THRE)) { 
         iir &= ~(UART_IIR_ID | UART_IIR_NO_INT); 
         iir |= UART_IIR_THRI; 
     } 
  
     if (!(iir & UART_IIR_NO_INT)) 
         serial8250_handle_port(up); 
  
     if (is_real_interrupt(up->port.irq)) 
         serial_out(up, UART_IER, ier); 
  
     /* Standard timer interval plus 0.2s to keep the port running */ 
     mod_timer(&up->timer, 
         jiffies + poll_timeout(up->port.timeout) + HZ / 5); 

如果IRQ线有效,先在IER中禁用全部中断.等定时器处理函数处理完后,再恢复IER中的内容.这样主要是
为了防止会产生发送缓存区空的中断. 
流程最后还是会转入到serial8250_handle_port()中.这个函数我们在上面已经分析过了. 
  
八:小结 
分析完了这个驱动,我们可以看到.专业的开发人员思维是多么的缜密.真是滴水不漏.该代码里有很多非常
精彩的处理,需要细细揣摩. 
  
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/wh8_2011/article/details/50183565

智能推荐

直播源码搭建教程,autojs颜色渐变效果_autojs推流直播-程序员宅基地

文章浏览阅读226次。直播源码搭建教程,autojs颜色渐变效果实现的相关代码布局ui.layout( <vertical gravity="center"> <button id="渐变色">渐变色</button> <View id="view1" w="300dp" h="300dp"></View> </vertical>); 初始化变量let view;view = ui.view1;let col_autojs推流直播

用FPGA设计软件无线电和调制解调器-程序员宅基地

文章浏览阅读2.5k次。本文以16-QAM RF发射数据泵的设计为例,介绍利用FPGA设计数字滤波器的技巧和器件选择方法,说明执行分布式计算时FPGA比DSP的优越之处。所有数字逻辑的基本结构16-QAM调制器编码和码元映射平方根升余弦滤波器设计技巧5 MHz载波

P123.30(求杨辉三角第39行第19列的数。)-程序员宅基地

文章浏览阅读1.2k次。#includeint main(){ long double i,j,x,y,a,b,c,p=1,m=1,n=1; printf("请输入行数x= 列数y= "); scanf("%lf%lf",&x,&y); for(a=1;a

noip2013 火柴排队 (离散化+归并排序求逆序对数)_noip 火柴排队 离散化-程序员宅基地

文章浏览阅读1.3k次。P1842火柴排队未递交标签:NOIP提高组2013描述涵涵有两盒火柴,每盒装有 n 根火柴,每根火柴都有一个高度。现在将每盒中的火柴各自排成一列,同一列火柴的高度互不相同,两列火柴之间的距离定义为:∑i=1n(ai−bi)2,其中 ai 表示第一列火柴中第 i 个火柴的高度,bi 表示第二列火柴中第 i 个火柴的高度。每列火柴中相邻两根_noip 火柴排队 离散化

Action Bar_there is no action mapped for namespace [/] and ac-程序员宅基地

文章浏览阅读705次。ActionBar 是标识用户位置的窗口功能,并提供用户操作和导航模式。在应用程序中使用ActionBar能够优雅的适应不同屏幕配置,为你的用户提供一个熟悉的界面。注:ActionBar包括了[1]app icon,[2]two action items,和[3]action overflow。Actionbar提供几个关键功能:1.提供了一个专门的空间给你的应用程_there is no action mapped for namespace [/] and action name [gettag] associated with context path [/students_lxx]. - [unknown location]

解决vue-i18n Cannot translate the value of keypath 'stateList.'. Use the value of keypath as default.-程序员宅基地

文章浏览阅读1.3w次,点赞5次,收藏3次。项目中使用了i18n来解决国际化问题。碰到了警告:Cannot translate the value of keypath ‘stateList.’. Use the value of keypath as default.百度了下,发现大多数都是配置i18n不显示警告的方式来解决,这不是掩耳盗铃么。检查了下代码,发现其实是个很简单的问题:如图,我在select中使用了i18n的$t函数..._use the value of keypath as default.

随便推点

NOIP2018普及组T1(标题统计)题解_标题统计(noip 2018 pjt1)-程序员宅基地

文章浏览阅读649次,点赞4次,收藏4次。经典NOIP题目~_标题统计(noip 2018 pjt1)

C语言生成时间戳_c生成时间戳-程序员宅基地

文章浏览阅读4.9k次,点赞2次,收藏4次。#include <stdio.h>#include <string.h>#include <time.h>void get_timestamp(char* timestamp){ time_t seconds = time(NULL); //The function time(NULL) returns the time since the Epo..._c生成时间戳

单链表的快速排序-程序员宅基地

文章浏览阅读568次。今天在学习《程序员使用算法》时,看到了单链表快排序这一节。初看时感觉程序有很大的问题,但是细细品味之后却发现程序设计的极为巧妙,同时又深感自己C语言指针知识之不牢固,特别是指针的指针方面的知识。单链表的快排序和数组的快排序基本思想相同,同样是基于划分,但是又有很大的不同:单链表不支持基于下标的访问。故书中把待排序的链表拆分为2个子链表。为了简单起见,选择链表的第一个节点作为基准,然后进行比较,

前端表单_前端 单子-程序员宅基地

文章浏览阅读302次。前端表单 表单:包含表单元素的区域表单元素:允许用户在表单中输入信息的元素(文本域,下拉列表,单选框,复选框)输入:多数情况用到的是输入标签,输入类型由定义。①文本域: 姓: 名: ②单选按钮:当用户从若干给定的选择中选择其一时,就会用到单选框。 Male Female③复选框I have _前端 单子

鸿蒙软件容量大吗,华为鸿蒙操作系统正式发布,它和安卓最大的区别是什么?...-程序员宅基地

文章浏览阅读576次。6月2号,也就是今天华为鸿蒙操作系统就要正式发布,将全面开源和多机型同步推送。根据已有的资料来看,鸿蒙在系统交互逻辑。ui 设计上和华为原来的并没有太大的区别,并且兼容所有的安卓app。那肯定就有人问了,既然这样,那和安卓有什么区别呢?只不过换个壳子罢了,将安卓开源代码下载下来,做个魔改。其实这种说法相当片面,因为安卓的代码并不都是谷歌写的,绝大部分来自于开源社区。鸿蒙当然也会吸收开源社区的优秀代..._华为鸿蒙软件太大

推荐文章

热门文章

相关标签