IO&Process基础知识

一.文件IO和标准IO特色linux

库函数:方便程序员开发,可能会存在一些问题(C库除外)。程序员

系统调用:有内核提供的函数接口, 更加的稳定高效。shell

文件IO:无缓冲/低级磁盘IO编程

无缓冲:增长磁盘的访问次数,不利于保护磁盘。数组

优势:能够保证数据的实时写入。缓存

IO的操做方式:看程序的应用场合。session

标准IO:带缓冲的/高级磁盘IO多线程

缓冲IO就是能够减小对磁盘的访问次数,提升系统效率,有利于保护磁盘。异步

缺点:数据不会实时写入。socket

标准IO的缓冲方式:全缓冲、行缓冲、无缓冲。

全缓冲:默认对文件的操做都是全缓冲。当填满标准I/O缓冲区后才进行实际I/O操做

数据什么时候写入磁盘:

     一、缓冲区满了。

     二、程序正常退出(ctrl +c, 断电,属于不正常退出)。

     三、执行刷新操做,fflush()

行缓冲:与终端相关的都是行缓冲

刷新条件:

一、缓冲区满了。

二、程序正常退出(ctrl +c, 断电,属于不正常退出)。

     三、执行刷新操做,fflush() 

     四、遇到\n就会刷新。

无缓冲:标准出错,好比人机交互。不对I/O的操做进行缓冲,即对流的读写时会马上操做实际的文件。

二.标准I/O库的流(stream)

标准I/O库的全部操做都是围绕流(stream)来进行。

FILE 类型在 /usr/include/libio.h中。描述了与文件相关的信息。

在标准I/O中,流用FILE *来描述。

如何得到文件流:

标准IO中,默认开启三个流, stdin  stdout  stderr

fprintf函数:向一个流中写入数据。

int fprintf(FILE *stream, const char *format, ...);

stream:格式化输出的流。

fprintf(stdout, "helloworld\n") === printf("helloworld\n");

fprintf(xxx, "helloworld\n");

⑴获取文件流

FILE *fopen (const char *path, const char *mode);

功能:得到流

参数:path: 要打开的文件的路径、名字, "printf.c"

      mode:

    r:表示打开的文件只能进行读操做, 并且必须保证该文件已经存在。

    如: fopen("printf.c", "r");

    r+:表示打开的文件可读可写操做, 并且必须保证该文件已经存在。

    如: fopen("printf.c", "r+");

    W:表示打开的文件只能进行写操做,可是若是文件已经存在,那么会将文件内容清空。

若是文件不存在,那么会建立一个文件。

        如: fopen("file", "w");

    w+: 表示打开的文件可读可写操做,可是若是文件已经存在,那么会将文件内容清空。

       若是文件不存在,那么会建立一个文件。

    a: 表示以追加的方式对文件进行只写操做,若是文件已经存在,那么会保留文件内容;

           若是文件不存在,那么会建立一个文件。

        a+: 表示以追加的方式对文件进行读写操做,若是文件已经存在,那么会保留文件内容;

           若是文件不存在,那么会建立一个文件。

返回值:FILE* , 若是出错,返回NULL。

⑵关闭文件流

关闭一个打开的流:若是没有显式的关闭时,程序结束后,系统会关闭该流。

fclose(FILE*);成功返回0。

限制:就是不能多重关闭一个流。

问题①:测试在一个程序中最多能够得到多少个流(1024)。

       得到数值多少。

   经过循环fopen实现。

FILE *freopen(const char *restrict pathname, const char *restrict type, FILE* restrict fp)

功能:就是对stdin  stdout进行重定向。将文件流fp重定向到文件pathname中

⑷刷新文件流

fflush(FILE *)

刷新流。

fflush(stdout); 指定要刷新stdout

fflush(NULL);刷新当前程序中全部的流。

⑸流操做:

每次一个字符的I/O:

读函数:

    int getc(FILE *stream);   

    int fgetc(FILE *stream);

    int getchar(void);

功能:就是从一个流中读入一个字符。

返回值:读到的字符, EOF结束。

ctrl + D  模拟EOF

问题②:实现一个简单的cat命令   getc读到文件结尾返回EOF

a、fopen得到文件流

b、循环的getc/fgetc来读取这个流。

     printf()

c、getc读到文件结尾返回EOF 

d、fclose(fp);

问题③:统计一个文件有多少行?

        统计'\n'

每次一个字符的写:

int putc(int c, FILE *stream);

int fputc(int c, FILE *stream);

int putchar(int c);

功能:就是向一个流中写入一个字符。

返回:写入的字符,出错返回EOF

问题④:使用fgetc  fputc实现cat命令

问题⑸:使用fgetc、fputc实现文件拷贝。

         file  拷贝一份file1

a、读源文件、写目标文件

b、须要读、写两个流

c、选择对应的打开模式

判断:文件结尾或者出错

int feof(FILE *stream);

判断是不是到达文件末尾

返回:非0  是到达文件末尾

int ferror(FILE *stream);

判断是不是出错

返回 非0  是出错

int clearerr(FILE *stream);

清空文件操做标志, 一旦清空,那么就没办法用feof和ferror测试。

每次一行的IO操做:

读操做:

  char *gets(char *s);//禁用, 不会检查数组长度。

  char *fgets(char *s, int size, FILE * stream);

  功能:就是从一个流中读入一个字符串。

  参数:s:就是存放读到的字符串

          size: 表示用户但愿读到的字节个数

      stream:要读的流

    返回值:读到的字符串,出错返回NULL。

fgets的特色:

①、执行一次最多能够读到 size - 1个字符,最后一个位置存放'\0';

    fgets读到的都是字符串。

②、fgets函数读到 \n会马上返回。

③、当从终端上读入时, 若是读入的字节个数小于 size -1, 那么'\n也会被读走。

       若是读入的字节个数大于 size -1, 那么'\n就不会被读走。

问题⑥:使用fgets和printf来实现cat命令

       fgets读到文件结尾返回NULL。

每次一行的写函数:

int puts(const char *s);

功能:向标准输出写入数据

特色:自带换行符'\n

      只会输出第一个'\0'以前的内容。

int fputs(const char *s, FILE * stream);

功能:向一个流中写入数据, fputs没有自带换行符。

