1. 在Linux系统中,终端是一类字符型设备,它包括多种类型,通常使用tty来简称各种类型的终端设备。

(1)串口终端(/dev/ttyS*):串口终端是使用计算机串口连接的终端设备。Linux把每个串行端口都看作是一个字符设备。这些串行端口所对应的设备名称是/dev/ttySAC0;/dev/ttySAC1……

(2)控制台终端(/dev/console):在Linux系统中,计算机的输出设备通常被称为控制台终端(Console),这里特指printk信息输出到的设备。/dev/console是一个虚拟的设备,它需要映射到真正的tty上,比如通过内核启动参数” console=ttySAC0”就把console映射到了串口0

(3)虚拟终端(/dev/tty*):

2. Linux tty子系统包含:tty核心,tty线路规程和tty驱动。

(1)tty核心是对整个tty设备的抽象,对用户提供统一的接口。

(2)tty线路规程是对传输数据的格式化。

(3)tty驱动则是面向tty设备的硬件驱动。

3. Linux中Uart框架:

  整个 uart 框架大概的样子如上图所示。简单来分的话可以说成两层,一层是下层我们的串口驱动层,它直接与硬件相接触,我们需要填充一个 struct uart_ops 的结构体,另一层是上层 tty 层,包括 tty 核心以及线路规程,它们各自都有一个 Ops 结构,用户空通过间是 tty 注册的字符设备节点来访问,这么说来如上图所示涉及到了4个 ops 结构了,层层跳转。

4. 在 s3c2440平台,注册串口驱动的步骤是,分配一个struct uart_driver 简单填充,并调用uart_register_driver 注册到内核中去。uart_driver源码

  1. struct uart_driver {
  2. struct module *owner;
  3. const char *driver_name;
  4. const char *dev_name;
  5. int major;
  6. int minor;
  7. int nr;
  8. struct console *cons;
  9.  
  10. /*
  11. * these are private; the low level driver should not
  12. * touch these; they should be initialised to NULL
  13. */
  14. struct uart_state *state;
  15. struct tty_driver *tty_driver;
  16. };

(1) 分配uart_driver结构并简单填充(state和tty_driver将会在uart_register_driver时候赋值)

  1. static struct uart_driver s3c24xx_uart_drv = {
  2. .owner = THIS_MODULE,
  3. .dev_name = "s3c2410_serial",
  4. .nr = CONFIG_SERIAL_SAMSUNG_UARTS,
  5. .cons = S3C24XX_SERIAL_CONSOLE,
  6. .driver_name = S3C24XX_SERIAL_NAME,
  7. .major = S3C24XX_SERIAL_MAJOR,
  8. .minor = S3C24XX_SERIAL_MINOR,
  9. };

(2) 调用uart_register_driver主要填充uart_driver的state和tty_deriver域

  1. int uart_register_driver(struct uart_driver *drv)
  2. {
  3. struct tty_driver *normal = NULL;
  4. int i, retval;
  5.  
  6. BUG_ON(drv->state);
  7.  
  8. /*
  9. * Maybe we should be using a slab cache for this, especially if
  10. * we have a large number of ports to handle.
  11. */
  12. drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
  13. retval = -ENOMEM;
  14. if (!drv->state)
  15. goto out;
  16.  
  17. normal = alloc_tty_driver(drv->nr);
  18. if (!normal)
  19. goto out;
  20.  
  21. drv->tty_driver = normal;
  22.  
  23. normal->owner = drv->owner;
  24. normal->driver_name = drv->driver_name;
  25. normal->name = drv->dev_name;
  26. normal->major = drv->major;
  27. normal->minor_start = drv->minor;
  28. normal->type = TTY_DRIVER_TYPE_SERIAL;
  29. normal->subtype = SERIAL_TYPE_NORMAL;
  30. normal->init_termios = tty_std_termios;
  31. normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
  32. normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = ;
  33. normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
  34. normal->driver_state = drv;
  35. tty_set_operations(normal, &uart_ops);
  36.  
  37. /*
  38. * Initialise the UART state(s).
  39. */
  40. for (i = ; i < drv->nr; i++) {
  41. struct uart_state *state = drv->state + i;
  42.  
  43. state->close_delay = ; /* .5 seconds */
  44. state->closing_wait = ; /* 30 seconds */
  45. mutex_init(&state->mutex);
  46.  
  47. tty_port_init(&state->info.port);
  48. init_waitqueue_head(&state->info.delta_msr_wait);
  49. tasklet_init(&state->info.tlet, uart_tasklet_action,
  50. (unsigned long)state);
  51. }
  52.  
  53. retval = tty_register_driver(normal);
  54. out:
  55. if (retval < ) {
  56. put_tty_driver(normal);
  57. kfree(drv->state);
  58. }
  59. return retval;
  60. }

