操作系统第六次上机教程——Proc文件系统

@(学习资料)[OS, 文件系统, Proc伪文件系统]

理解Linux操作系统文件系统的概念

  • 岳友(yoyo9170@163.com)

上节课我们主要介绍了动态加载内核模块,并完成了相应的实验,本次实验主要讲解和考察的是Linux 文件系统,其中核心主要涉及到三个方面:Proc文件系统介绍、文件创建、文件读写、内核空间与用户空间交互。

Proc文件系统简介

Proc文件系统是一个虚拟文件系统,只存在于内存中,通过它可以查询、设置系统的运行情况及各种系统参数。
/proc目录下,每个文件的读写都可以绑定到一个内核函数上;当文件被读写时,被绑定的相应的内核函数就会被触发,由这些函数处理文件的输出与输入。
/proc目录下几乎包含了系统所有信息:

  • 可以通过 Proc文件系统收集系统信息;
  • 也可以用于程序调试;
  • 还可以动态的修改内核的数据结构,改变内核的运行状态

大家可以在自己的Linux系统里,进入到/proc目录下,会看到很多以数字命名的文件夹,这些一个个数字即对应着进程PID,根据上面提到的三点,不难理解这里主要收集系统运行进程的状态信息,具体对应参数和意义为:

文件/目录名 描述
Cmdline 该进程的命令行参数
Cwd 进程运行的当前路径的符号链接
Environ 该进程运行的环境变量
Exe 该进程相关的程序的符号链接
Fd 包含该进程使用的文件描述符
Maps 可执行文件或库文件的内存映像
Mem 该进程使用的内存
Root 该进程所有者的家(home)目录
Stat 进程状态
Statm 进程的内存状态
Status 用易读的方式表示的进程状态

当然/proc目录下还有一系列的文件,这些文件的作用
为:
cat /proc/cpuinfo 命令获得系统的CPU信息
cat /proc/meminfo 命令获得系统内存信息
cat /proc/loadvag 命令获得CPU平均负载
……(请大家自己查看)

1、核心数据结构

  • proc_dir_entry结构

代表各文件节点,管理操作系统从用户空间到内核空间对文件读写的驱动

1
2
3
4
5
6
7
8
9
10
struct proc_dir_entry { 
...
const char *name; //名字;
mode_t mode; //权限属性;
nlink_t nlink; //目录下子目录的数目;
read_proc_t *read_proc;//读操作函数
write_proc_t *write_proc;//写操作函数
struct file_operations *proc_fops; //文件操作函数;
...
}

2、创建文件

  • 头文件
    1
    #include <linux/proc_fs.h>
  • 创建文件系统
    1
    struct proc_dir_entry* create_proc_entry(const char* name, mode_t mode, struct proc_dir_entry* parent);

name:文件名
mode:文件类型和访问权限 parent:父目录,如果parent为根目录,则为NULL
返回值:成功,返回指向文件的proc_dir_entry结构指针.

  • 实例
    1
    foo_file = create_proc_entry(“foo”, 0644, example_dir);

3、删除文件

内核通过函数remove_proc_entry()删除proc文件

1
void remove_proc_entry(const char* name, struct proc_dir_entry* parent);

parent目录下移除名为name的文件 proc文件不能使用rmdirrm命令删除,只能使用上述函数 在删除文件之前,先释放proc_dir_entry结构中的data

4、创建目录

  • 创建一个目录

    1
    2
    3
    struct proc_dir_entry* proc_mkdir(const char* name, struct
    proc_dir_entry* parent)
    ;

    //目录名为name,父目录为parent
  • 创建符号连接

    1
    2
    3
    struct proc_dir_entry* proc_symlink(const char* name, struct
    proc_dir_entry* parent, const char* dest)
    ;

    //在parent目录下创建一个名字为name的符号连接,链接目标是dest

5、用户空间与内核空间数据传输

proc_dir_entry结构中的read_proc, write_proc是回调函数,分别定义了对文件的读写函数;

1
2
3
4
//实现读写文件接口
struct proc_dir_entry* foo_file;
foo_file->read_proc = proc_read_foobar;
foo_file->write_proc = proc_write_foobar;

6、读取文件

  • 读文件接口函数原型
    1
    2
    int read_func(char* page, char** start, off_t off, int count, int* eof,
    void* data)
    ;

其中,Page指针指向将写入数据的缓冲区 Start说明数据写在页面的位置 Count写入的字节数 Offset写入数据的位置相对于缓冲区的偏移量 Eof返回参数,当读到文件结尾时,该标志被置位 Data保存私有数据

