zoukankan      html  css  js  c++  java
  • 驱动开发之read/write

    驱动开发之read/write:

    系统中一切的读写都是站在用户空间的角度来考虑(把你自己当做用户空间)
    什么是输入/读?数据从内核空间流向用户空间
    什么是输出/写?数据从用户空间流向内核空间

    read:

    应用层:

    ssize_t read(int fd, void *buf, size_t count);

    参数1:文件描述符
    参数2:存放读取到的数据的空间首地址
    参数3:空间大小(不是读到的数据大小)
    返回值:成功读取到的字节数

    驱动层:

    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

    参数1:
    参数2:应用层read函数的参数2
    参数3:应用层read函数的参数3
    参数4:文件指针偏移量
    返回值:正确读取的字节数

    驱动层中实现读功能:

    static inline long copy_to_user(void __user *to,const void *from, unsigned long n);    

    参数1:用户空间缓存区首地址
    参数2:内核空间的缓存区首地址
    参数3:实际拷贝的字节数
    返回值:0成功,非0出错

    write:

    应用层:

    ssize_t write(int fd, const void *buf, size_t count);

    参数1:
    参数2:存放数据的空间首地址
    参数3:实际写的字节数

    驱动层:

    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

    参数1:
    参数2:应用层write函数的参数2
    参数3:应用层write函数的参数3
    参数4:文件指针偏移量
    返回值:正确写入的字节数

    驱动层中实现写功能:

    static inline long copy_from_user(void *to,const void __user * from, unsigned long n)

    参数1:内核空间的缓存区首地址
    参数2:用户空间缓存区首地址
    参数3:实际拷贝的字节数
    返回值:0成功,非0出错

     1 #include <stdio.h>
     2 #include <sys/types.h>
     3 #include <sys/stat.h>
     4 #include <fcntl.h>
     5 
     6 int main(int argc, const char *argv[])
     7 {
     8     int fd;
     9 
    10     fd = open("/dev/demo",O_RDWR);
    11     if(fd == -1)
    12     {
    13         perror("open");
    14         return -1;
    15     }
    16     
    17     char buf[64];
    18     read(fd,buf,sizeof(buf));
    19     printf("%s
    ",buf);
    20 
    21     write(fd,"I am from user",15);
    22     close(fd);
    23     return 0;
    24 }
    app.c 
    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/fs.h>
    #include <linux/device.h>
    #include <asm/uaccess.h>
    
    MODULE_LICENSE("GPL");
    
    int major;
    struct class *cls;
    struct device *devs;
    
    char krbuf[1024] = "I am from kernel";
    char kwbuf[1024];
    
    int demo_open(struct inode *inode,struct file *filp)
    {
        return 0;
    }
    
    int demo_close(struct inode *inode,struct file *filp)
    {
        return 0;
    }
    
    ssize_t demo_read(struct file *filp,char __user *ubuf,size_t size,loff_t *off)
    {
    //    printk("demo_read
    ");
    //    strncpy(ubuf,krbuf,strlen(krbuf) + 1);
    //    return strlen(krbuf) + 1;
        
        int ret;
        ssize_t n;
        if(strlen(krbuf) + 1 > size)
            n = size;
        else
            n = strlen(krbuf) + 1;
        ret = copy_to_user(ubuf,krbuf,n);
        if(ret != 0)
        {
            return -EFAULT;
        }
    
        return n;
    }
    
    ssize_t demo_write(struct file *filp,const char __user *ubuf,size_t size,loff_t *off)
    {
        ssize_t n;
        int ret;
        if(size > sizeof(kwbuf))
            n = sizeof(kwbuf);
        else 
            n = size;
        ret = copy_from_user(kwbuf,ubuf,n);
        if(ret != 0)
            return -EFAULT;
        
        printk("%s
    ",kwbuf);
        return n;
    }
    struct file_operations fops = {
        .owner = THIS_MODULE,
        .open = demo_open,
        .read = demo_read,
        .write = demo_write,
        .release = demo_close,
    };
    
    int demo_init(void)
    {
        major = register_chrdev(0,"demo",&fops);
    
        cls = class_create(THIS_MODULE,"demo");
    
        devs = device_create(cls,NULL,MKDEV(major,0),NULL,"demo");
    
        return 0;
    }
    module_init(demo_init);
    
    void demo_exit(void)
    {
        device_destroy(cls,MKDEV(major,0));
        class_destroy(cls);
        unregister_chrdev(major,"demo");
        return;
    }
    module_exit(demo_exit);
    demo.c

    分支匹配函数:

    应用层:

    int ioctl(int fd, int request, ...);

    参数1:文件描述符
    参数2:命令——用来和驱动中的某个分支匹配
    参数3:可以没有参数
    可以有参数(只能有一个),有参数时可以传递变量名或者变量地址,不能是常量。

    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

    参数1:
    参数2:接收应用层ioctl的参数2
    参数3:如果应用层参数3没有传参,此参数没有值
    如果应用层参数3传递的是变量名,此参数接收了变量的内容
    如果应用层参数3传递的是变量的地址,此参数接收的就是变量地址

    命令的封装方法:
    命令本身是一个32位无符号整数,这32位被分成了4个部分(方向,数据类型大小,幻数,序号)

    #define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
                       
    #define _IOC(dir,type,nr,size) 
    (((dir) << _IOC_DIRSHIFT) 
    ((type) << _IOC_TYPESHIFT) 
    ((nr) << _IOC_NRSHIFT) 
    ((size) << _IOC_SIZESHIFT))

    dir代表方向
    type代表幻数
    nr代表序号
    size代表数据类型大小

    #define _IOC_TYPESHIFT 8
    #define _IOC_SIZESHIFT 16
    #define _IOC_DIRSHIFT 30

    命令 = 方向 << 30 | 数据类型大小 << 16 | 幻数 << 8 | 序号 << 0
    方向占2位:无读写数据、只读数据、只写数据、读写数据
    数据类型大小占14位:
    幻数占8位:如何选择幻数必须查看Documetation/ioctl/ioctl-number.txt
    序号占8位:

    命令的封装需要调用:

    _IO(幻数,序号) 
    _IOR(幻数,序号,数据类型) 
    _IOW(幻数,序号,数据类型) 
    _IOWR(幻数,序号,数据类型)

    ioctl代码:

    1:

     1 struct test
     2 {
     3     int a;
     4     char b;
     5 };
     6 
     7 #define DEMO_CMD1         _IO('x',0)
     8 #define DEMO_CMD2         _IO('x',1)
     9 #define DEMO_CMD3         _IOW('x',2,int)
    10 #define DEMO_CMD4         _IOW('x',3,int)
    11 #define DEMO_CMD5         _IOW('x',4,struct test)
    head.h
     1 #include <stdio.h>
     2 #include <sys/types.h>
     3 #include <sys/stat.h>
     4 #include <fcntl.h>
     5 #include <sys/ioctl.h>
     6 #include "head.h"
     7 
     8 struct test t = {
     9     .a = 100,
    10     .b = 'w',
    11 };
    12 
    13 int main(int argc, const char *argv[])
    14 {
    15     int fd;
    16 
    17     fd = open("/dev/demo",O_RDWR);
    18     if(fd == -1)
    19     {
    20         perror("open");
    21         return -1;
    22     }
    23     
    24 //    ioctl(fd,1);
    25 //    ioctl(fd,2);
    26     
    27     ioctl(fd,DEMO_CMD1);
    28     ioctl(fd,DEMO_CMD2);
    29 
    30     int a = 10;
    31     ioctl(fd,DEMO_CMD3,a);
    32     ioctl(fd,DEMO_CMD4,&a);
    33     ioctl(fd,DEMO_CMD5,&t);
    34     close(fd);
    35     return 0;
    36 }
    app.c
     1 #include <linux/init.h>
     2 #include <linux/module.h>
     3 #include <linux/fs.h>
     4 #include <linux/device.h>
     5 #include <asm/uaccess.h>
     6 #include "head.h"
     7 
     8 MODULE_LICENSE("GPL");
     9 
    10 int major;
    11 struct class *cls;
    12 struct device *devs;
    13 
    14 int demo_open(struct inode *inode,struct file *filp)
    15 {
    16     return 0;
    17 }
    18 
    19 long demo_ioctl(struct file *filp,unsigned int cmd,unsigned long arg)
    20 {
    21     int ret;
    22     int a;
    23     struct test t;
    24     switch(cmd)
    25     {
    26 #if 0
    27     case 1:
    28         printk("first cmd
    ");
    29         break;
    30     case 2:
    31         printk("second cmd
    ");
    32         break;
    33 #endif
    34     case DEMO_CMD1:
    35         printk("first cmd
    ");
    36         break;
    37     case DEMO_CMD2:
    38         printk("second cmd
    ");
    39         break;
    40     case DEMO_CMD3:
    41         printk("%ld
    ",arg);
    42         break;
    43     case DEMO_CMD4:
    44         a = *((int *)arg);
    45         printk("%d
    ",a);
    46         break;
    47     case DEMO_CMD5:
    48         ret = copy_from_user(&t,(struct test *)arg,sizeof(struct test));
    49         printk("%d,%c
    ",t.a,t.b);
    50         break;
    51 
    52     }
    53     return 0;
    54 }
    55 
    56 int demo_close(struct inode *inode,struct file *filp)
    57 {
    58     return 0;
    59 }
    60 
    61 struct file_operations fops = {
    62     .owner = THIS_MODULE,
    63     .open = demo_open,
    64     .unlocked_ioctl = demo_ioctl,
    65     .release = demo_close,
    66 };
    67 
    68 int demo_init(void)
    69 {
    70     major = register_chrdev(0,"demo",&fops);
    71 
    72     cls = class_create(THIS_MODULE,"demo");
    73 
    74     devs = device_create(cls,NULL,MKDEV(major,0),NULL,"demo");
    75 
    76     return 0;
    77 }
    78 module_init(demo_init);
    79 
    80 void demo_exit(void)
    81 {
    82     device_destroy(cls,MKDEV(major,0));
    83     class_destroy(cls);
    84     unregister_chrdev(major,"demo");
    85     return;
    86 }
    87 module_exit(demo_exit);
    demo.c

    2.led:

    head.h
     1 #include <stdio.h>
     2 #include <sys/types.h>
     3 #include <sys/stat.h>
     4 #include <fcntl.h>
     5 #include <sys/ioctl.h>
     6 #include "head.h"
     7 
     8 int main(int argc, const char *argv[])
     9 {
    10     int fd;
    11 
    12     fd = open("/dev/led",O_RDWR);
    13 
    14     while(1)
    15     {
    16         ioctl(fd,LED2_ON);
    17         sleep(1);
    18         ioctl(fd,LED3_ON);
    19         sleep(1);
    20         ioctl(fd,LED1_ON);
    21         sleep(1);
    22         ioctl(fd,LED4_ON);
    23         sleep(1);
    24 
    25         ioctl(fd,LED4_OFF);
    26         sleep(1);
    27         ioctl(fd,LED3_OFF);
    28         sleep(1);
    29         ioctl(fd,LED1_OFF);
    30         sleep(1);
    31         ioctl(fd,LED2_OFF);
    32         sleep(1);
    33     }
    34     return 0;
    35 }
    app.c
     1 #include <linux/module.h>
     2 #include <linux/init.h>
     3 #include <linux/fs.h>
     4 #include <linux/device.h>
     5 #include <asm/io.h>
     6 #include "head.h"
     7 
     8 #define GPX2CON 0x11000c40
     9 #define GPX1CON 0x11000c20
    10 #define GPF3CON 0x114001e0
    11 
    12 unsigned int *gpx2con;
    13 unsigned int *gpx2dat;
    14 unsigned int *gpx1con;
    15 unsigned int *gpx1dat;
    16 unsigned int *gpf3con;
    17 unsigned int *gpf3dat;
    18 
    19 int fs4412_led_major;
    20 struct class *cls;
    21 struct device *devs;
    22 
    23 int fs4412_led_open(struct inode *inode,struct file *filp)
    24 {
    25     return 0;
    26 }
    27 
    28 long fs4412_led_ioctl(struct file *filp,unsigned int cmd,unsigned long arg)
    29 {
    30     switch(cmd)
    31     {
    32     case LED2_ON:
    33         writel((readl(gpx2dat) & ~(1 << 7)) | 1 << 7,gpx2dat);
    34         break;
    35     case LED2_OFF:
    36         writel((readl(gpx2dat) & ~(1 << 7)),gpx2dat);
    37         break;
    38     case LED1_ON:
    39         writel((readl(gpx1dat) & ~(1 << 0)) | 1 << 0,gpx1dat);
    40         break;
    41     case LED1_OFF:
    42         writel((readl(gpx1dat) & ~(1 << 0)),gpx1dat);
    43         break;
    44     case LED3_ON:
    45         writel((readl(gpf3dat) & ~(1 << 4)) | 1 << 4,gpf3dat);
    46         break;
    47     case LED3_OFF:
    48         writel((readl(gpf3dat) & ~(1 << 4)),gpf3dat);
    49         break;
    50     case LED4_ON:
    51         writel((readl(gpf3dat) & ~(1 << 5)) | 1 << 5,gpf3dat);
    52         break;
    53     case LED4_OFF:
    54         writel((readl(gpf3dat) & ~(1 << 5)),gpf3dat);
    55         break;
    56     }
    57     return 0;
    58 }
    59 
    60 struct file_operations fs4412_led_ops = {
    61     .owner = THIS_MODULE,
    62     .open = fs4412_led_open,
    63     .unlocked_ioctl = fs4412_led_ioctl,    
    64 };
    65 
    66 int __init fs4412_led_init(void)
    67 {
    68     fs4412_led_major = register_chrdev(0,"led",&fs4412_led_ops);
    69     cls = class_create(THIS_MODULE,"led");
    70     devs = device_create(cls,NULL,MKDEV(fs4412_led_major,0),NULL,"led");
    71 
    72     gpx2con = ioremap(GPX2CON,4);
    73     gpx2dat = gpx2con + 1;
    74 
    75     gpx1con = ioremap(GPX1CON,4);
    76     gpx1dat = gpx1con + 1;
    77 
    78     gpf3con = ioremap(GPF3CON,4);
    79     gpf3dat = gpf3con + 1;
    80 
    81     writel((readl(gpx2con) & ~(0xf << 28)) | 1 << 28,gpx2con);
    82     writel((readl(gpx1con) & ~(0xf << 0)) | 1 << 0,gpx1con);
    83     writel((readl(gpf3con) & ~(0xff << 16)) | 0x11 << 16,gpf3con);
    84 
    85     return 0;
    86 }
    87 module_init(fs4412_led_init);
    88 
    89 void __exit fs4412_led_exit(void)
    90 {
    91     device_destroy(cls,MKDEV(fs4412_led_major,0));
    92     class_destroy(cls);
    93     unregister_chrdev(fs4412_led_major,"led");
    94     return;
    95 }
    96 module_exit(fs4412_led_exit);
    97 MODULE_LICENSE("GPL");
    led.c

    3.资源的竞争

    1 #define LED_ON         _IO('x',0)
    2 #define LED_OFF     _IO('x',1)
    head.h
     1 #include <stdio.h>
     2 #include <sys/types.h>
     3 #include <sys/stat.h>
     4 #include <fcntl.h>
     5 #include <sys/ioctl.h>
     6 #include "head.h"
     7 
     8 int main(int argc, const char *argv[])
     9 {
    10     int fd;
    11 
    12     fd = open("/dev/led0",O_RDWR);
    13 
    14     while(1)
    15     {
    16         ioctl(fd,LED_ON);
    17         sleep(1);
    18 
    19         ioctl(fd,LED_OFF);
    20         sleep(1);
    21     }
    22     return 0;
    23 }
    app0.c
     1 #include <stdio.h>
     2 #include <sys/types.h>
     3 #include <sys/stat.h>
     4 #include <fcntl.h>
     5 #include <sys/ioctl.h>
     6 #include "head.h"
     7 
     8 int main(int argc, const char *argv[])
     9 {
    10     int fd;
    11 
    12     fd = open("/dev/led1",O_RDWR);
    13 
    14     while(1)
    15     {
    16         ioctl(fd,LED_ON);
    17         sleep(1);
    18 
    19         ioctl(fd,LED_OFF);
    20         sleep(1);
    21     }
    22     return 0;
    23 }
    app1.c
     1 #include <stdio.h>
     2 #include <sys/types.h>
     3 #include <sys/stat.h>
     4 #include <fcntl.h>
     5 #include <sys/ioctl.h>
     6 #include "head.h"
     7 
     8 int main(int argc, const char *argv[])
     9 {
    10     int fd;
    11 
    12     fd = open("/dev/led2",O_RDWR);
    13 
    14     while(1)
    15     {
    16         ioctl(fd,LED_ON);
    17         sleep(1);
    18 
    19         ioctl(fd,LED_OFF);
    20         sleep(1);
    21     }
    22     return 0;
    23 }
    app2.c
     1 #include <stdio.h>
     2 #include <sys/types.h>
     3 #include <sys/stat.h>
     4 #include <fcntl.h>
     5 #include <sys/ioctl.h>
     6 #include "head.h"
     7 
     8 int main(int argc, const char *argv[])
     9 {
    10     int fd;
    11 
    12     fd = open("/dev/led3",O_RDWR);
    13 
    14     while(1)
    15     {
    16         ioctl(fd,LED_ON);
    17         sleep(1);
    18 
    19         ioctl(fd,LED_OFF);
    20         sleep(1);
    21     }
    22     return 0;
    23 }
    app3.c
      1 #include <linux/module.h>
      2 #include <linux/init.h>
      3 #include <linux/fs.h>
      4 #include <linux/device.h>
      5 #include <asm/io.h>
      6 #include "head.h"
      7 
      8 #define GPX2CON 0x11000c40
      9 #define GPX1CON 0x11000c20
     10 #define GPF3CON 0x114001e0
     11 
     12 unsigned int *gpx2con;
     13 unsigned int *gpx2dat;
     14 unsigned int *gpx1con;
     15 unsigned int *gpx1dat;
     16 unsigned int *gpf3con;
     17 unsigned int *gpf3dat;
     18 
     19 int fs4412_led_major;
     20 struct class *cls;
     21 struct device *devs;
     22 
     23 int fs4412_led_open(struct inode *inode,struct file *filp)
     24 {
     25     int num;
     26     num = iminor(inode);//获取次设备号
     27     filp->private_data = (void *)num;
     28     return 0;
     29 }
     30 
     31 void fs4412_led_on(int num)
     32 {
     33     switch(num)
     34     {
     35     case 0:
     36         //点亮地一个灯
     37         break;
     38     case 1:
     39         break;
     40     case 2:
     41         break;
     42     case 3:
     43         break;
     44     }
     45 }
     46 
     47 void fs4412_led_off(int num)
     48 {
     49     switch(num)
     50     {
     51     case 0:
     52         //关闭地一个灯
     53         break;
     54     case 1:
     55         break;
     56     case 2:
     57         break;
     58     case 3:
     59         break;
     60     }
     61 }
     62 
     63 
     64 long fs4412_led_ioctl(struct file *filp,unsigned int cmd,unsigned long arg)
     65 {
     66     int num;
     67     num = (int)filp->private_data;
     68     switch(cmd)
     69     {
     70     case LED_ON:
     71         fs4412_led_on(num);
     72         break;
     73     case LED_OFF:
     74         fs4412_led_off(num);
     75         break;
     76 #if 0
     77     case LED2_ON:
     78         writel((readl(gpx2dat) & ~(1 << 7)) | 1 << 7,gpx2dat);
     79         break;
     80     case LED2_OFF:
     81         writel((readl(gpx2dat) & ~(1 << 7)),gpx2dat);
     82         break;
     83     case LED1_ON:
     84         writel((readl(gpx1dat) & ~(1 << 0)) | 1 << 0,gpx1dat);
     85         break;
     86     case LED1_OFF:
     87         writel((readl(gpx1dat) & ~(1 << 0)),gpx1dat);
     88         break;
     89     case LED3_ON:
     90         writel((readl(gpf3dat) & ~(1 << 4)) | 1 << 4,gpf3dat);
     91         break;
     92     case LED3_OFF:
     93         writel((readl(gpf3dat) & ~(1 << 4)),gpf3dat);
     94         break;
     95     case LED4_ON:
     96         writel((readl(gpf3dat) & ~(1 << 5)) | 1 << 5,gpf3dat);
     97         break;
     98     case LED4_OFF:
     99         writel((readl(gpf3dat) & ~(1 << 5)),gpf3dat);
    100         break;
    101 #endif
    102     }
    103     return 0;
    104 }
    105 
    106 struct file_operations fs4412_led_ops = {
    107     .owner = THIS_MODULE,
    108     .open = fs4412_led_open,
    109     .unlocked_ioctl = fs4412_led_ioctl,    
    110 };
    111 
    112 int __init fs4412_led_init(void)
    113 {
    114     int i;
    115     fs4412_led_major = register_chrdev(0,"led",&fs4412_led_ops);
    116     cls = class_create(THIS_MODULE,"led");
    117 
    118     for(i = 0;i < 4;i ++)
    119         devs = device_create(cls,NULL,MKDEV(fs4412_led_major,i),NULL,"led%d",i);
    120 
    121     gpx2con = ioremap(GPX2CON,4);
    122     gpx2dat = gpx2con + 1;
    123 
    124     gpx1con = ioremap(GPX1CON,4);
    125     gpx1dat = gpx1con + 1;
    126 
    127     gpf3con = ioremap(GPF3CON,4);
    128     gpf3dat = gpf3con + 1;
    129 
    130     writel((readl(gpx2con) & ~(0xf << 28)) | 1 << 28,gpx2con);
    131     writel((readl(gpx1con) & ~(0xf << 0)) | 1 << 0,gpx1con);
    132     writel((readl(gpf3con) & ~(0xff << 16)) | 0x11 << 16,gpf3con);
    133 
    134     return 0;
    135 }
    136 module_init(fs4412_led_init);
    137 
    138 void __exit fs4412_led_exit(void)
    139 {
    140     int i;
    141     for(i = 3;i >= 0;i --)
    142         device_destroy(cls,MKDEV(fs4412_led_major,i));
    143     class_destroy(cls);
    144     unregister_chrdev(fs4412_led_major,"led");
    145     return;
    146 }
    147 module_exit(fs4412_led_exit);
    148 MODULE_LICENSE("GPL");
    led_muiltfile.c

      

    总结:

    struct file_operations 
    {
        ssize_t (*read)(struct file *filp,char __user *ubuf,size_t size,loff_t *off);
        ssize_t (*write)(struct file *filp,const char __user *ubuf,size_t size,loff_t *off);
        long (*unlock_ioctl)(struct file *filp,unsigned int cmd,unsigned long arg);
    };
    
    读写都是站在用户空间的角度来考虑
    输入:数据从内核空间流向用户空间
    输出:数据从用户空间流向内核空间
    
    应用层:ssize_t read(int fd,void *buf,size_t size);
    驱动层:ssize_t (*read)(struct file *filp,char __user *ubuf,size_t size,loff_t *off);
    int copy_to_user(void *to,void *from,size_t size);
    
    
    应用层:ssize_t write(int fd,const void *buf,size_t size);
    驱动层:ssize_t (*write)(struct file *filp,const char __user *ubuf,size_t size,loff_t *off);
    int copy_from_user(void *to,void *from,size_t size);
    
    应用层:int ioctl(int fd,int cmd,...)
    驱动层:long (*unlock_ioctl)(struct file *filp,unsigned int cmd,unsigned long arg);
    
    命令分成了4个部分:
    2位方向、14位数据类型大小、8位幻数、8位序号
            
    设置命令需要调用一些宏函数:
    _IO(幻数,序号);
    _IOR(幻数,序号,数据类型);
    _IOW(幻数,序号,数据类型);
    _IOWR(幻数,序号,数据类型);
    
    避免冲突需要查看Documetation/ioctl/ioctl-number.txt:
    查看幻数和序号配合使用时哪些值会有冲突。
    
    
    假设:4个灯,使不同的设备文件(led0 led1 led2 led3)操作不同的led灯。
    相应的可以有4个进程,每个进程打开一个设备文件。这四个进程访问的是同一个驱动。
    在操作驱动接口时调用的也是相同的接口,这种情况下会造成资源的竞争。
    
    
    int led_open(struct inode *,struct file *)
    {
        num = iminor(inode);
        filp->private_data = (void *)num;
    }
    
    long led_ioctl()
    {
        int num;
        num = (int)filp->private_data;
    }
    课程总结


  • 相关阅读:
    make ubuntu desktop beautiful
    scratch 编程第二弹
    stratch pragramming
    emacs 安装与基本设置-1
    linux相关命令
    7-12
    python strip()
    python 正则表达式 re.sub & re.subn
    python 正则表达式 re.findall &re.finditer
    python 正则表达式 re.split
  • 原文地址:https://www.cnblogs.com/hslixiqian/p/9655949.html
Copyright © 2011-2022 走看看