Linux串口驱动分析及移植-程序员宅基地

技术标签: 嵌入式Linux驱动开发  

1 概述

    在Linux中,常碰到“控制台”、“终端”、“console”、“tty”等术语,也会经常使用一些设备文件:/dev/console、/dev/ttyS0、/dev/ttyUSB0等。tty是Teletype的缩写,Teletype是最早出现的一种终端设备,Linux通常使用tty来表示“终端”,终端设备的种类有很多,比如串行终端、键盘和显示器、通过网络实现的终端等。
    UART与USART都是单片机上的串口通信, UART(universal asynchronous receiver and transmitter)通用异步收/发器,USART(universal synchronous asynchronous receiver and transmitter)通用同步/异步收/发器。从名字上可以看出,USART在UART基础上增加了同步功能,即USART是UART的增强型。当我们使用USART在异步通信的时候,它与UART没有什么区别,但是用在同步通信的时候,区别就很明显了:同步通信需要时钟来触发数据传输,也就是说USART相对UART的区别之一就是能提供主动时钟。
    串口属于终端设备,它的驱动程序并不仅仅是简单的初始化硬件、接收/发送数据,在基本硬件操作的基础上,还增加了很多软件功能,是一个多层次的驱动程序。

2 串口驱动程序层次结构

在这里插入图片描述
    串口驱动程序层次结构如图2.1所示。简单来说,串口驱动程序层次结构可以分为两层,下层为串口驱动层,它直接与硬件相接触,需要填充一个 struct uart_ops 的结构体。上层为tty层,包括tty核心层及线路规程,它们各自都有一个 ops 结构体,用户空间可以通过tty注册的字符设备节点来访问串口设备。如图2.1所示,涉及到了4个 ops 结构体,层层进行跳转。
    下面以Microchip SAMA5D4处理器的UART控制器来进行代码分析。

2.1 串口驱动注册-uart_register_driver

    路径: \linux-at91\drivers\tty\serial\atmel_serial.c。对于Atmel平台,是这样来注册串口驱动的,首先分配一个struct uart_driver 简单填充,并调用uart_register_driver 注册到内核中去。
在这里插入图片描述
在这里插入图片描述
struct uart_driver 中,只是填充了一些名字、设备号等信息,这些都是不涉及底层硬件访问的。
在这里插入图片描述
在这里插入图片描述
下面看一下完整的 uart_driver 结构。
在这里插入图片描述
    在struct uart_driver atmel_uart结构体中,有两个成员未被赋值,分别是tty_driver和uart_state。对于tty_driver,代表的是上层,它会在 uart_ register_driver 的过程中赋值。而uart_state ,则代表下层,uart_state也会在uart_ register_driver 的过程中分配空间,但是它里面真正设置硬件相关的东西是 uart_state->uart_port ,这个uart_port 是需要从其它地方调用 uart_add_one_port 来添加的。

 下层(串口驱动层)

首先,认识几个结构体。
1) 结构体:uart_state
路径:\linux-at91\linux-at91\linux-at91\include\linux\serial_core.h

/*
 * This is the state information which is persistent across opens.
 * 打开时下的状态信息
 */
struct uart_state {
    
    struct tty_port     port;

    enum uart_pm_state  pm_state;
    struct circ_buf     xmit;

    atomic_t        refcount;
    wait_queue_head_t   remove_wait;
    struct uart_port    *uart_port; // 对应于一个串口设备
};

    在注册 driver 时,会根据 uart_driver->nr 来申请 nr 个 uart_state 空间,用来存放驱动所支持的串口(端口)物理信息。

2) 结构体:uart_port
    
路径:\linux-at91\linux-at91\linux-at91\include\linux\serial_core.h

struct uart_port {
    