7、写入文件

  • 写文件接口函数原型
    1
    2
    int write_func(struct file* file, const char* buffer, unsigned long count, void*
    data)
    ;

buffer数据缓冲区中读取count字节数的数据,写入到file文件
一般buffer指向用户空间缓冲区,应先调用copy_from_user将数据拷贝到内 核空间
一般省略file参数

代码示例

1、实现在/Proc目录下床间一个可读可写的my_procfile_1K文件

  • 编写 rw_proc.c 文件,如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/proc_fs.h>
    #include <asm/uaccess.h> /* for copy_from_user */

    #define PROCFS_MAX_SIZE 1024
    #define PROCFS_NAME "my_procfile_1k"

    MODULE_LICENSE("GPL");
    static struct proc_dir_entry *Our_Proc_File;
    static char procfs_buffer[PROCFS_MAX_SIZE];
    static unsigned long procfs_buffer_size = 0;

    static int procfile_read(char *buffer,char **buffer_location, off_t offset, int buffer_length, int *eof,
    {
    int ret;
    void *data)

    printk(KERN_INFO "procfile_read (/proc/%s) called\n", PROCFS_NAME);

    if (offset > 0)
    {
    ret=0;
    }
    else{
    memcpy(buffer, procfs_buffer, procfs_buffer_size);
    ret = procfs_buffer_size;
    }
    return ret;
    }

    static int procfile_write(struct file *file, const char *buffer, unsigned long count, void *data)
    {

    procfs_buffer_size = count;
    if(procfs_buffer_size > PROCFS_MAX_SIZE )
    {
    procfs_buffer_size = PROCFS_MAX_SIZE;
    }
    if(copy_from_user(procfs_buffer, buffer, procfs_buffer_size) )
    {
    return EFAULT;
    }
    return procfs_buffer_size;
    }

    static int rw_proc_init(void)
    {

    Our_Proc_File = create_proc_entry(PROCFS_NAME, 0644, NULL);
    if (Our_Proc_File == NULL) {
    remove_proc_entry(PROCFS_NAME, &proc_root);
    printk(KERN_ALERT "Error: Could not initialize /proc/%s\n", PROCFS_NAME);
    return ENOMEM;
    }
    Our_Proc_File->read_proc = procfile_read;
    Our_Proc_File->write_proc = procfile_write;
    Our_Proc_File->owner = THIS_MODULE;
    Our_Proc_File->mode = S_IFREG | S_IRUGO;
    Our_Proc_File->uid = 0;
    Our_Proc_File->gid = 0;
    Our_Proc_File->size = 37;
    printk(KERN_INFO "/proc/%s created\n", PROCFS_NAME);
    return 0;
    }

    static void rw_proc_exit(void) {
    remove_proc_entry(PROCFS_NAME, &proc_root);
    printk(KERN_INFO "/proc/%s removed\n", PROCFS_NAME);
    }

    module_init(r w_proc_init);
    module_exit(rw_proc_exit);

    /*代码注释
    A> Our_Proc_File: /proc 文件系统下的每一个文件或目录项都对应一个proc_dir_entry 结构体实例。
    B> procfs_buffer: 这个缓冲区是我们 my_procfile_1k 的真实载体,本质上我们将来对my_procfile_1k 文件的读写都是对该缓冲区的读写。
    C> PROCFS_MAX_SIZE 和 procfs_buffer_size: 这两个值的关系就好比你有 4G(PROCFS_MAX_SIZE)的 U 盘,里面已用空间为 2.3G(procfs_buffer_size)一样。因此每次写入文件
    D> procfile_read:我们为文件 my_procfile_1k 实现的 read 函数。buffer 为写出内核空间的缓冲区(一个 page 大小),buffer_location 是用来写出大于一个 page 大 小的数据是用的参数。offset 是用来标记一个进程中对当前文件读写的偏移量。 buffer_len 是要读的大小,这里被忽略掉,直接返回了文件(buffer)的全部内容。eof 用来标识文件已经到达末尾。
    E> procfile_write:我们为文件 my_procfile_1k 实现的 write 函数。使用copy_from_user 函数的原因是 buffer 是一个指向用户地址空间的指针。
    F> create_proc_entry:用来创建一个 proc 文件系统的文件实例,创建目录实例的 函数为 proc_mkdir(char * dirname, struct proc_dir_entr * father_dir);两个函数的末尾参数置 NULL,表示父目录为/proc
    */

  • 编写 Makefile 文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    KVER=$(shell uname -r) 
    KDIR=/lib/modules/$(KVER)/build
    PWD=$(shell pwd)

    obj-m += rw_proc.o
    default:
    make -C $(KDIR) M=$(PWD) modules
    clean:
    -rm *.ko
    -rm *.o
    -rm -f Mo*
    -rm *.mod.*
  • 编译模块 #make

  • 安装模块

    1
    #insmod rw_proc.ko
  • 测试/proc/my_procfile_1k 文件功能

    1
    2
    3
    4
    #cat /proc/my_procfile_1k
    #ls * > /proc/my_procfile_1k
    #cat /proc/my_procfile_1k
    #dmesg | tail -10 //查看系统日志里的打印信息
  • 卸载模块

    1
    rmmod