返回:EOF失败, 非负表明成功。

特色:只会输出第一个'\0'以前的内容。

问题⑦:使用fgets  fputs实现文件拷贝?

直接IO方式:按记录个数进行操做。

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

功能:从一个流中读数据

参数:ptr 存放都到的数据。

  size: 每一条记录的字节个数。

  nmemb: 执行一次fread最多要读的记录的个数。

     最多能够读到字节个数 = size  * nmemb;

stream: 要读的流。

每一条记录:能够是任何数据,好比struct 、int、 字符串。

返回值:表示正确读到的记录个数。

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)

功能:向一个流中写数据

参数:ptr 存放要写入的数据。

  size: 每一条记录的字节个数。

  nmemb: 执行一次fread最多要读的记录的个数。

    最多能够写入字节个数 = size  * nmemb;

  stream: 要写的流。

每一条记录:能够是任何数据,好比struct 、int、 字符串。

返回值:表示正确写入的记录个数。

对文件的偏移 和定位操做:

int fseek(FILE *stream, long offset, int whence);

功能:对一个文件的访问位置进行偏移。

      stream:要进行操做的流

  offset: 偏移量

  whence:要偏移的参考点,基准点。

SEEK_SET 表明文件的起始位置

   SEEK_END 表明文件的结尾

  SEEK_CUR 表明文件当前的访问位置。

fseek(fp, 0, SEEK_END);  表明偏移到文件末尾位置

fseek(fp, 0, SEEK_SET);  表明偏移到文件开始位置  

fseek(fp, 100, SEEK_SET); 表示向文件结尾的方向偏移100个字节

fseek(fp, -100, SEEK_END); 表示向文件开头的方向偏移100个字节

返回值:成功返回0, 失败-1.

long ftell(FILE *stream);

功能:就是定位当前的访问位置

返回值:返回从开头到如今位置的字节个数。

void rewind(FILE *stream);

功能:将文件位置偏移到开头。

+---------------------------------------------------------------------------+

做业:

编程读写一个文件test.txt,每隔1秒向文件中写入一行数据,相似这样: 

1,  2007-7-30 15:16:42  

2,  2007-7-30 15:16:43

该程序应该无限循环,直到按Ctrl-C中断程序。

再次启动程序写文件时能够追加到原文件以后,而且序号可以接续上次的序号,好比: 

1,  2007-7-30 15:16:42

2,  2007-7-30 15:16:43

3,  2007-7-30 15:19:02

4,  2007-7-30 15:19:03

5,  2007-7-30 15:19:04

提示:

要追加写入文件,同时要读取该文件的内容以决定下一个序号是几,应该用什么模式打开文件? 

首先判断一下打开的文件是否为新文件,若是是新文件,就从序号1开始写入;若是不是新文件,则统计原来有多少行,好比有n行,而后从序号n+1开始写入。之后每写一行就把行号加1。 

获取当前的系统时间须要调用函数time(),获得的结果是一个time_t类型,其实就是一个大整数,其值表示从UTC时间1970年1月1日00:00:00(称为UNIX的Epoch时间)到当前时刻的秒钟数。而后调用localtime()将time_t所表示的UTC时间转换为本地时间(咱们是+8区,比UTC多8个小时)并转成struct tm类型,该类型的各数据成员分别表示年月日时分秒,请本身写出转换格式的代码,不要使用ctime()或asctime()函数。具体用法请查阅man page。time和localtime函数须要头文件time.h。 

调用sleep(n)可以使程序睡眠n秒,该函数须要头文件unistd.h。 

time()得到自 1970-1-1 0:0:0: 距离当前系统的秒数。

time_t mytime;

mytime = time()

localtime()用来将秒数转换成对应的时间格式。

struct tm *localtime(const time_t *timep);

struct tm * mytm;

mytm = localtime(&mytime);

sleep(1);睡眠

fprintf(fp, "");

出错判断:

strerror() - 映射errno对应的错误信息

char *strerror(int errnum);

perror() – 输出用户信息及errno对应的错误信息

void perror(const char *s);

自带换行符。

三.文件IO:系统调用

默认打开文件描述符:标准输入   标准输出   标准出错

                     0           1            2

              STDIN_FILENO   STDOUT_FILENO  STDERR_FILENO

文件描述符是一个非负整数。

int open(const char *pathname, int flags, mode_t mode)

功能:就是打开一个文件,得到一个文件描述符。

参数:pathname  文件名

      flags: 

  O_RDONLY   表示对文件只读

  O_WRONLY   表示对文件只写

  O_RDWR     表示对文件可读可写  ====》这三个标志位是互斥的,有且只能有一个。

  O_CREAT    表示文件不存在时,会建立文件。

  O_EXCL     配合O_CREAT使用,当文件已经存在时还要建立,那么就会出错。

  O_TRUNC    如文件已经存在,那么打开文件时先删除文件中原有数据。

  O_APPEND   以添加方式打开文件,因此对文件的写操做都在文件的末尾进行。

  如: open("file", O_WRONLY|O_TRUNC);

      mode:  只有添加了O_CREAT 标志位时,才须要使用。

             指定建立的文件的权限。

S_IRWXU S_IRUSR S_IWUSR S_IXUSR S_IRWXG S_IRGRP S_IWGRP S_IXGRP S_IRWXO S_IROTH S_IWOTH S_IXOTH

      如:open("file", O_WRONLY|O_CREAT|O_EXCL,  0666);

 

返回值:获得的当前程序中最小的未使用的非负整数文件描述符。

open函数建立的文件权限: 指定权限 & (~umask)

                          好比: 0666 & (~0002)

问题:测试在一个程序中最多能够得到多少个文件描述符?

关闭文件描述符:

close(fd);

读写文件:

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

功能:从文件描述符中读取数据

参数:fd  要读的文件描述符

  buf  存放读取到的数据

  count 指望执行一次read要读取的字节个数。

返回值:就是实际读到的字节个数。

        读到文件末尾返回0.

特色:read读取数据和’\n’, ‘\0’没有任何关系。

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

功能:向文件描述符中写入数据

 参数: fd  要写的文件描述符

    buf  存放要写入到的数据

count 指望执行一次write要写入的字节个数。

返回值:就是实际写入的字节个数。