    spinlock_t      lock;           /* port lock */
    unsigned long       iobase; /* in/out[bwl] io端口基地址(物理) */
    unsigned char __iomem   *membase;       /* read/write[bwl] */
    unsigned int        (*serial_in)(struct uart_port *, int);
    void            (*serial_out)(struct uart_port *, int, int);
    void            (*set_termios)(struct uart_port *,
                               struct ktermios *new,
                               struct ktermios *old);
    unsigned int        (*get_mctrl)(struct uart_port *);
    void            (*set_mctrl)(struct uart_port *, unsigned int);
    int         (*startup)(struct uart_port *port);
    void            (*shutdown)(struct uart_port *port);
    void            (*throttle)(struct uart_port *port);
    void            (*unthrottle)(struct uart_port *port);
    int         (*handle_irq)(struct uart_port *);
    void            (*pm)(struct uart_port *, unsigned int state,
                      unsigned int old);
    void            (*handle_break)(struct uart_port *);
    int         (*rs485_config)(struct uart_port *,
                        struct serial_rs485 *rs485);
    unsigned int        irq;            /* irq number */
    unsigned long       irqflags;       /* irq flags  */
    unsigned int        uartclk;        /* base uart clock */
    unsigned int        fifosize;       /* tx fifo size */
    unsigned char       x_char;         /* xon/xoff char */
    unsigned char       regshift;       /* reg offset shift */
    unsigned char       iotype;         /* io access style */
    unsigned char       unused1;

#define UPIO_PORT       (SERIAL_IO_PORT)    /* 8b I/O port access */
#define UPIO_HUB6       (SERIAL_IO_HUB6)    /* Hub6 ISA card */
#define UPIO_MEM        (SERIAL_IO_MEM)     /* driver-specific */
#define UPIO_MEM32      (SERIAL_IO_MEM32)   /* 32b little endian */
#define UPIO_AU         (SERIAL_IO_AU)      /* Au1x00 and RT288x type IO */
#define UPIO_TSI        (SERIAL_IO_TSI)     /* Tsi108/109 type IO */
#define UPIO_MEM32BE        (SERIAL_IO_MEM32BE) /* 32b big endian */
#define UPIO_MEM16      (SERIAL_IO_MEM16)   /* 16b little endian */

    unsigned int        read_status_mask;   /* driver specific */
    unsigned int        ignore_status_mask; /* driver specific */
    struct uart_state   *state;         /* pointer to parent state */
    struct uart_icount  icount;         /* statistics */

    struct console      *cons;          /* struct console, if any */
#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(SUPPORT_SYSRQ)
    unsigned long       sysrq;          /* sysrq timeout */
#endif

。。。
。。。

    int         hw_stopped;     /* sw-assisted CTS flow state */
    unsigned int        mctrl;          /* current modem ctrl settings */
    unsigned int        timeout;        /* character-based timeout */
    unsigned int        type;           /* port type */
    const struct uart_ops   *ops;
    unsigned int        custom_divisor;
    unsigned int        line;           /* port index */
    unsigned int        minor;
    resource_size_t     mapbase;        /* for ioremap */
    resource_size_t     mapsize;
    struct device       *dev;           /* parent device */
    unsigned char       hub6;           /* this should be in the 8250 driver */
    unsigned char       suspended;
    unsigned char       irq_wake;
    unsigned char       unused[2];
    struct attribute_group  *attr_group;        /* port specific attributes */
    const struct attribute_group **tty_groups;  /* all attributes (serial core use only) */
    struct serial_rs485     rs485;
    void            *private_data;      /* generic platform data pointer */
};

    struct uart_port,这个结构体对应于一个串口设备,如果平台有3个串口那么就需要填充3个uart_port ,并且通过 uart_add_one_port 添加到 uart_driver->uart_state->uart_port 中去。当然 uart_driver 有多个 uart_state ,每个 uart_state 有一个 uart_port。在 uart_port 里还有一个非常重要的成员 struct uart_ops *ops ,一般芯片厂家都写好了,只需要稍作修改。
    下面是struct uart_ops 结构体。
在这里插入图片描述

 上层(tty核心层)
    tty 核心层要从 uart_register_ driver 来看起,因为 tty_driver 是在注册过程中构建的,顺便了解注册过程。

/**
 *  uart_register_driver - register a driver with the uart core layer
 *  @drv: low level driver structure
 *
 *  Register a uart driver with the core driver.  We in turn register
 *  with the tty layer, and initialise the core driver per-port state.
 *
 *  We have a proc file in /proc/tty/driver which is named after the
 *  normal driver.
 *
 *  drv->port should be NULL, and the per-port structures should be
 *  registered using uart_add_one_port after this call has succeeded.
 */