实验题目

1、(考查内核与用户空间的交互方式)在文档中给出的示例代码中procfile_read函数里面的offset>0分支里面添加printk打印信息到系统日志,并编写一个读my_procfile_1k的c语言程序。在程序路循环五次读取my_procfile_1k文件并打印读取的信息。(贴出代码和截图)查看内核日志信息。想想为什么。

2、(考查创建可读写的proc文件)在/proc目录下创建一个以task_info目录,并在里面创建一个pid文件和task_info文件:pid文件可读写,task_info文件只读。若pid=-1,则task_info文件的内容为当前系统所有进程的ID:应用程序名称的记录集合;若pid=>0task_info为进程pid的详细信息(自定义)。(请贴出代码并展示截图结果)
提示:
遍历所有进程可以用Linux/sched.h中的for_each_process宏实现:
struct tast_struct * task;
for_each_prcess(task){
}

操作系统第四次上机教程——同步与互斥

@(学习资料)[OS, 多线程, 进程同步, 进程互斥, 生产者与消费者]

理解Linux操作系统多线程以及多线程之间关于资源的同步与互斥

  • 岳友(yoyo9170@163.com)

上节课我们主要介绍了管道和共享内存进程间通信,并完成了相应的实验,本次实验主要讲解和考察的是多线程以及消费者生产者问题,其中核心主要涉及到三个方面:Pthread介绍、Pthread创建多线程、互斥锁。

Pthread简介

pthread表示 POSIX Threads,是一个标准,也是一个库。C 语言程序使用 pthread 需要引用 pthread.h头文件,并且在链接时指定参数 -lpthread

pthread 可以创建多线程应用程序,线程类似于进程,但是线程是共享地址空间的,因此同一进程下的多个线程可以都访问和操作同一变量。因此需要开发者进行访问同步控制。

  • 创建线程
    1
    2
    #include <pthread.h>
    int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict attr,void *(*start_rtn)(void), void *restrict arg);

该函数执行成功返回为0

  • 第一个参数为指向线程标识符的指针。
  • 第二个参数用来设置线程属性。
  • 第三个参数是线程运行函数的起始地址。
  • 最后一个参数是运行函数的参数。
    其中说明,最后一个参数即为该线程的入口函数,创建了线程之后,新的线程会拥有自己的函数栈,但是全局数据和其他线程共享。因此,函数的局部变量是线程的私有数据,而函数之外的全局变量则是在所有线程之间共享的。
  • 结束线程

    结束一个进程有许多种方法,线程可以调用pthread_exit退出,也可以直接从start_routine函数返回实现退出。此外,线程是附属于进程的,因此任何一个线程调用exit结束进程的时候,所有的线程都会退出。

  • 线程等待

    调用函数pthread_join可以等待某个线程结束。

    1
    int pthread_join(pthread_t tid, void** ret)

    接受两个参数,第一个参数是要等待的线程的ID,第二个参数是线程的返回值,即退出状态。这个函数会阻塞当前线程,直到正在等待的线程退出才会继续运行。

互斥锁

因为线程可以共享全局数据,多个线程对全局数据进行写入操作就需要同步,同步的典型做法是使用锁。pthread中的互斥锁的数据类型是pthread_mutex_t,表示同一时刻只有一个线程能够拥有这把锁,其他尝试获取这把锁的线程都需要等待。创建和销毁互斥锁的方法为:

1
2
3
pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER;
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);

首先需要使用宏PTHREAD_MUTEX_INITIALIZER类型的变量,然后使用pthread_mutex_init函数初始化,创建互斥锁的时候,可以将attr参数传入NULL使用默认设置。两个函数在执行成功的时候都返回0。