5. uart_driver的state域(下层,主要是对uart口的统一封装与描述)

(1)state的类型为struct uart_state,定义如下

  1. struct uart_state {
  2. unsigned int close_delay; /* msec */
  3. unsigned int closing_wait; /* msec */
  4.  
  5. #define USF_CLOSING_WAIT_INF (0)
  6. #define USF_CLOSING_WAIT_NONE (~0U)
  7.  
  8. int count;
  9. int pm_state;
  10. struct uart_info info;
  11. struct uart_port *port;
  12.  
  13. struct mutex mutex;
  14. };

(2)在注册driver时,即在uart_register_driver()函数内,会根据 uart_driver->nr 来申请 nr 个 uart_state 空间,用来存放驱动所支持的串口(端口)的物理信息。在Linux内核中,每一个串口的信息由struct uart_port结构来描述,

  1. struct uart_port {
  2. spinlock_t lock; /* port lock */
  3. unsigned long iobase; /* in/out[bwl] */
  4. unsigned char __iomem *membase; /* read/write[bwl] */
  5. unsigned int (*serial_in)(struct uart_port *, int);
  6. void (*serial_out)(struct uart_port *, int, int);
  7. unsigned int irq; /* irq number */
  8. unsigned int uartclk; /* base uart clock */
  9. unsigned int fifosize; /* tx fifo size */
  10. unsigned char x_char; /* xon/xoff char */
  11. unsigned char regshift; /* reg offset shift */
  12. unsigned char iotype; /* io access style */
  13. unsigned char unused1;
  14.  
  15. #define UPIO_PORT (0)
  16. #define UPIO_HUB6 (1)
  17. #define UPIO_MEM (2)
  18. #define UPIO_MEM32 (3)
  19. #define UPIO_AU (4) /* Au1x00 type IO */
  20. #define UPIO_TSI (5) /* Tsi108/109 type IO */
  21. #define UPIO_DWAPB (6) /* DesignWare APB UART */
  22. #define UPIO_RM9000 (7) /* RM9000 type IO */
  23.  
  24. unsigned int read_status_mask; /* driver specific */
  25. unsigned int ignore_status_mask; /* driver specific */
  26. struct uart_info *info; /* pointer to parent info */
  27. struct uart_icount icount; /* statistics */
  28.  
  29. struct console *cons; /* struct console, if any */
  30. #ifdef CONFIG_SERIAL_CORE_CONSOLE
  31. unsigned long sysrq; /* sysrq timeout */
  32. #endif
  33.  
  34. upf_t flags;
  35.  
  36. #define UPF_FOURPORT ((__force upf_t) (1 << 1))
  37. #define UPF_SAK ((__force upf_t) (1 << 2))
  38. #define UPF_SPD_MASK ((__force upf_t) (0x1030))
  39. #define UPF_SPD_HI ((__force upf_t) (0x0010))
  40. #define UPF_SPD_VHI ((__force upf_t) (0x0020))
  41. #define UPF_SPD_CUST ((__force upf_t) (0x0030))
  42. #define UPF_SPD_SHI ((__force upf_t) (0x1000))
  43. #define UPF_SPD_WARP ((__force upf_t) (0x1010))
  44. #define UPF_SKIP_TEST ((__force upf_t) (1 << 6))
  45. #define UPF_AUTO_IRQ ((__force upf_t) (1 << 7))
  46. #define UPF_HARDPPS_CD ((__force upf_t) (1 << 11))
  47. #define UPF_LOW_LATENCY ((__force upf_t) (1 << 13))
  48. #define UPF_BUGGY_UART ((__force upf_t) (1 << 14))
  49. #define UPF_NO_TXEN_TEST ((__force upf_t) (1 << 15))
  50. #define UPF_MAGIC_MULTIPLIER ((__force upf_t) (1 << 16))
  51. #define UPF_CONS_FLOW ((__force upf_t) (1 << 23))
  52. #define UPF_SHARE_IRQ ((__force upf_t) (1 << 24))
  53. /* The exact UART type is known and should not be probed. */
  54. #define UPF_FIXED_TYPE ((__force upf_t) (1 << 27))
  55. #define UPF_BOOT_AUTOCONF ((__force upf_t) (1 << 28))
  56. #define UPF_FIXED_PORT ((__force upf_t) (1 << 29))
  57. #define UPF_DEAD ((__force upf_t) (1 << 30))
  58. #define UPF_IOREMAP ((__force upf_t) (1 << 31))
  59.  
  60. #define UPF_CHANGE_MASK ((__force upf_t) (0x17fff))
  61. #define UPF_USR_MASK ((__force upf_t) (UPF_SPD_MASK|UPF_LOW_LATENCY))
  62.  
  63. unsigned int mctrl; /* current modem ctrl settings */
  64. unsigned int timeout; /* character-based timeout */
  65. unsigned int type; /* port type */
  66. const struct uart_ops *ops;
  67. unsigned int custom_divisor;
  68. unsigned int line; /* port index */
  69. resource_size_t mapbase; /* for ioremap */
  70. struct device *dev; /* parent device */
  71. unsigned char hub6; /* this should be in the 8250 driver */
  72. unsigned char suspended;
  73. unsigned char unused[];
  74. void *private_data; /* generic platform data pointer */
  75. };

  注:这个结构体,是需要我们自己来填充的,比如我们 s3c2440 有3个串口,那么就需要填充3个 uart_port ,并且通过 uart_add_one_port 添加到 uart_driver->uart_state->uart_port 中去。当然 uart_driver 有多个 uart_state ,每个 uart_state 有一个 uart_port