int uart_register_driver(struct uart_driver *drv)
{
    
    struct tty_driver *normal;
    int i, retval;

    BUG_ON(drv->state);

    /*
     * Maybe we should be using a slab cache for this, especially if
     * we have a large number of ports to handle.
     */
/* 根据driver支持的最大设备数,申请n个 uart_state 空间,每一个 uart_state 都有一个uart_port */
    drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
    if (!drv->state)
        goto out;
/* tty层:分配一个 tty_driver ,并将drv->tty_driver 指向它 */
    normal = alloc_tty_driver(drv->nr);
    if (!normal)
        goto out_kfree;

    drv->tty_driver = normal;
/* 对 tty_driver 进行设置 */
    normal->driver_name = drv->driver_name;
    normal->name        = drv->dev_name;
    normal->major       = drv->major;
    normal->minor_start = drv->minor;
    normal->type        = TTY_DRIVER_TYPE_SERIAL;
    normal->subtype     = SERIAL_TYPE_NORMAL;
    normal->init_termios    = tty_std_termios;
    normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
    normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
    normal->flags       = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
    normal->driver_state    = drv;
    tty_set_operations(normal, &uart_ops);

    /*
     * Initialise the UART state(s).
     */
    for (i = 0; i < drv->nr; i++) {
    
        struct uart_state *state = drv->state + i;
        struct tty_port *port = &state->port;

        tty_port_init(port);
        port->ops = &uart_port_ops;
        //port->close_delay     = HZ / 2; /* .5 seconds */
        //port->closing_wait    = 30 * HZ;/* 30 seconds */
    }
	/* tty层:注册 driver->tty_driver */
    retval = tty_register_driver(normal);
    if (retval >= 0)
        return retval;

    for (i = 0; i < drv->nr; i++)
        tty_port_destroy(&drv->state[i].port);
    put_tty_driver(normal);
out_kfree:
    kfree(drv->state);
out:
    return -ENOMEM;
}

uart_register_ driver 注册过程干了哪些事
     1)根据driver支持的最大设备数,申请n个 uart_state 空间,每一个 uart_state 都有一个 uart_port ;
     2)分配一个 tty_driver ,并将drv->tty_driver 指向它;
     3)对 tty_driver 进行设置,其中包括默认波特率、校验方式等,还有一个重要的ops ,uart_ops ,它是tty核心与串口驱动通信的接口;
     4)初始化每一个 uart_state ;
     5)注册 tty_driver 。

    注册 uart_driver 实际上是注册 tty_driver,因此与用户空间打交道的工作完全交给了 tty_driver ,而且这一部分都是内核实现好的,基本不需要修改,了解一下工作原理即可。

这里介绍下uart_ops结构体。

static const struct tty_operations uart_ops = {
    
    .open       = uart_open,
    .close      = uart_close,
    .write      = uart_write,
    .put_char   = uart_put_char,
    .flush_chars    = uart_flush_chars,
    .write_room = uart_write_room,
    .chars_in_buffer= uart_chars_in_buffer,
    .flush_buffer   = uart_flush_buffer,
    .ioctl      = uart_ioctl,
    .throttle   = uart_throttle,
    .unthrottle = uart_unthrottle,
    .send_xchar = uart_send_xchar,
    .set_termios    = uart_set_termios,
    .set_ldisc  = uart_set_ldisc,
    .stop       = uart_stop,
    .start      = uart_start,
    .hangup     = uart_hangup,
    .break_ctl  = uart_break_ctl,
    .wait_until_sent= uart_wait_until_sent,
#ifdef CONFIG_PROC_FS
    .proc_fops  = &uart_proc_fops,
#endif
    .tiocmget   = uart_tiocmget,
    .tiocmset   = uart_tiocmset,
    .get_icount = uart_get_icount,
#ifdef CONFIG_CONSOLE_POLL
    .poll_init  = uart_poll_init,
    .poll_get_char  = uart_poll_get_char,
    .poll_put_char  = uart_poll_put_char,
#endif
};

这个是 tty 核心层的 ops ,简单一看,后面分析调用关系时,我们再来看具体的里边的函数。
下面来看 tty_driver 的注册,tty_register_driver

/*
 * Called by a tty driver to register itself.
 */