获取锁和释放所使用下面的函数完成:

1
2
3
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

其中,pthread_mutex_lockpthread_mutex_trylock函数都会尝试获取互斥锁,它们的区别在于当锁已经被其他线程使用的时候,pthread_mutex_lock函数会阻塞,直到获取这把锁,而pthread_mutex_trylock则会直接返回一个非零值。

多线程示例

创建线程

  • 编写pthread.c

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    //pthread.c
    #include <pthread.h>
    #include <string.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <stdio.h>

    pthread_t tid;
    void * thread_fun(void * name){
    printf("%s: Hello world !\n",name);
    return NULL;
    }

    int main() {
    int err;
    err = pthread_create(&tid,NULL, thread_fun,"Child thread");
    if(err !=0){
    printf("can't create thread!\n");
    exit(1);
    }
    printf("Main thread: Hello world!\n");
    sleep(1 );
    return 0;
    }
  • 编译运行
    gcc pthread.c -o pthread -lpthread
    ./pthread

  • return语句前面的sleep(1);移到thread_fun中的printf之前,再次编译一遍,观察结果。对比上次实验 fork 实验中父子进程运行关系,有什么不同。为什么是这样?

线程间的资源关系

  • 编写pthread2.c

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    #include <pthread.h> 
    #include <string.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <stdio.h>

    pthread_t tid;
    int share=0;
    void *thread_fun(void * name){
    printf("%s: Hello world !\n",name);
    int i;
    int s =5;

    for( i =0;i <1000;i++){
    share = 111;
    sleep( s);
    printf("%s has slept %d seconds;share = %d\n",
    name, i*s, share);
    }
    return NULL;
    }
    int main() {
    int err;
    err = pthread_create(&tid,NULL, thread_fun,"Child thread");

    if(err !=0){
    printf("can't create thread!\n");
    exit(1);
    }
    printf("Main thread: Hello world!\n");

    int i ;
    for( i =0;i <1000;i++){
    share = 333;
    sleep(1);
    printf("Main thread: has slept %d seconds;share = %d\n",i,share);
    }
    return 0;
    }
  • 编译运行
    gcc pthread2.c -o pthread2 -lpthread
    ./pthread2
    @仔细观察主线程和子线程中 share 的输出值,是否是想要的结果?和上次 fork 实验那个中的 n 相比,有什么不同?为什么?