(3)在 uart_port 里还有一个非常重要的成员 struct uart_ops *ops ,这个也是需要我们自己来实现的。该结构体描述了针对某一串口的具体操作方法

  1. struct uart_ops {
  2. unsigned int (*tx_empty)(struct uart_port *);
  3. void (*set_mctrl)(struct uart_port *, unsigned int mctrl);
  4. unsigned int (*get_mctrl)(struct uart_port *);
  5. void (*stop_tx)(struct uart_port *);
  6. void (*start_tx)(struct uart_port *);
  7. void (*send_xchar)(struct uart_port *, char ch);
  8. void (*stop_rx)(struct uart_port *);
  9. void (*enable_ms)(struct uart_port *);
  10. void (*break_ctl)(struct uart_port *, int ctl);
  11. int (*startup)(struct uart_port *);
  12. void (*shutdown)(struct uart_port *);
  13. void (*flush_buffer)(struct uart_port *);
  14. void (*set_termios)(struct uart_port *, struct ktermios *new,
  15. struct ktermios *old);
  16. void (*set_ldisc)(struct uart_port *);
  17. void (*pm)(struct uart_port *, unsigned int state,
  18. unsigned int oldstate);
  19. int (*set_wake)(struct uart_port *, unsigned int state);
  20.  
  21. /*
  22. * Return a string describing the type of the port
  23. */
  24. const char *(*type)(struct uart_port *);
  25.  
  26. /*
  27. * Release IO and memory resources used by the port.
  28. * This includes iounmap if necessary.
  29. */
  30. void (*release_port)(struct uart_port *);
  31.  
  32. /*
  33. * Request IO and memory resources used by the port.
  34. * This includes iomapping the port if necessary.
  35. */
  36. int (*request_port)(struct uart_port *);
  37. void (*config_port)(struct uart_port *, int);
  38. int (*verify_port)(struct uart_port *, struct serial_struct *);
  39. int (*ioctl)(struct uart_port *, unsigned int, unsigned long);
  40. #ifdef CONFIG_CONSOLE_POLL
  41. void (*poll_put_char)(struct uart_port *, unsigned char);
  42. int (*poll_get_char)(struct uart_port *);
  43. #endif
  44. };

6.  uart_driver的tty_driver域(上层)

(1)tty_driver 是在注册过程中构建的,即在uart_register_driver()函数中构建起来的。