特色:write 读取数据和'\n, '\0'没有任何关系    

od -c  以字符形式显示文件内容。

问题:使用read  write函数实现文件拷贝?

当read和write配合使用时,write要写入的字节个数必须是read实际读到的字节个数。

bytes = read(fd_r, buf, sizeof(buf));

write(fd_w, buf, bytes);

lseek:

off_t lseek(int fd, off_t offset, int whence);

功能:就是对文件访问位置进行定位和偏移

参数:fd 要操做的描述符

       offset  指定的偏移量

   whence 偏移的参考点

          SEEK_SET

  SEEK_END

  SEEK_CUR

返回值:就是偏移以后的位置。

如: lseek(fd, 0, SEEK_END);偏移到文件末尾, 返回值就是文件的大小(字节)。

    lseek(fd, 0, SEEK_SET); 偏移到文件开头。

lseek(fd, 100, SEEK_SET);向文件末尾方向偏移100个字节

lseek(fd, -100, SEEK_END);向文件开头方向偏移100个字节

lseek(fd, 0, SEEK_CUR);返回的当前的访问位置。 

限制: lseek()只对常规文件有效,对socket、管道、FIFO等进行lseek()操做失败。

产生空洞文件:能够起到抢占磁盘空间的做用。

在文件末尾使用lseek继续向后进行偏移时,而且在偏移只有须要写入数据(文件大小会增长)

在文件末尾使用lseek继续向后进行偏移时 ,若是偏移以后没有写入任何数据,那么文件大小不会变。

lseek和O_APPEND:

当使用O_APPEND标志位进行写操做时, lseek的偏移对于写操做无效, 只对读操做有效。

补充:

获取文件属性:

int stat(const char *path, struct stat *buf);

    int fstat(int fd, struct stat *buf);

    int lstat(const char *path, struct stat *buf);

    参数 path: 要操做的文件

         buf: 存放获得文件属性。

目录操做相关:

DIR *opendir(const char *name);

功能:就是得到一个结构体指针。

struct dirent *readdir(DIR *dirp);

功能:读取一个目录

返回值:

int closedir(DIR *dirp);

关闭一个目录流

四.静态库和动态库的分析

本质上来讲库是一种可执行代码的二进制形式。

linux下的库有两种:静态库和共享库(动态库)

静态库在程序编译时会被链接到目标代码中:程序运行时将再也不须要该静态库,所以体积较大。

优势:程序运行时将再也不须要该静态库

缺点:可执行文件的体积较大。

      相同的库可能会须要被屡次加载。

静态库: libxxxxx.a 

动态库:动态库在程序编译时并不会被链接到目标代码中,         

优势: 在程序运行时动态库才会被载入内存,所以代码体积较小。

缺点: 所以在程序运行时还须要动态库存在。

静态库的制做:将功能函数编译成库。

一、先生成目标文件

   gcc -c -Wall fun.c -o fun.o

二、ar crs  libfun.a  fun.o

    将fun.o文件 打包生成libfun.a的静态库

   库的命名:lib库名.a 

使用:   

-L:指定库的路径

-l :指定须要链接的库的名字

   gcc test.c -o test -L .  -lfun 

动态库的制做和使用:

  一、须要生成目录文件

   gcc -c -fPIC -Wall  fun.c -o fun.o

   fPIC:说明库能够被加载到内存的任意位置

  二、 gcc -Wl,-soname,libfun.so  -shared fun.o -o libfun.so.1

     -Wl,-soname,libfun.so  须要链接的库

libfun.so.1  实际生成的库。

库的命名:lib库名.so

三、 ln -s   绝对路径/libfun.so.1    libfun.so

  四、gcc test.c -o test -L . -lfun 

共享库的加载方法:

一、动态库须要被放置到/usr/lib  或者  /lib目录下。 

   只须要将软链接移动过去。

二、将库的路径添加到系统环境变量中

    LD_LIBRARY_PATH

    exprot  LD_LIBRARY_PATH=库的路径

三、将库的路径添加到  /etc/ld.so.conf/xxx.conf 的配置文件中

    sudo  ldconfig  来重启配置文件

用户id转换成用户名

struct passwd *getpwuid(uid_t uid);

passwd-> pw_name

组id转换成组名

struct group *getgrgid(gid_t gid);

group->gr_name

char *ctime(const time_t *timep);

返回值:"Wed Jun 30 21:49:08 1993\n"

printf("%.12s",  buf + 4);

五.进程

1.特色:

    动态、占用内存资源、cpu资源、有生命周期、具备独立的IO状态。

内核中有struct  task_struct 结构体来记录一个进程的全部信息。

程序:静态的、占用磁盘空间。 

PID: 惟一的标识一个进程

⒉  进程的类型:  

交互进程

守护进程:该类进程在后台运行。它通常在Linux启动时开始执行,系统关闭时才结束。

进程的时间片:内核会为每一个进程分配时间片。

进程都是宏观并行,微观串行。

进程的状态:运行态、就绪态、 阻塞态(睡眠等待)

进程运行与操做系统之上:

进程有用户空间模式、内核空间模式。

32位的操做系统上:进程的寻址空间4G(虚拟地址)

一般的空间划分: 3G(用户空间)  1G(内核空间)

进程启动方式: 手动启动、 调度启动  (/etc/crontab 定时运行脚本)

3.进程经常使用命令:

ps ajx

ps aux

"?" 就是守护进程, 不受终端的控制。

stat: 状态

S: interruptible sleep (waiting for an event to complete)可中断的睡眠状态

R: running or runnable (on run queue)可执行状态

Z: 退出状态,成为僵尸进程

D:uninterruptible sleep (usually IO)不可中断的睡眠状态

T:stopped, either by a job control signal or because it is being traced暂停或跟踪状态

X:dead (should never be seen)退出状态,进程即将被销毁

对于BSD格式,传统的状态关键字符:

<:high-priority (not nice to other users)

N: low-priority (nice to other users)

L: has pages locked into memory

s: is a session leader

l: is multi-threaded (using CLONE_THREAD, like NPTL pthreads do)

+:is in the foreground process group前台进程

top:动态显示系统资源使用状况

shift + <

shift  + > 

q:  结束

nice   按用户指定的优先级运行进程