int tty_register_driver(struct tty_driver *driver)
{
    
    int error;
    int i;
    dev_t dev;
    struct device *d;
    /* 如果没有主设备号则申请 */
    if (!driver->major) {
    
        error = alloc_chrdev_region(&dev, driver->minor_start,
                        driver->num, driver->name);
        if (!error) {
    
            driver->major = MAJOR(dev);
            driver->minor_start = MINOR(dev);
        }
    } else {
    
        dev = MKDEV(driver->major, driver->minor_start);
        error = register_chrdev_region(dev, driver->num, driver->name);
    }
    if (error < 0)
        goto err;
    /* 创建字符设备 ,使用 tty_fops */
    if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) {
    
        error = tty_cdev_add(driver, dev, 0, driver->num);
        if (error)
            goto err_unreg_char;
    }

    mutex_lock(&tty_mutex);
    /* 将该 driver->tty_drivers 添加到全局链表 tty_drivers */
    list_add(&driver->tty_drivers, &tty_drivers);
    mutex_unlock(&tty_mutex);

    if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {
    
        for (i = 0; i < driver->num; i++) {
    
            d = tty_register_device(driver, i, NULL);
            if (IS_ERR(d)) {
    
                error = PTR_ERR(d);
                goto err_unreg_devs;
            }
        }
    }
    /* proc 文件系统注册driver */
    proc_tty_register_driver(driver);
    driver->flags |= TTY_DRIVER_INSTALLED;
    return 0;

err_unreg_devs:
    for (i--; i >= 0; i--)
        tty_unregister_device(driver, i);

    mutex_lock(&tty_mutex);
    list_del(&driver->tty_drivers);
    mutex_unlock(&tty_mutex);

err_unreg_char:
    unregister_chrdev_region(dev, driver->num);
err:
    return error;
}

tty_register_driver注册过程干了哪些事
1)为线路规程和termios分配空间,并使 tty_driver 相应的成员指向它们;
2)注册字符设备,名字是 uart_driver->name 我们这里是“ttyS”,文件操作函数集是 tty_fops。可查看tty_cdev_add 函数的定义;
3)将该 uart_driver->tty_drivers 添加到全局链表 tty_drivers ;
4)向 proc 文件系统添加 driver ,这个暂时不了解。

下面是结构体 tty_fops
在这里插入图片描述
    至此,文章起初的结构图中的4个ops已经出现了3个,另一个关于线路规程的在哪?下面来看一下调用关系。

 调用关系
    tty_driver注册了一个字符设备(/dev/ttyS0),从它的 tty_fops 入手,可以得知用户空间是如何访问到最底层的硬件操作函数。以 open、read、write 为例,用户空间 open 时将调用到 uart_port.ops.startup ,在用户空间 write 则调用 uart_port.ops.start_tx。这些内核都已经实现好,在驱动开发过程中几乎不涉及这些代码的修改移植工作。图2.2为串口读写函数调用关系。
在这里插入图片描述

2.2 平台驱动注册-platform _driver_register

    目前Linux2.6版本以后的ARM 结构使用设备树(DTS,Device Tree Source)来分管设备,DeviceTree是一种描述硬件的数据结构,为什么要引入DTS?这是因为在Linux2.6中,ARM架构的板极硬件细节过多地被硬编码在arch/arm/plat-xxx和arch/arm/mach-xxx,比如板上的platform设备、resource、i2c_board_info、spi_board_info以及各种硬件的platform_data,这些板级细节代码对内核来讲只不过是垃圾代码。而采用DeviceTree后,许多硬件的细节可以直接透过它传递给Linux,而不再需要在kernel中进行大量的冗余编码。
    platform_driver的入口函数,仍采用platform_driver_register注册。platform_driver_register用来枚举名称为 “atmel_usart” 的平台设备,只要内核中有相同名称的平台设备,platform_driver_register函数就会调用atmel_serial_driver 中的atmel_serial_probe函数来枚举它。

    函数:platform_driver_register
在这里插入图片描述
结构体:atmel_serial_driver
在这里插入图片描述
    看结构体参数 atmel_serial_driver 的定义,该结构体一个很重要的成员是 .of_match_table = of_match_ptr(atmel_serial_dt_ids), of_match_ptr函数用于将atmel_serial_dt_ids 中.compatible 属性与.dts 文件中描述的设备节点.compatible 属性进行匹配,且不区分大小写,当完成配对之后,就调用atmel_serial_probe函数完成驱动注册的最后工作。

    结构体:atmel_serial_dt_ids