(2)注册过程即uart_register_driver()所做的工作

  ① 根据driver支持的最大设备数,申请n个 uart_state 空间,每一个 uart_state 都有一个 uart_port 。

  ② 分配一个 tty_driver 结构,并将drv->tty_driver 指向它

  ③ 对 tty_driver 进行设置,其中包括默认波特率、校验方式等

  ④ 初始化每一个 uart_state 的 tasklet

  ⑤ 注册tty_driver

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

(3)tty_dr的注册tty_register_driver()

  1. /*
  2. * Called by a tty driver to register itself.
  3. */
  4. int tty_register_driver(struct tty_driver *driver)
  5. {
  6. int error;
  7. int i;
  8. dev_t dev;
  9. void **p = NULL;
  10.  
  11. if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) {
  12. p = kzalloc(driver->num * * sizeof(void *), GFP_KERNEL);
  13. if (!p)
  14. return -ENOMEM;
  15. }
  16.  
  17. if (!driver->major) {
  18. error = alloc_chrdev_region(&dev, driver->minor_start,
  19. driver->num, driver->name);
  20. if (!error) {
  21. driver->major = MAJOR(dev);
  22. driver->minor_start = MINOR(dev);
  23. }
  24. } else {
  25. dev = MKDEV(driver->major, driver->minor_start);
  26. error = register_chrdev_region(dev, driver->num, driver->name);
  27. }
  28. if (error < ) {
  29. kfree(p);
  30. return error;
  31. }
  32.  
  33. if (p) {
  34. driver->ttys = (struct tty_struct **)p;
  35. driver->termios = (struct ktermios **)(p + driver->num);
  36. } else {
  37. driver->ttys = NULL;
  38. driver->termios = NULL;
  39. }
  40.  
  41. cdev_init(&driver->cdev, &tty_fops);
  42. driver->cdev.owner = driver->owner;
  43. error = cdev_add(&driver->cdev, dev, driver->num);
  44. if (error) {
  45. unregister_chrdev_region(dev, driver->num);
  46. driver->ttys = NULL;
  47. driver->termios = NULL;
  48. kfree(p);
  49. return error;
  50. }
  51.  
  52. mutex_lock(&tty_mutex);
  53. list_add(&driver->tty_drivers, &tty_drivers);
  54. mutex_unlock(&tty_mutex);
  55.  
  56. if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {
  57. for (i = ; i < driver->num; i++)
  58. tty_register_device(driver, i, NULL);
  59. }
  60. proc_tty_register_driver(driver);
  61. driver->flags |= TTY_DRIVER_INSTALLED;
  62. return ;
  63. }

  tty_driver注册过程

  ① 为线路规程和termios分配空间,并使 tty_driver 相应的成员指向它们。

  ② 注册字符设备,名字是 uart_driver->name 我们这里是“ttySAC”,文件操作函数集是 tty_fops。

  ③ 将该 uart_driver->tty_drivers 添加到全局链表 tty_drivers

  ④ 向 proc 文件系统添加 driver

7. 调用过程分析:tty_driver 注册了一个字符设备,我们以它的 tty_fops 入手,以 open、read、write 为例,分析用户空间如何访问到最底层的硬件操作函数

  1. static const struct file_operations tty_fops = {
  2. .llseek = no_llseek,
  3. .read = tty_read,
  4. .write = tty_write,
  5. .poll = tty_poll,
  6. .unlocked_ioctl = tty_ioctl,
  7. .compat_ioctl = tty_compat_ioctl,
  8. .open = tty_open,
  9. .release = tty_release,
  10. .fasync = tty_fasync,
  11. };