线程间互斥

  • 为了防止上例中出现的问题,我们需要利用 pthread 库提供的互斥锁来达到这个目的。
  • 编写pthread3.c

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    #include <pthread.h>
    #include <string.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <stdio.h>

    pthread_t tid;
    int share=0;
    pthread_mutex_t share_mutex = PTHREAD_MUTEX_INITIALIZER;

    void *thread_fun(void * name){
    printf("%s: Hello world !\n",name);
    int i ;
    int s =5;

    for( i =0;i <1000;i++){
    pthread_mutex_lock(&share_mutex);
    share = 111;
    sleep( s);
    printf("%s has slept %d seconds; share =%d\n",
    name,i*s,share);
    pthread_mutex_unlock(&share_mutex);
    return NULL;

    int main() {
    int err;
    err = pthread_create(&tid,NULL, thread_fun,"Child thread");

    if(err !=0){
    printf("can't create thread!\n");
    exit(1);
    }
    printf("Main thread: Hello world!\n");
    int i ;

    for( i =0;i <1000;i++){
    pthread_mutex_lock(&share_mutex);
    share = 333;
    sleep(1 );
    printf("Main thread: has slept %d seconds;
    share = %d\n",i,share);

    pthread_mutex_unlock(&share_mutex);
    }
    return 0;
    }
    /*@一个线程可以调用 pthread_mutex_lock 获得 Mutex,如果这时另一个线程已经调 用 pthread_mutex_lock 获得了该 Mutex,则当前线程需要挂起等待,直到另一个线 程调用 pthread_mutex_unlock 释放 Mutex,当前线程被唤醒,才能获得该 Mutex 并 继续执行。*/
  • 编译运行
    gcc pthread3.c -o pthread3 -lpthread
    ./pthread3
    @ 观察运行结果。主线程和次线程中的两个sleep调用分别放到 pthread_mutex_unlock 之后,再次编译运行,观察结果,思考第一次运行的结果为 何不对? 使用互斥量时应该注意什么问题?
    @该示例还代码中还引入两个概念:临界区:主次线程中被 pthread_mutex_lockpthread_mutex_unlock 函数保护起来的区域。临界资源:变量 share。请大家回忆 操作系统概念知识。临界资源、临界区、互斥量之间的关系。

消费者生产者问题

  • 生产者与消费者模型中,要保证以下几点:
    • 同一时间内只能有一个生产者生产
    • 同一时间内只能有一个消费者消费
    • 生产者生产的同时消费者不能消费
    • 消息队列满时生产者不能继续生产
    • 消息队列空时消费者不能继续消费
  • 数据模型
    • 仓库:warehouse [CAPABILITY];
    • 生产者:procuder();
    • 消费者:consumer();

操作系统第一次上机教程——Linux基础

@(学习资料)[OS, shell 命令,C 语言,GCC编译]

认识Linux

  • 岳友(yoyo9170@163.com)

关于Linux的历史

也许有人已经了解到,Linux 和 Unix 是非常像的。没错,Linux 就是根据 Unix 演变过来的。 当年 Linus Torvalds 就是因为接触到了 Unix 而后才自己想开发一个简易的系统内核的,他开发 的简易系统内核其实就是 Linux。当时 Linus 把开发的这个系统内核丢到网上提供大家下载,由于 它的精致小巧,越来越多的爱好者去研究它。人们对这个内核添枝加叶,而后成为了一个系统。 也许你听说过吧,Linux 是免费的。其实这里的免费只是说 Linux 的内核免费。在 Linux 内核的 基础上而产生了众多 Linux 的版本。
Linux 的发行版说简单点就是将 Linux 内核与应用软件做一个打包。较知名的发行版有: Ubuntu、RedHat、CentOS、Debain、Fedora、SuSE、OpenSUSE、TurboLinux、 BluePoint、RedFlag、Xterm、SlackWare 等。
而常用的就是 Redhat 和CentOS,这里有必要说一下,其实 CentOS 是基于 Redhat 的, 网上有人说,Centos 是 Redhat 企业版的克隆。CentOS 较之于 Redhat 可以免费使用 yum 下载安装所需要的软件包,这个是相当方便的。而 Redhat 要想使用 yum 必须要购买服务了。

图形界面还是命令窗口

对于 linux 的应用,我想大多数都是用在服务器领域,对于服务器来讲真的没有必要跑一个图形界 面。所以我们平时安装 linux 操作系统时往往是不安装图形界面的。说到这里也许你会有疑问,图 形界面还能选择装或者不装?
是的,虽然 linux 和微软的 windows 一样同位操作系统,但是它们有一个很大的区别就是 windows 操作系统的图形界面是和内核一体的,俗称微内核,而 linux 操作系统图形界面就像一个软件一样, 和内核并不是一体的。所以 linux 你可以选择不安装图形界面,这样不仅不影响服务器的正常使用 还可以节省系统资源的开销,何乐而不为呢?
如果你对 linux 超级感兴趣,想使用 linux 就像使用 windows 一样,那你可以安装图形界面,可以像 windows 一样来体验 linux 也是蛮不错的。但是如果你想成为一个专业的 linux系统工程师,那我建 议你从第一天开始就不要去安装图形界面,从命令窗口开始熟悉它。
另外一点值得说的是,日常应用中,我们都是远程管理服务器的,不可能开着图形界面来让你去操作, 虽然目前也有相应的工具支持远程图形连接服务器,可是那样太消耗网络带宽资源,所以从这方面 来考虑还是建议你不要使用图形界面。

Linux基础操作命令

强烈建议大家可以去阅读《鸟哥的私房菜》,这是对于Linux初学者来说就是圣典。

1、查看登录用户

1
2
$ who
$ who am i

2、查看虚拟控制台(终端)当前目录

1
$ pwd

3、查看联机(帮助)手册

1
$ man cd   #查看 cd 命令的帮助文档(按 HJKL 移动光标,按 Q 退出)

4、查看当前工作目录下的文件

  • 常见用法

    1
    2
    buaasoft@buaasoft_virtual_machine:/var$ ls
    backups cache crash games lib local lock log mail opt run spool tmp
  • 查看全部文件

    1
    2
    buaasoft@buaasoft_virtual_machine:/var$ ls -a
    . .. backups cache crash games lib local lock log mail opt run spool tmp
  • 查看详细信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    buaasoft@buaasoft_virtual_machine:/var$ ls -l
    total 44
    drwxr-xr-x 2 root root 4096 Aug 16 03:48 backups
    drwxr-xr-x 17 root root 4096 Oct 13 20:40 cache
    drwxrwsrwt 2 root whoopsie 4096 Nov 21 00:44 crash
    drwxr-xr-x 2 root root 4096 Mar 27 2012 games
    drwxr-xr-x 61 root root 4096 Nov 21 00:48 lib
    drwxrwsr-x 2 root staff 4096 Jan 27 2012 local
    lrwxrwxrwx 1 root root 9 Aug 16 03:28 lock -> /run/lock
    drwxr-xr-x 18 root root 4096 Nov 21 00:48 log
    drwxrwsr-x 2 root mail 4096 Mar 27 2012 mail
    drwxr-xr-x 2 root root 4096 Mar 27 2012 opt
    lrwxrwxrwx 1 root root 4 Aug 16 03:28 run -> /run
    drwxr-xr-x 8 root root 4096 Mar 27 2012 spool
    drwxrwxrwt 2 root root 4096 Aug 15 20:13 tmp
  • 查看隐藏文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    buaasoft@buaasoft_virtual_machine:/var$ ls -la #或者 ls -al, ls -l -a
    total 52
    drwxr-xr-x 13 root root 4096 Aug 16 03:28 .
    drwxr-xr-x 23 root root 4096 Aug 4 00:20 ..
    drwxr-xr-x 2 root root 4096 Aug 16 03:48 backups
    drwxr-xr-x 17 root root 4096 Oct 13 20:40 cache
    drwxrwsrwt 2 root whoopsie 4096 Nov 21 00:44 crash
    drwxr-xr-x 2 root root 4096 Mar 27 2012 games
    drwxr-xr-x 61 root root 4096 Nov 21 00:48 lib
    drwxrwsr-x 2 root staff 4096 Jan 27 2012 local
    lrwxrwxrwx 1 root root 9 Aug 16 03:28 lock -> /run/lock
    drwxr-xr-x 18 root root 4096 Nov 21 00:48 log
    drwxrwsr-x 2 root mail 4096 Mar 27 2012 mail
    drwxr-xr-x 2 root root 4096 Mar 27 2012 opt
    lrwxrwxrwx 1 root root 4 Aug 16 03:28 run -> /run
    drwxr-xr-x 8 root root 4096 Mar 27 2012 spool
    drwxrwxrwt 2 root root 4096 Aug 15 20:13 tmp

在 Ubuntu 上,还可以使用llla

5、切换工作目录

1
2
3
4
5
$ cd /usr	# 进入绝对路径 '/usr'
$ cd .. # 返回上一层目录
$ cd - # 回到刚才目录
$ cd / # 进入根目录
$ cd ~ # 进入用户目录

6、创建目录和文件

1
2
3
$ mkdir lab		# 在当前目录下创建 lab 目录
$ mkdir -p lab/report # 在当前目录下创建 lab ,并在 lab 下创建 report
$ touch hello.c # 创建一个名为 hello.c 的空文件

7、删除目录和文件

1
2
3
4
$ rm hello.c 	# 删除文件hello.c
$ rmdir lab # 删除空目录lab
$ rm -r lab # 删除空目录lab
$ rm -rf lab # 删除目录lab(可以非空)

8、复制和移动文件

1
2
$ cp source target # 将 source 复制到 target (保留原文件)
$ mv source target # 将 source 移动到 target (不保留原文件)

Linux C编程基础

1、GCC的使用

  • vim 编写hello.c源文件
    1
    2
    3
    4
    5
    6
    #include <stdio.h>

    int main(int argc, char *argv[], char *env[]){
    printf("%s\n","hello,world!");
    return 0;
    }

思考一下main函数第三个参数的作用!

  • 使用GCC编译源代码
    1
    2
    3
    4
    5
    $ gcc -E hello.c -o hello.i 	# 预处理,生成hello.i
    $ gcc -S hello.c -o hello.s # 编译,生成汇编文件 hello.s
    $ gcc -c hello.c -o hello.o # 编译,生成目标代码 hello.o
    $ gcc hello.o -o hello # 链接,生成可执行文件 hello
    $ ./hello # 执行 hello

当然上面是为了让大家了解C代码是如何被编译、链接并最终执行的过程,其中每一步过程对于开发者来说都是透明的,如果熟悉了这一系列的步骤,可以采取gcc hello.c -o hello 一步生成可执行文件。

2、GCC编译多源码文件的工程

myprintf.h文件

1
2
3
4
5
6
#ifndef MYPRINTF_H
#define MYPRINTF_H

void myprintf(char *);

#endif

myprintf.c文件

1
2
3
4
5
6
#include <stdio.h>
#include "myprintf.h"

void myprintf(char *s){
printf("myprintf: %s.\n",s);
}

main.c文件

1
2
3
4
5
6
#include "myprintf.h"

int main(void){
myprintf("hello,world!");
return 0;
}

思考一下ifndefdefineendif三者的作用,为什么这样组织文件?

  • 使用GCC 编译
    1
    2
    3
    4
    $ gcc -c myprintf.c -o myprintf.o
    $ gcc -c main.c -o main.o
    $ gcc myprintf.o main.o -o hello
    $ ./hello # 执行

思考为什么需要多步编译,能不能一步编译成功?

Makefile 入门

上面最后的问题,答案是肯定的。用Makefile编译上面的三个文件,创建一个名为Makefile的文件,内容如下:

1
2
3
4
5
6
7
8
hello: myprintf.o main.o
gcc -o hello myprintf.o main.o

myprintf.o: myprintf.c myprintf.h
gcc -c -o myprintf.o myprintf.c

main.o: main.c myprintf.h
gcc -c -o main.o main.c

然后在命令行下执行make命令,就可以得到hello文件。
Makefile 文件的核心组成是“规则”,一条规则的结构如下:

1
2
3
4
目标:条件 1 条件 2 ...
命令 1
命令 2
...

注意,各条命令前面必须有一个Tab(制表符),不能是两个Tab,也不能是空格,只能是一个Tab
概括起来相当于:要得到“目标”,需要具备“条件1”、“条件2”……,并且要顺序地执行“命令 1”、“命令2”……
条件可以是文件,也可以是其他规则的目标。执行make命令的时候,make会在当前目录下寻找Makefile问价,并将文件的第一条规则的目标当做本次执行的目标。
Makefile的功能非常复杂,可以用一整本书来讲,下面只介绍一些基本用法。
Makefile可以定义变量, :=用来赋值,使用变量的时候要用$(..)。前面的例子可以改写 为:

1
2
3
4
5
6
7
8
9
10
11
TARGET := hello
CC := gcc

$(TARGET): myprintf.o main.o
$(cc) -o hello myprintf.o main.o

myprintf.o: myprintf.c myprintf.h
$(cc) -c -o myprintf.o myprintf.c

main.o: main.c myprintf.h
$(cc) -c -o main.o main.c

在每一条规则内部,可以使用$@来代表这条规则的目标,用$<来表示这条规则中的第一个条件,用$^表示所有的条件,因此前面的例子可以改写为:

1
2
3
4
5
6
7
8
9
10
11
TARGET := hello
CC := gcc

$(TARGET): myprintf.o main.o
$(cc) -o $@ @^

myprintf.o: myprintf.c myprintf.h
$(cc) -c -o $@ $<

main.o: main.c myprintf.h
$(cc) -c -o $@ $<

如果不想在执行make命令的时候看到一对命令的输出,可以在命令前面加上 @ ,例如:

1
2
3
4
5
6
7
8
9
10
11
TARGET := hello
CC := gcc

$(TARGET): myprintf.o main.o
$(cc) -o $$^

myprintf.o: myprintf.c myprintf.h
$(cc) -c -o $$<

main.o: main.c myprintf.h
$(cc) -c -o $$<

如果还想看到编译的过程,可以加入一些自定义的输出命令,打印一些内容到终端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
TARGET := hello
CC := gcc

$(TARGET): myprintf.o main.o
@echo "\033[1;34mlinking $@\033[0m"
$(cc) -o $$^

myprintf.o: myprintf.c myprintf.h
@echo "\033[1;32mcompiling $@\033[0m"
$(cc) -c -o $$<

main.o: main.c myprintf.h
@echo "\033[1;32mcompiling $@\033[0m"
$(cc) -c -o $$<

echo命令中的\033[1;34m表示切换颜色。
Makefile有一个功能,就是会自动比较目标文件的时间和依赖项的最近更改时间,只有需要重新 编译的文件Makefile才会执行相应的命令。对于一个比较大的项目,这个特点可以节省很多的时 间。
如果希望自动清理编译缓存(就是删除编译过程中生成的.o文件),可以给Makefile 加一个新 的规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
TARGET := hello
CC := gcc

$(TARGET): myprintf.o main.o
@echo "\033[1;34mlinking $@\033[0m"
$(cc) -o $$^

myprintf.o: myprintf.c myprintf.h
@echo "\033[1;32mcompiling $@\033[0m"
$(cc) -c -o $$<

main.o: main.c myprintf.h
@echo "\033[1;32mcompiling $@\033[0m"
$(cc) -c -o $$<

clean:
@echo "\033[1;33mcleaning $@\033[0m"
rm *.o

android点击两次退出应用

android应用开发过程中经常会遇到点击两次退出应用的场景,再android给出的定时机制中,有两种方式能够实现:1)TimerTask通过Timer来进行调度;2)利用ScheduledExecutorService来进行处理。本文主要利用TimerTask来实现,这也是最常见的实现方式。

private static Boolean isQuit = false; 
private Timer timer = new Timer();
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {

        if (keyCode == KeyEvent.KEYCODE_BACK) {
            if (!isQuit) {
                isQuit = true;
                Toast.makeText(getBaseContext(),
                        R.string.back_more_quit,Toast.LENGTH_LONG).show();
                TimerTask task = null;
                task = new TimerTask() {
                    @Override
                    public void run() {
                        isQuit = false;
                    }
                };
                timer.schedule(task, 2000);//其中task
            } else {
                finish();//结束当前Activity
                System.exit(0);//退出应用
            }
        }
        return false;
 }

当然Timer以及ScheduledExecutorService的应用绝不仅限单次定时任务,还能实现不间断重复任务,比如常见的banner轮播就可以用这两种方式来实现,感兴趣可以研究研究。

写给她的第一封信

坐在去上海的火车上,天气依旧是雨,无聊地看着窗外飞过的风景和身旁回上海的情侣,心绪就像后退的街景往后追溯~~
然后拿着手机,忽然想写点文字了,只因为明天——它于现在的我而言,有特殊的意义!
王家卫曾这样描述爱情:那一刻,我很暖!回想四个月前,我赞同了王先生的话,两个人打着电话傻傻地笑着,两个熟悉的人却羞赧地面对面吃饭,还一起坐在还有些微凉的亭子里抱着彼此,但浑然不觉得冷~~
那不是我刻意设计的桥段,虽然在我内心深处,我相信它后面一定会发生,只是没想它的发生带给人的竟然是那般温暖和幸福的感觉。
晚上躺在床上,还傻傻地甚至有点质疑地问自己:我又恋爱了?又是和从班上的同学?妈蛋,我是不是太不厚道。。。。。。
是的,我又恋爱了,我怀着一颗激动而又忐忑的心去确认了n次这个事实。然而事实也反过来诉我:孩子,别激动,也别害怕,过去已经过去,还有未来!一个如此美好的姑娘,一个如此美好的生活开端,接下来你将如何延续这段美好?
我承认我小白了,那一刻似乎百试不爽的工科逻辑、运算思维和丰富想象都歇菜了。我该怎样正确地以一个男朋友的身份去对待一个姑娘:逗她开心?陪她逛街?了解她的爱好?======
这种小白很快反应在生活中了:过分的关心,让你感觉不自由,不太懂你的喜怒哀乐,说话不经过大脑,总是沉默不说令媳妇儿心情更糟。。。。。。
但是生活就是这样宽容,总有个人会容许你犯错,可是不是人人都可以像爱迪生一样犯1000次错误然后才屁颠屁颠弄出个灯泡儿来。于是,慢慢的我平静了,我慢慢适应了这种生活和这种身份:
不再过于激动半夜凌晨三、四点醒来不能入睡;
不再过于担心你每天的吃喝拉撒我有没有服务到位;
不再过于紧张你的忧伤和疲惫。。。。。。
我知道,你需要的是一个依靠和肩膀,需要一个可以跟你share喜悦和哇靠,需要一个可以陪你去看医生照顾你身体,需要一个可以懂你的余氏心思,需要一个可以让你觉得生活很nice的人,我想我已经在慢慢做到,并将最终能做到!
这四个月,或许有别离,或许有郁闷,或许有伤痛(附:嘿嘿。。曾经晚上从学校去赶地铁,跑的过于风骚,摔马路牙子上了),但是最后我忽然想用一个貌似幽默却又跟前面不搭的话来结束这段文字——赵本山小品《白云黑土》中黑土很羞涩却很顺溜的跟白云说:别怕,有老头呢!
胡乱写在2014.8.1,去上海的火车上,很平凡的一天,七夕节的前一天,四个月纪念的前六天,但于我和末末余,每一天都是重要和快乐的一天,不管身在何时何方!