在这里插入图片描述
 设备树
    Dtsi和dts同为设备树文件,dtsi为被包含文件,可被dts或者dtsi文件包含。
    设备树文件sama5d4.dtsi中对串口设备节点的描述。路径:\linux-at91\arch\arm\boot\dts\sama5d4.dtsi。
    sama5d4.dtsi中描述了7个串口设备节点,2个uart,5个usart。
(别名)
在这里插入图片描述
(节点描述)描述了节点的一些属性。

在这里插入图片描述
(引脚定义)
在这里插入图片描述
设备树文件at91-sama5d4_xplained.dts中对串口设备节点的描述。路径:\linux-at91\arch\arm\boot\dts\at91-sama5d4_xplained.dts。

在这里插入图片描述

3 串口移植

3.1 查看板卡的串口引脚

  阅读A5处理器资料,查看UART章节和USART章节对串口的相关描述,根据硬件原理图确定串口的引脚定义。
在这里插入图片描述

3.2 修改情况

1) 设备树文件,到sama5d4.dtsi 文件下去搜索 USART ,对应找到相应的串口节点以及串口的引脚定义部分。sama5d4.dtsi 文件不需要进行修改。
 节点描述
在这里插入图片描述
在这里插入图片描述
 引脚定义
在这里插入图片描述
在这里插入图片描述
2) 设备树文件,查看 at91-sama5d4_xplained.dts 文件中的串口USART,在此文件下对节点进行相应参数修改。
—原来的串口节点信息如下:原始状态使能了uart0但我们没有接其引脚使用,使能了usart3的收发功能,使能了usart4但不具备收发功能,没有使能usart2。
在这里插入图片描述
在这里插入图片描述
—修改后的串口节点信息如下:
在这里插入图片描述
3) 驱动文件,在\linux-at91\drivers\tty\serial\atmel_serial.c文件中,查找static int __init atmel_serial_init(void)函数,找到uart_register_driver()函数,转到该函数定义,函数定义在 serial_core.c 文件中,文件中修改波特率,。原始波特率9600 ,可按自己的需求修改波特率,我们这里将其改为115200。
在这里插入图片描述
    到此,串口驱动移植完成,重新编译驱动文件,包括设备树文件和驱动文件,烧录至开发板,测试串口是否可以正常使用。

4 串口收发功能测试

    将编译成功的内核镜像和设备树文件编译到开发板中,然后使用超级终端接开发板查看打印的日志。
    串口收发功能测试步骤如下:

    1) 查看tty设备。

    (1)编译运行内核,如果UART驱动加载成功会在/dev目录下产生相应UART设备节点; 名称为:ttySx。这里会产生 ttyS0, ttyS1, ttyS2, ttyS5(没有使用)。
ttyS0对应USART3,为DEGB口;
ttyS1对应 USART4;
ttyS2对应 USART2;
ttyS5 对应UART0;

    (2)运行命令: $ cat /proc/tty/driver/atmel_serial ,可以查看显示设备节点详细信息,其中通过地址可以对照设备节点;
root@sama5d4-xplained:/proc/tty/driver# cat atmel_serial
serinfo:1.0 driver revision:
0: uart:ATMEL_SERIAL mmio:0xFC00C000 irq:34 tx:2176533 rx:2142522 RTS|CTS|DTR|DSR|CD|RI
1: uart:ATMEL_SERIAL mmio:0xFC010000 irq:35 tx:1253 rx:68 brk:68 CTS|DSR|CD|RI
2: uart:ATMEL_SERIAL mmio:0xFC008000 irq:33 tx:6 rx:0 DSR|CD|RI
如果UART设备节点未产生,可在其相应驱动程序xx_probe函数中添加打印,查看xx_probe函数是否被调用,进一步查找原因。

    2) 通过跳冒选择串口。
下图中,两个黑色串口共用一个芯片。左边USART3和USART4共用一个串口,通过跳冒来区分,右边USART2单独使用一个串口。
在这里插入图片描述
跳线冒J3用于设置选择DEBG- USART3和串口4,电路图如下图4.2。