nice的值  -20 ~ 19(优先级最低)

nice ./a.out 默认nice值是10.

nice --11  ./a.out   将a.out的nice值设置为 -11

nice  -11  ./a.out   将a.out的nice值设置为 11

renice   改变正在运行进程的优先级

renice  num  pid  如: renice  -9  1200  , 改变进程的nicenice值为-9.

kill: 给进程发送信号。

kill -l  列出当前系统支持的信号类型。

kill -9  pid

bg  将挂起的进程在后台执行

fg  把挂起的进程放到前台运行

ctrl + Z   用来挂起一个进程。

+-------------------------------------------------+

ctags的使用:

sudo ctags -R   建立索引文件tags

vi -t  pid_t 

ctrl + ]  光标的位置,前进查找该单词

ctrl + O/T  回退

+-------------------------------------------------+

4.进程:建立、控制、销毁、通讯机制。

pid_t fork(void);

功能:建立一个子进程

返回值:fork函数会返回两个值。

        若是返回值 0: 表示进入的是子进程的代码

        若是返回值 大于0 : 表示进入的是父进程的代码 

返回-1, 表示建立子进程失败。 

fork函数的返回顺序不必定。

现代版的fork特色:这就是著名的“写操做时拷贝”(copy-on-write)技术。

                  若是其中任何一个进程试图修改数据,那么进程才会具备各自不一样的物理空间。

pid_t getpid(void);得到当前进程的pid号

pid_t getppid(void);得到当前进程的父进程的pid号。

注意: 必需要注意两个函数的使用场合。

exec函数族 :一系列函数

功能:用于执行一个可执行文件。

int execl(const char *path, const char *arg, ...);

参数:path: 可执行文件路径

  arg: 传递给可执行文件的参数。

  第一个参数必须和可执行文件的名字同样。

  最后一个参数必须是NULL。

好比: execl("/bin/ls", "ls", "-l", "-a", NULL);

返回值:成功返回0, 失败返回-1.

int execlp(const char *file, const char *arg, ...);

如: execlp("ls", "ls", "-l", "-a", NULL);

int execle(const char *path, const char *arg,    ..., char * const envp[]);

如:char *env[3] = {"PATH=/bin/", NULL};

    execle("a.out", "a.out", env);

int execv(const char *path, char *const argv[]);

如:char *arg[4] = {"ls", "-l", "-a", NULL};

    execv("/bin/ls", arg);

int execvp(const char *file, char *const argv[]);

int execvpe(const char *file, char *const argv[], char *const envp[]);

l: list,列表, 就是以列表的形式传参。

v: 传递参数时, 使用数组传递。

p: 表示能够执行存在于系统环境变量中的可执行文件。

e:环境变量的意思   

void exit(int status);

功能:正常退出一个进程。

   参数: status: 用来传递进程的退出状态。

   exit(0);一般表示正常退出

   exit(1);表示失败退出。

   exit 在退出进程时会刷新标准IO的缓冲区

void _exit(int status);

功能:正常退出一个进程。

  参数: status: 用来传递进程的退出状态。

 _exit 在退出进程时会不会刷新标准IO的缓冲区

 孤儿进程:父进程先退出,子进程仍然执行。

           这样会致使子进程被系统的1号进程收养。

 僵尸进程:父进程仍然运行,子进程先退出。

           当父进程退出时,僵尸进程也会消失。 

 处理僵尸进程的方法:

 wait 、waitpid  用来处理退出的子进程。

 第一种方法:

 pid_t wait(int *status);

 功能:就是阻塞等待当前进程中任意一个子进程退出

 参数: status:用来保存退出进程的退出状态。

 返回值:成功就返回退出进程的pid

         失败,返回-1.

 exit(status);

 能够经过 WEXITSTAT(status)来获得子进程退出时的真实状态。

 第二种方法:

 pid_t waitpid(pid_t pid, int *status, int options);

 参数:pid:

       经常使用: pid>0:只等待进程ID等于pid的子进程,无论已经有其余子进程

  经常使用: pid=-1:等待任何一个子进程退出,此时和wait做用同样。

  pid=0:等待其组ID等于调用进程的组ID的任一子进程。

  pid < -1: :等待其组ID等于pid的绝对值的任一子进程。

 status: 等价于wait中的status

      options:

经常使用: WNOHANG:若由pid指定的子进程并不马上退出,则waitpid不阻塞等待,此时返回值为0 。

            WUNTRACED:若是子进程进入暂停执行则立刻返回,但终止状态不予理睬。

            WCONTINUED:

返回值:若是有子进程退出,那么返回退出的pid号

        使用选项WNOHANG且没有子进程结束时:0

 调用失败,返回-1.

wait(status)  ==== waitpid(-1, status, 0); 

第三种方法:???

问题:使用父子进程实现文件拷贝?  

      父进程拷贝前一半, 子进程拷贝后一半。

  一、fork产生一个子进程

  二、open得到源文件、目标文件描述符

  三、统计源文件的字节数

  四、实现为目标文件分配与源文件大小相同的空间

  五、在子进程的代码中先关闭以前继承过来的描述符,而后从新打开两个文件,得到本身的描述符。

  六、两个进程在目标文件的不一样位置开始拷贝。

     read  write函数实现拷贝。

  七、父进程要等待子进程退出,避免僵尸进程。

守护进程

建立方法⑴步骤:

①、建立子进程,父进程退出 (先得到孤儿进程) 

②、在子进程中建立新会话 

    setsid();

    建立一个新会话,而且当前进程变为会话组组长。

    函数可以使进程彻底独立出来,从而脱离全部其余进程的控制。

③、改变当前目录为根目录 

   chdir("/"); 一般将守护进程的工做目录设置为根目录。

④、重设文件权限掩码 

    umask(0);

⑤、关闭不须要的文件描述符 

     int file_num = getdtablesize(); 得到最大的描述符值 + 1;

     for (fd = 0; fd < fdtablesize; fd++)

    close(fd); 

建立方法⑵:daemon()函数

六.线程:

是一种轻量级的进程  

使用多线程的好处:

(1)大大提升了任务切换的效率;

(2)避免了额外的TLB & cache的刷新 