(1)tty_open分析

  1. static int tty_open(struct inode *inode, struct file *filp)
  2. {
  3. int ret;
  4.  
  5. lock_kernel();
  6. ret = __tty_open(inode, filp);
  7. unlock_kernel();
  8. return ret;
  9. }

  ① tty_open的核心代码是调用函数__tty_open(),__tty_open()的简略代码如下

  1. static int __tty_open(struct inode *inode, struct file *filp)
  2. {
  3. struct tty_struct *tty = NULL;
  4. int noctty, retval;
  5. struct tty_driver *driver;
  6. int index;
  7.  
  8. dev_t device = inode->i_rdev;
  9. unsigned saved_flags = filp->f_flags;
  10.  
  11. ...
  12.  
  13. //在全局tty_drivers链表中获取Core注册的tty_driver
  14.  
  15. driver = get_tty_driver(device, &index);
  16.  
  17. tty = tty_init_dev(driver, index, ); // tty->ops = driver->ops;
  18.  
  19. filp->private_data = tty;
  20.  
  21. if (tty->ops->open)
  22. /* 调用tty_driver->tty_foperation->open */
  23. retval = tty->ops->open(tty, filp);
  24.  
  25. return ;
  26. }

  从 tty_drivers 全局链表获取到前边我们注册进去的 tty_driver ,然后分配设置一个 struct tty_struct 的结构,最后调用 tty_struct->ops->open 函数,其实 tty_struct->ops == tty_driver->ops  。

  ② tty_init_dev源代码为

  1. struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx,
  2. int first_ok)
  3. {
  4. struct tty_struct *tty;
  5. int retval;
  6.  
  7. /* Check if pty master is being opened multiple times */
  8. if (driver->subtype == PTY_TYPE_MASTER &&
  9. (driver->flags & TTY_DRIVER_DEVPTS_MEM) && !first_ok)
  10. return ERR_PTR(-EIO);
  11.  
  12. /*
  13. * First time open is complex, especially for PTY devices.
  14. * This code guarantees that either everything succeeds and the
  15. * TTY is ready for operation, or else the table slots are vacated
  16. * and the allocated memory released. (Except that the termios
  17. * and locked termios may be retained.)
  18. */
  19.  
  20. if (!try_module_get(driver->owner))
  21. return ERR_PTR(-ENODEV);
  22.  
  23. tty = alloc_tty_struct();
  24. if (!tty)
  25. goto fail_no_mem;
  26. initialize_tty_struct(tty, driver, idx);
  27.  
  28. retval = tty_driver_install_tty(driver, tty);
  29. if (retval < ) {
  30. free_tty_struct(tty);
  31. module_put(driver->owner);
  32. return ERR_PTR(retval);
  33. }
  34.  
  35. /*
  36. * Structures all installed ... call the ldisc open routines.
  37. * If we fail here just call release_tty to clean up. No need
  38. * to decrement the use counts, as release_tty doesn't care.
  39. */
  40.  
  41. retval = tty_ldisc_setup(tty, tty->link);
  42. if (retval)
  43. goto release_mem_out;
  44. return tty;
  45.  
  46. fail_no_mem:
  47. module_put(driver->owner);
  48. return ERR_PTR(-ENOMEM);
  49.  
  50. /* call the tty release_tty routine to clean out this slot */
  51. release_mem_out:
  52. if (printk_ratelimit())
  53. printk(KERN_INFO "tty_init_dev: ldisc open failed, "
  54. "clearing slot %d\n", idx);
  55. release_tty(tty, idx);
  56. return ERR_PTR(retval);
  57. }

  initialize_tty_struct():用tty_driver来初始化tty_struct结构

  tty_ldisc_setup():调用线路规程中的open函数

(2)tty_open总结

  ① 获取tty_driver

  ② 根据tty_driver初始化一个tty_struct结构

    * 设置 tty_struct 的线路规程为 N_TTY (不同类型的线路规程有不同的 ops)

    * 初始化一个延时工作队列,唤醒时调用flush_to_ldisc ,读函数时我们需要分析它

    * 初始化 tty_struct 里的两个等待队列头

    * 设置 tty_struct->ops == tty_driver->ops

  ③ 在 tty_ldisc_setup 函数中调用到线路规程的open函数

  ④ 如果 tty_struct->ops 也就是 tty_driver->ops 定义了 open 函数则调用,显然是有的 uart_open