在这里插入图片描述
具体的跳线设置如下表4.1和表4.2所示。
表4.1 DEGU模式跳线状态
在这里插入图片描述
3) 如果成功产生了UART设备节点,下面进行串口收发测试;
方法1:
可通过软件回环测试确认UART驱动程序功能是否正常。编写测试程序,将串口2、3引脚短接。读写数据。如果管脚信号测试通过,则串口功能基本调试成功。此方法的优点是无需上位机串口助手的配合,在串口模块到位之前提前完成接口调试工作。

方法2:
Echo 命令回显测试;
在超级终端下进入 /dev 目录下,以测试USART2为例,输入命令: echo test > ttyS2.在串口助手中查看收到的信息,这一步测试串口的发。在超级终端下输入命令: cat ttyS2, 在串口助手中发送clear,在超级终端下查看收到信息,这一步查看串口的收。由于USART2不是调试口,无法打印系统信息,需要使用SSH连接开发板。具体的测试如下图所示。
在这里插入图片描述
方法3: 使用系统集成的串口调试工具 busybox microcom
1) busybox microcom工具的使用;
命令(busybox microcom)使用方法很简单:
Usage: microcom [-d DELAY] [-t TIMEOUT] [-s SPEED] [-X] TTY
参数如下:
-d 表示延时时间,一般我都不设置。
-t 表示超时时间,超多少时间就自动退出。单位为ms
-s 表示传输速度,波特率的意思,这个根据自己的情况而定。
-X 不加
最后指定你的串口设备。如 /dev/ttyO0 , 这是TI的串口设备节点
2)测试方式如下:
 将要测试串口与pc端连接,在pc端开启串口调试工具,波特率设定跟等下microcom设定一样。
 在产品端运行如下命令:
microcom -s 115200 /dev/ttyS2
 在超级终端命令行输出信息,在PC是否有正确显示。反之也测试一下。
在这里插入图片描述

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_40407893/article/details/117956968

智能推荐

TLS OpenSSL 证书验证_ssl_get_verify_result-程序员宅基地