TLB(Translation Lookaside Buffer)传输后备缓冲器是一个内存管理单元用于改进虚拟地址到物理地址转换速度的缓存;TLB是一个小的,虚拟寻址的缓存,其中每一行都保存着一个由单个PTE组成的块。若是没有TLB,则每次取数据都须要两次访问内存,即查页表得到物理地址和取数据。

高速缓冲存储器是存在于主存与CPU之间的一级存储器, 由静态存储芯片(SRAM)组成,容量比较小但速度比主存高得多, 接近于CPU的速度。

Linux里一样用task_struct来描述一个线程。线程和进程都参与统一的调度

一个进程中的多个线程:共享一部分资源的, 也有本身的私有资源。

线程基本操做:

a.建立线程

b.删除线程

c.控制线程

线程头文件:#include <pthread.h>

多线程经过第三方的线程库来实现

gcc pthread_create.c  -lpthread

线程操做函数:

(1) 建立线程

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

   功能:就是建立一个线程。   

   参数:thread: 线程的标识符。

       attr: 用来设置线程的属性(一般不须要设置,直接设置为NULL)attribute

  start_routine: 回调函数,就是线程要执行的函数。

  arg:传递给 start_routine的参数

    返回:成功返回0, 失败返回非负的errno值。 

(2) 回收(释放)线程相关资源

int pthread_join(pthread_t thread, void **retval);

功能:就是阻塞等待一个线程的退出,回收其资源。

参数:thread:指定要等待退出的线程id。

retval: 接收退出线程的状态。

返回:成功返回0, 失败返回非负的errno值。

(3) 退出线程

void pthread_exit(void *retval);

功能:线程退出

   参数:retval  退出状态。    

线程间同步和互斥机制:

    信号量

    互斥锁

    条件变量

互斥锁: 

(1) 初始化互斥锁

int pthread_mutex_init(pthread_mutex_t *restrict mutex,  const pthread_mutexattr_t *restrict attr);

功能:就是初始化一把锁

参数:mutex: 是锁的标识符。

         attr: 用来设置线程锁的属性(一般为NULL)

返回:成功返回0, 失败返回非负的errno值。

用法:pthread_mutex_t  mutex;

          pthread_mutex_init(&mutex, NULL);   

(2) 互斥锁加锁      

int  pthread_mutex_lock(pthread_mutex_t *mutex)   

功能:加锁,是阻塞等待,直到该锁可用为止。

参数:就是初始化后的锁。

返回:成功:0

  出错:-1

(3) 互斥锁解锁

int  pthread_mutex_unlock(pthread_mutex_t *mutex)  

功能:解锁 

参数:就是初始化后的锁。

返回:成功:0

  出错:-1

(4)互斥锁销毁

int pthread_mutex_destroy(pthread_mutex_t *mutex);

功能:就是销毁一把锁

参数:mutex,将要被销毁的锁。

返回:成功:0

  出错:-1

信号量:

基于POSIX的无名信号量:

头文件:#include <semaphore.h>

(1) 信号量初始化

int  sem_init(sem_t *sem,  int pshared,  unsigned int value); 

功能: 信号量值的初始化

参数: sem: 信号量的标识符

           pshared: 一般为0, 表示该信号量应用于线程之间。

                1, 表示该信号量应用于进程之间。

      value:信号量的初始值。

(2) 申请信号量资源(阻塞)

int sem_wait(sem_t *sem);   

功能:用于阻塞申请信号量资源, P操做。

         每执行一次,信号量的值减 1.

参数:要申请的信号量

(3) 释放资源

int sem_post(sem_t *sem);   

功能:释放资源 , V操做

      每执行一次,信号量的值加1.

(4) 申请信号量资源(非阻塞)

int sem_trywait(sem_t *sem);

功能:非阻塞申请资源。

(5) 获取当前信号量的值

int sem_getvalue(sem_t *sem,  int *svalue);

功能:得到当前信号值的值。

参数:sem, 要操做的信号量

svalue, 存放获得的值。

(6) 信号量销毁

int sem_destroy(sem_t *sem);

功能:销毁一个信号量

条件变量:睡眠、唤醒

说明:条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动做:一个线程等待"条件变量的条件成立"而挂起;另外一个线程使"条件成立"(给出条件成立信号)。为了防止竞争,条件变量的使用老是和一个互斥锁结合在一块儿。

(1) 使线程睡眠----等待

说明:必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()的竞争条件(Race   Condition)。mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁(PTHREAD_MUTEX_ADAPTIVE_NP),且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列之前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件知足从而离开pthread_cond_wait()以前,mutex将被从新加锁,以与进入pthread_cond_wait()前的加锁动做对应。   

函数声明:

int pthread_cond_wait(pthread_cond_t *restrict cond,  pthread_mutex_t *restrict mutex);

功能:使线程的睡眠函数     

参数:cond :睡眠的条件。

          mutex: 互斥锁

特色:线程睡眠时须要获得一把锁,在睡眠以后有会将锁释放。

可是,当线程被唤醒时须要从新上锁,若是该锁已经被其余线程占用,那么该线程继续睡眠。

内部操做:

pthread_mutex_lock(&mutex)

pthread_cond_wait()

{

pthread_mutex_unloct(&mutex);

if(条件不知足)

睡觉

else

{

pthread_mutex_lock(&mutex);

return;

}

}

do something();

pthread_mutex_unlock(&mutex);

(2) 唤醒一个指定条件线程----激发

说明:激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个。

int pthread_cond_signal(pthread_cond_t *cond);

功能:只能唤醒一个指定条件线程。

参数:cond

(3) 唤醒全部指定条件线程----激发

说明:激活全部等待线程

int pthread_cond_broadcast(pthread_cond_t *cond);

功能:唤醒全部指定条件线程。

参数: cond

(4) 销毁条件变量

说明:只有在没有线程在该条件变量上等待的时候才能注销这个条件变量,不然返回EBUSY。注销动做只包括检查是否有等待线程

int pthread_cond_destroy(pthread_cond_t *cond);

功能: 销毁一个条件变量

(5)初始化条件变量

int pthread_cond_init(pthread_cond_t *restrict cond,  const pthread_condattr_t *restrict attr);

功能:初始化一个条件变量。

参数:cond: 条件变量

         attr:属性,一般为NULL.