Linux串口驱动程序设计的更多相关文章

  1. linux串口驱动分析

    linux串口驱动分析 硬件资源及描写叙述 s3c2440A 通用异步接收器和发送器(UART)提供了三个独立的异步串行 I/O(SIO)port,每一个port都能够在中断模式或 DMA 模式下操作 ...

  2. Smart210学习记录------linux串口驱动

    转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=27025492&id=327609 一.核心数据结构 串口驱动有 ...

  3. linux串口驱动分析——发送数据

    一.应用程序中write函数到底层驱动历程 和前文提到的一样,首先先注册串口,使用uart_register_driver函数,依次分别为tty_register_driver,cdev_init函数 ...

  4. linux串口驱动分析——打开设备

    串口驱动是由tty_driver架构实现的.一个应用程序中的函数要操作硬件,首先会经过tty,级级调用之后才会到达驱动之中.本文先介绍应用程序中打开设备的open函数的整个历程. 首先在串口初始化中会 ...

  5. linux 串口驱动(二)初始化 【转】

    转自:http://blog.chinaunix.net/uid-27717694-id-3493611.html 8250串口的初始化: (1)定义uart_driver.uart_ops.uart ...

  6. linux串口驱动分析【转】

    转自:http://blog.csdn.net/hanmengaidudu/article/details/11946591 硬件资源及描述 s3c2440A 通用异步接收器和发送器(UART)提供了 ...

  7. Linux按键驱动程序设计--从简单到不简单【转】

    本文转载自:http://blog.csdn.net/coding__madman/article/details/51399353 混杂设备驱动模型: 1. 混杂设备描述 在Linux系统中,存在一 ...

  8. Linux按键驱动程序设计详解---从简单到不简单【转】

    转自:http://blog.csdn.net/coding__madman/article/details/51399353 版权声明:本文为博主原创文章,未经博主允许不得转载. 混杂设备驱动模型: ...

  9. Linux 网络设备驱动程序设计(2)

    二.回环网卡的程序设计 /*************************** *******回环网卡的驱动程序*** ***********吕晓宁*********** *********2015 ...

随机推荐

  1. 设计模式(java)--观察者模式

    转自:卡奴达摩http://blog.csdn.net/zhengzhb/article/details/7471978?reload#reply 定义:定义对象间一种一对多的依赖关系,使得当每一个对 ...

  2. 在windows上编译wireshark源代码

    终于在windows上成功编译了wireshark源代码,个中酸辛,都是泪..只能说要多试! windows上编译wireshark共用到三个东西:wireshark源代码.python.cygwin ...

  3. C# Winform 跨线程更新UI控件常用方法总结(转)

    出处:http://www.tuicool.com/articles/FNzURb 概述 C#Winform编程中,跨线程直接更新UI控件的做法是不正确的,会时常出现“线程间操作无效: 从不是创建控件 ...

  4. 【转】Java多线程编程(十)-并发编程原理(分布式环境中并发问题)

    转载地址:http://blog.csdn.net/leicool_518/article/details/42268947 在分布式环境中,处理并发问题就没办法通过操作系统和JVM的工具来解决,那么 ...

  5. 老司机带你玩Spring.Net -入门篇

    网上有 Spring.Net 的相关的很多介绍的文章还有实践例子,推荐个还不错的博客 Spring.Net 学习笔记 .以前对 Spring.Net 算是有过一面之缘,但却迟迟未真正相识.在网上有太多 ...

  6. OpenNI depth深度数据的数据格式

    图像如何打开 如何查看它的数据格式并一个个读取 试一下ENVI等 可见,灰度图的Data只有一个值[0],而彩色图的Data却有三个值[142,119,113]. 这是用ENVI的Cursor Val ...

  7. hadoop大致问题

    一.项目编码实现 HDFS文件上传 HDFS文件下载 定位文件读取 通过API操作HDFS 通过IO流操作HDFS HDFS写数据流程 HDFS读数据流程 统计一堆文件中单词出现的个数(WordCou ...

  8. Python下载网页图片

    有时候不如不想输入路径,那就需要用os模块来修改当前路径 下面是从其他地方看到的一个例子,就是把图片url中的图片名字修改,然后就可以循环保存了,不过也是先确定了某个url 来源:http://www ...

  9. java-设计模式汇总整理

    最近,对各种模式做了一个整理,便于后续自用. 1.工厂模式 总结:很好理解,一个接口,2个类实现这个接口,那么可以用“接口 变量=new 接口实现类”的方式调用不同的实现类,调用方式直接使用接口的方法 ...

  10. Ubuntu 网关服务器配置

    1.设置Linux内核支持ip数据包的转发 echo "1" > /proc/sys/net/ipv4/ip_forward or vi /etc/sysctl.conf   ...