文章浏览阅读690次。int verify_err = SSL_get_verify_result(client.ssl);拿到非X509_V_OK结果后,需由客户端/服务端应用层来决定是否中止TLS流程,在一些场景下,openSSL库并不会通过Alert Message来反馈证书验证失败的结果,具体如下:场景1:当服务端只发送合法的用户证书时,客户端设置根证书和二级证书:r = SSL_CTX_load_verify_locations(k->ctx, "/data/user/0/cn.areful.ope_ssl_get_verify_result

android studio 使用Picasso第三方库的方法_android studio中picasso-程序员宅基地

文章浏览阅读3.1k次。github:https://github.com/square/picasso引入依赖:compile 'com.squareup.picasso:picasso:2.5.2'public class usePicasso extends AppCompatActivity { ImageView imageView; @Override protected void o..._android studio中picasso

使用大图+脚本,生成各种size的app icon和图片素材_sh脚本 sips-程序员宅基地

文章浏览阅读770次。美术UI在公司是宝贵的资源,集各种项目宠爱于一身。为了努力完成好老板的进度需求,不给UI添麻烦。程序员开始忙活了。在iOS里面,我们使用image assert来管理素材和app icon。为什么呢?因为方便,按照image assert要求的尺寸拖进去就好了。Image Assert方便适配各种大小的屏幕什么?UI只给你大图,压缩啥的自己搞。What the f*_sh脚本 sips

finally面试常问_finally{}面试-程序员宅基地

文章浏览阅读541次,点赞3次,收藏3次。1.finally在什么时候执行,什么时候不执行?finally在 电脑关机、程序不再内存等非正常情况下不执行,其他情况都执行。唯一一种在代码中导致finally不执行的情况就是System.exit(0);public class Demo8 { public static void main(String[] args) { haha(); } public static void haha(){ try{ i_finally{}面试

数据流_小记_数据流条目怎么写-程序员宅基地

文章浏览阅读625次。数据流:针对最底层的数据,进行最基本的字节字符数据的读写操作。构造方法:public DataInputStream(InputStream in);//基础的InputStraeam构建DataInputStreampublic DataOutputStream(OutputStream out);//基础的OutputStraeam构建DataOutputStream写入字符串:writeBy_数据流条目怎么写

mysql导入sql脚本文件的正确姿势_mysql怎么导入sql脚本-程序员宅基地

文章浏览阅读10w+次。欢迎使用Markdown编辑器写博客本Markdown编辑器使用StackEdit修改而来,用它写博客,将会带来全新的体验哦:Markdown和扩展Markdown简洁的语法代码块高亮图片链接和图片上传LaTex数学公式UML序列图和流程图离线写博客导入导出Markdown文件丰富的快捷键快捷键加粗 Ctrl + B 斜体 Ctrl + I 引用 Ctrl_mysql怎么导入sql脚本

随便推点

SharePoint Online 定制左侧导航_sharepoint左边的列表如何制作-程序员宅基地

文章浏览阅读673次。  前言  之前几篇文章,都是为大家介绍工作流相关的知识,这一篇文章,我们先暂别工作流,定制一下左侧导航,因为实在是太丑了。  正文  1.先看看我们定制完的左侧导航吧,虽然不是特别的美观,但是!但是跟页面看起来很协调,有木有!  如果你觉得这款导航不好看,其实可以用本文的方式,定制成你喜欢的样子  2.首先创建一个自定义列表用来保存导航,过程比较简单就不截图详解了,有前面..._sharepoint左边的列表如何制作

Matlab调用C接口_matlab ansi c 接口-程序员宅基地

文章浏览阅读884次。matlab调用C语言mex标签(空格分隔): 学习笔记一、为什么要用C语言编写MEX文件MATLAB是矩阵语言,是为向量和矩阵操作设计的,一般来说,如果运算可以用向量或矩阵实现,其运算速度是非常快的。但若运算中涉及到大量的循环处理,MATLAB的速度的令人难以忍受的。解决方法之一为,当必须使用for循环时,把它写为MEX文件,这样不必在每次运行循环中的语句时MATLAB都对它们进行解释。二、 编译_matlab ansi c 接口

使用docker-compose部署Kibana和es_docker-compose es kibana-程序员宅基地

文章浏览阅读1.7k次。目录1. 概述2. 查看es几点容器的实际IP3. 修改kibana容器中kibana.yml文件中es的链接ip:port4. 验证结果5. 参考资料1. 概述基于docker-compose启动es集群和kibana后,在浏览器中访问kibana的5601端口,浏览器显示下异常信息:Kibana server is not ready yes。于是我们接下来就解决该问题:docker kibana出现Kibana server is not ready yet问题2. 查_docker-compose es kibana

CSS深入理解之line-height_css line-height-程序员宅基地

文章浏览阅读1.3w次,点赞19次,收藏65次。以下文字整理自慕课网——张鑫旭的《CSS深入理解之line-height》。一、line-height的定义line-height,又称行高,指的是两行文字基线之间的距离,又可以称为这行文字所占的高度。定义三问:什么是基线?为何是基线?需要两行?如图红色线即为基线基线(baseline),指的是一行字横排时下沿的基础线,基线并不是汉字的下端沿,而是英文字母x的下端沿。基线乃*线定义之根本! (*线指任意线)第3个问题,一行文字难道就没有行高吗?非也,一行文字也是有行高的,两行的定义已_css line-height

SQLServer之创建存储过程_sqlserver create procedure-程序员宅基地

文章浏览阅读2.6w次,点赞5次,收藏51次。创建存储过程注意事项在 SQL Server、 Azure SQL Database、Azure SQL 数据仓库和并行数据库中创建 Transact-SQL 或公共语言运行时 (CLR) 存储过程,存储过程与其他编程语言中的过程类似。可以在当前数据库中创建永久过程,或者在 tempdb 数据库中创建临时程序。存储过程可以: 接受输入参数并以输出参数的格式向调用过程或批处理返回多..._sqlserver create procedure

Java常用API(9)----BigInteger类、BigDecimal类_import java.math.biginteger; 2 public class test {-程序员宅基地

文章浏览阅读2.9k次。一、BigInteger类 点击此处返回总目录 二、BigInteger类的构造方法 三、Big..._import java.math.biginteger; 2 public class test { 3 public static void