临界资源:多道程序系统中存在许多进程,它们共享各类资源,然而有不少资源一次只能供一个进程使用。一次仅容许一个进程使用的资源称为临界资源。

七.进程间通讯机制:   

⑴system V IPC   其通讯进程主要局限在单个计算机内

⑵BSD   造成了基于套接字(socket)的进程间通讯机制

⑶POSIX 进程间通讯机制。

system V IPC:①共享内存(share memory)

②消息队列(message queue)

③信号灯(semaphore)

传统的进程间通讯方式:④无名管道(pipe)

⑤有名管道(fifo)

⑥信号(signal)

⑦套接字

㈠.无名管道:存在于内存中。

特色:    只能用于具备亲缘关系的进程之间的通讯

          半双工的通讯模式,具备固定的读端和写端

          管道能够当作是一种特殊的文件,对于它的读写可使用文件IO如read、write函数。

int pipe(int fd[2])

功能:建立一个无名管道。

参数:fd.

fd[0] 是无名管道的读端

fd[1] 是无名管道的写端

管道的大小有限制:64KB

测试无名管道的大小?

   无名管道的特性:当管道被填满时, 写操做会被阻塞即write函数不会返回。

pipe在父子进程之间通讯:

 

㈡.有名管道(fifo):能够在没有亲缘关系的进程间通讯。

有名管道能够经过路径名来指出,而且在文件系统中可见。

有名管道的建立:双向的读写。

第一种方法: mkfifo 命令,即mkfifo 文件名

第二种方法: int mkfifo(const char *filename,mode_t mode);

             功能:建立有名管道

             参数:filename:要建立的管道名;mode:管道的访问权限

             返回值:成功返回0, 失败返回-1.

头文件:#include <sys/types.h>   

#include <sys/stat.h>

特色:

    open函数打开fifo文件时:若是一个进程指定的O_RDONLY或者O_WRONLY,那么open此时会阻塞,直到另一个进程以对应的方式打开为止。

    在一个进程中直接打开,能够用O_RDWR标志位。   

fifo它是随着进程维护,当全部操做该管道的进程都结束时,那么管道中的数据就会被丢弃。  

open("fifo", O_WRONLY|O_NONBLOCK);

O_NONBLOCK:以非阻塞的方式打开特殊文件, 之后对该文件的操做都是非阻塞模式。

㈢.信号 signal:

信号是在软件层次上对中断机制的一种模拟(内核模拟),是惟一一种异步通讯机制;信号是由内核产生。信号能够直接进行用户空间进程和内核进程之间的交互,内核进程也能够利用它来通知用户空间进程发生了哪些系列事件。内核进程能够在任什么时候刻发送信号给某一进程,无需知道该进程的状态。若该进程处于未执行态,那么内核就会保存该信号,直到该进程恢复执行态再传给它;若信号被进程设置为阻塞,那么该信号的传递被延迟,直到其阻塞被取消才被传递给进程。

linux支持的信号类型:不可靠信号(有默认处理方式,也能够自定义)、可靠信号(用户自定义处理方式)。由命令kill –l在终端上显示的序号1-31这31个信号为不可靠信号;序号34-64这31个信号为可靠信号。

经常使用信号:

不能够被忽略、阻塞、处理:

SIGKILL:不能够被忽略、阻塞、处理, 接收到该信号的进程只能退出。默认终止。

SIGSTOP:该信号用于暂停一个进程,且不能被阻塞、处理或忽略。

 

SIGALRM:该信号当一个定时器到时的时候发出, 默认操做时终止进程。

SIGCHLD:子进程改变状态时,父进程会收到这个信号,默认忽略。

SIGABORT:该信号用于结束进程。默认终止。

SIGHUP:与终端相关,该信号在用户终端链接结束时发出,一般在终端的控制进程结束时,通知同一会话内的各个进程与控制终端再也不联系。默认终止。

SIGILL:该信号在一个进程企图执行一条非法指令时(可执行文本自己出现错误,或企图执行数据段 堆栈溢出时)发出。默认终止。

SIGFPE:该信号在发生致命的运算错误时发出。默认终止。 

 

按键产生信号:

SIGTSTP:该信号用于暂停交互进程,用户可键入SUSP字符(一般是Ctrl+Z)发出这个信号。

SIGINT:该信号在用户键入INTR字符(一般是Ctrl+C)时发出,终端驱动程序发送此信号并送到前台进程中的每个进程。默认终止。

SIGQUIT:该信号和SIGINT相似,但由QUIT字符(一般是Ctrl+\)来控制。默认终止。

 

用户进程对信号的响应方式:

①忽略信号:对信号不作任何处理,可是有两个信号不能忽略:即SIGKILL及SIGSTOP。

②捕捉信号(用户自定义):定义信号处理函数,当信号发生时,执行相应的处理函数。

③执行缺省操做:Linux对每种信号都规定了默认操做 

与信号相关的函数:

⑴int kill(pid_t pid, int sig);  

功能:给进程发送信号

参数:pid:正数:值就是指定要接收信号的进程的进程号

   0:信号被发送到全部和当前进程在同一个进程组的进程

  -1:信号发给全部的进程表中的进程(除了进程号最大的进程外)

   <-1:信号发送给进程阻号为-pid的每个进程

      sig: 要发送的信号类型

头文件:#include<signal.h>  

#include<sys/types.h>

kill(getpid(), xxx);  能够给本身发送信号。  

返回:成功返回0, 失败返回-1。 

若是sig的值小于0或sig大于64,那么函数kill不能发送信号,执行出错;若是sig等于0,那么函数kill没有发送信号(发送空信号),可是函数执行成功,不会报错,这样的操做能够用于检查一个进程或进程组是否存在。 

⑵int raise(int sig);

功能:给自身发送信号

参数:sig:信号类型

返回值:成功:0 ,出错:-1

头文件:#include<signal.h>

⑶unsigned int alarm(unsigned int seconds)

该函数不会阻塞。

功能:给当前进程设置一个闹钟, 默认在定时时间到达以后进程会退出。

alarm: 在一个进程中只能有一个(最后一个)定时有效。

返回值:成功:若是调用此alarm()前,进程中已经设置了闹钟时间,则

        返回上一个闹钟时间的剩余时间,不然返回0。

   失败: 返回-1.

int pause(void);

功能:阻塞等待任意一个信号。

头文件:#include <unistd.h>

对信号捕捉处理:

signal函数

sigaction函数

signal函数:

⑷typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

功能:用于捕捉、处理信号。

参数: signum: 指定要捕捉的信号类型

       handler; 表明对捕捉到的信号进行自定义处理。

            SIG_IGN,  表示对捕捉到的信号进行忽略。

   SIG_DFL,   表示对该信号进行默认处理。

signal(SIGINT, handler) == SIG_DFL

返回值: 成功返回上一次对信号的处理方式

         失败, SIG_ERR

头文件:#include <signal.h>

system V 的IPC机制(3种):

①共享内存(效率最高)

②消息队列

③信号灯集

ipcs: 用来查看system V的IPC机制标识符的命令

ipcs -m  shared memory segments显示当前系统中 共享内存的使用

     -q  message queues显示当前系统中 消息队列的使用

-s  semaphores显示当前系统中 信号灯集的使用

-a  all (default)显示当前系统中 共享内存,消息队列,信号灯集的使用

-h  显示帮助

-t  time显示时间

-p  pid显示进程号

-c  creator显示建立者

-l  limits显示容量限制,最大或最小

-u  summary显示使用状态

ipcrm 用来删除 当前系统中 system V的IPC机制标识符的命令

     -m  删除当前系统中 共享内存的标识符

     -q  删除当前系统中 消息队列的标识符

-s  删除当前系统中 信号灯集的标识符

如:  ipcrm -m  共享内存的id

⑴.将文件名转换成key值

说明:在IPC(InterProcess Communication)的通讯模式下,无论是使用消息队列仍是共享内存,甚至是信号量,每一个IPC的对象(object)都有惟一的名字,称为(key)。经过,进程可以识别所用的对象。与IPC对象的关系就如同文件名称之于文件,经过文件名,进程可以读写文件内的数据,甚至多个进程可以共用一个文件。而在IPC的通信模式下,经过的使用也使得一个IPC对象能为多个进程所共用。

key_t ftok(const char *pathname, int proj_id);

功能:得到一个key值。

参数: pathname  文件名,文件索引号,通常由命令ls –i 查询文件索引号

       proj_id  就是一个整数(低八为不能为0),因为该int型大小范围0-255,因此通常传入字符。

头文件:#include <sys/types.h>

#include <sys/ipc.h>

如:key_t key;

key = ftok(".", 'a');    

㈣.共享内存:

内存模型:在 Linux 系统中,每一个进程的虚拟内存是被分为许多页面的。这些内存页面中包含了实际的数据。每一个进程都会维护一个从内存地址到虚拟内存页面之间的映射关系。尽管每一个进程都有本身的内存地址,不一样的进程能够同时将同一个内存页面映射到本身的地址空间中,从而达到共享内存的目的。

分配一个新的共享内存块会建立新的内存页面。由于全部进程都但愿共享对同一块内存的访问,只应由一个进程建立一块新的共享内存。再次分配一块已经存在的内存块不会建立新的页面,而只是会返回一个标识该内存块的标识符。一个进程如需使用这个共享内存块,则首先须要将它绑定到本身的地址空间中。这样会建立一个从进程自己虚拟地址到共享页面的映射关系。当对共享内存的使用结束以后,这个映射关系将被删除。当再也没有进程须要使用这个共享内存块的时候,必须有一个(且只能是一个)进程负责释放这个被共享的内存页面。

全部共享内存块的大小都必须是系统页面大小的整数倍。系统页面大小指的是系统中单个内存页面包含的字节数。在 Linux 系统中,内存页面大小是4KB,不过您仍然应该经过调用 getpagesize 获取这个值。

共享存储器的执行方式:是将一个储存器区段标记为共用,这时各进程能够把这个区段映射到该进程自己的虚拟地址里。创建共享存储器可经过shmget系统调用,shmget执行后,核心程序就保留一块指定大小的空间,同时关于此共享存储器的一切数据,如区段的长度,区段的存取权,区段创建者的进程识别码等存入一个叫shmid_ds的结构。如今共享存储器虽然已经创建了,但是仍没法连上它,这时就须经过shmat系统调用获得一个指向共享存储器基址的指针,经过此指针,就能够如同于操做通常存储器似的取用共享存储器。shmdt进行相反的工做,用来脱离已连上的共享存储器。(数据保护)

⑵建立/打开共享内存:

int shmget(key_t key, size_t size, int shmflg);

功能:分配得到一片共享内存,实际只告诉内核,而内核并无分配,只是赞成你获得一个毫无心义的标识符。

参数:key, 用来使用不一样进程使用相同的共享内存。

           IPC_PRIVATE:表示申请的共享内存是私用的。

     key = ftok(xxx,yyy); 须要经过ftok函数来得到。 

 size:进程要申请的共享内存的大小(字节)若是一段进程只申请一块只有一个字节的内存,内存也会分配整整一页(在i386机器中一页的缺省大小PACE_SIZE=4096字节)这样,新建立的共享内存的大小其实是从size这个参数调整而来的页面大小。即若是size为1至4096,则实际申请到的共享内存大小为4K(一页);4097到8192,则实际申请到的共享内存大小为8K(两页)。size---/proc/sys/kernel/shmmax

 shmflg: IPC_CREAT :这个标志表示应建立一个新的共享内存块。经过指定这个标志,咱们能够建立一个具备指定键值的新共享内存块。 

          IPC_EXCL:这个标志只能与 IPC_CREAT 同时使用。当指定这个标志的时候,若是已有一个具备这个键值的共享内存块存在,则shmget会调用失败。

 指定的共享内存的权限:IPC_CREAT|IPC_EXCL| 0666

返回值:成功返回 就是得到的共享内存的标识符。

        失败, 返回-1.

头文件:#include <sys/ipc.h>

   #include <sys/shm.h>   

用法: 

int shmid;

key = ftok(".", 'a');   

shmid = shmget(key, 1000,IPC_CREAT|IPC_EXCL|0666); 建立

shmid = shmget(key, 1000,0666)打开共享内存:

⑶共享内存的映射

void *shmat(int shmid, const void *shmaddr, int shmflg);

参数:  shmid 共享内存标识符

        shmaddr:一个指针,指向您但愿用于映射该共享内存块的进程内存地址,若为 NULL表示让系统为用户自动分配内存

         非NULL表示用户自定义空间

   shmflg:  SHM_RDONLY   表示对共享内存的操做,只能够读。

 SHM_RND表示第二个参数指定的地址应被向下靠拢到内存页面大小的整数倍

         0  表示可读可写

返回值:成功返回映射后的共享内存块对应的地址。

        失败返回NULL。  

头文件:#include <sys/ipc.h>

#include <sys/shm.h>

⑷解除映射:

int shmdt(const void *shmaddr);

功能:解除映射。

参数:就是shmat的返回值

成功返回0, 失败返回-1.

头文件:#include <sys/types.h>

#include <sys/shm.h>

⑸控制共享内存:

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

功能:共享内存的控制函数

参数:shmid: 要操做的共享内存的标识符

cmd:  IPC_STAT  (获取对象属性), 传递一个指向一个 struct shmid_ds 对象的指针做为第三个参数

             IPC_SET (设置对象属性),第三个参数存放将要设置的属性

             IPC_RMID (删除对象), 此时第三个参数为NULL。

返回值:成功返回0, 失败返回-1.

头文件:#include <sys/ipc.h>

   #include <sys/shm.h>

system函数:用于执行shell命令

 

int system(const char *command);

参数:command  要执行的命令

system("ipcs -m");

system V消息队列:

#include <sys/msg.h>

一、消息队列的建立、打开

int msgget(key_t key, int flag);

参数:key, ftok得到值。

      flag: IPC_CREAT

        IPC_EXCL  

msgget(key, IPC_CREAT|IPC_EXCL|0666);

返回值:消息队列的标识符。

二、int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

功能:往一个消息队里中发送消息。

参数: msqid: 要操做的消息队列的标识符--(对象)

       msgp: 存放要发送的消息。指向要发送的消息所在的内存

       msgp的定义要求: struc msgbuf

                     { 

   long type;//必须有,表示消息的类型

   消息的数据

 }

        msgsz:表示发送的消息的正文的长度(除去消息类型以外)。

  sizeof(struct msgbuf) - sizeof(long);

  

msgflg:决定了消息的发送是不是阻塞方式

                IPC_NOWAIT   非阻塞方式发送

                0          阻塞等待直到发送成功

返回值:成功返回0, 失败返回-1

三、ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,  int msgflg);

功能:从消息队列中读取一条消息。

参数: msqid: 要读取的消息队列

       msgp: 接收消息的缓冲区

                             struc msgbuf

                     { 

   long type;//必须有,表示消息的类型

   消息的数据

 }   

       msgsz: 要接收的消息的正文长度。

       msgtyp: 要读取的消息队列中的消息类型。  

               0,  表示读取消息队列中的第一条消息。

                > 0, 表示读取消息队列中类型为msgtyp的消息。

                < 0, 接收消息队列中类型值不小于msgtyp的绝对值且类型值又最小的消息。

 

msgflg:决定了消息的接收是不是阻塞方式

                IPC_NOWAIT   非阻塞方式接收

                0          阻塞等待直到有对应消息

返回值:

        失败返回-1

        成功返回读取到消息的正文的字节个数。

四、int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );

功能:消息队列的控制函数

参数:msgqid   要操做的消息队列的标识符

 cmd:       

            IPC_STAT  (获取对象属性), 存放在第三参数中

            IPC_SET (设置对象属性),第三个参数存放将要设置的属性

            IPC_RMID (删除对象), 此时第三个参数为NULL。

 

成功返回0, 失败返回-1.

system V的 信号灯集:以一种计数信号灯

#incldue <sys/sem.h>

int semget(key_t key, int nsems, int semflg);

功能:建立或者打开一个信号灯集合

参数: key   ftok得到值。

       nsems: 表示要申请的信号灯集合中包含的信号量的个数。

   semflg: IPC_CREAT

            IPC_EXCL

semget(key, 2, IPC_CREAT|IPC_EXCL|0666);

返回值:返回一个信号灯集合的标识符。

int semctl(int semid, int semnum, int cmd, ...);

 功能:对信号灯集合的控制

 参数: semid

        semnum: 要操做该集合中信号量的编号, 信号量的编号从0开始。  

         cmd:   

       GETVAL:获取信号灯的值

           SETVAL:设置信号灯的值             semclt(semid, 0, SETVAL, 共用体);

           IPC_RMID:从系统中删除信号灯集合   semctl(semid, 0, IPC_RMID);

   

第四个参数:

union semun {

                int              val;    /* Value for SETVAL */

                struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */

                unsigned short  *array;  /* Array for GETALL, SETALL */

                struct seminfo  *__buf;  /* Buffer for IPC_INFO

                                           (Linux-specific) */   

};

返回值:成功返回0,失败返回-1.

int semop ( int semid, struct sembuf  *opsptr,  size_t  nops);

功能:信号灯 集合的PV操做。

参数:semid

      struct  sembuf{

           unsigned short sem_num; /* semaphore number */  要操做的信号量的编号。

           short          sem_op; /* semaphore operation */ 要执行的PV操做

           short          sem_flg;/* operation flags */

   };  

sem_num成员:要操做的信号量的编号。 

        semop成员:  0 等待,直到信号灯的值变成0

         : > 0  表示信号量的值要增长semop个, 至关于V

 : < 0  表示信号量的值要较少semop个, 至关于P

flag:

         0, semop函数阻塞操做。 

        IPC_NOWAIT, semop函数阻塞操做。

SEM_UNDO(不经常使用)

        nops:semop函数执行一次要操做的信号量的个数。

返回值:成功返回0,失败返回-1.

问题:

使用syetem V的信号灯集合实现对共享内存的互斥操做?

要求:两个C文件(一个读内存、一个写内存)

  须要两个信号量:一个控制读内存, 要申请读的信号量资源(0)。

                  一个控制写内存 ,要申请写的信号量资源(1)。 

处理僵尸进程的第三种方法(可移植性较差):

                        SIGCHLD

                        signal(SIGCHLD, SIG_IGN);

相关文章
相关标签/搜索