linux基础:文件io

文件IO

文件IO 1

一:标准(C库函数标准)Io 5

一〉文件 5

1. 系统调用与库函数的区别 5

2.文件定义 6

二〉标准IO 6

1.介绍 7

1.1标准IO定义 7

1.2标准IO的优点 7

2. 文件指针(流) 7

3. 8

4.缓冲 8

4.1分类 8

4. 计算标准IO缓存的大小 10

4. 文件函数 10

5.1文件打开函数 11

5.2文件关闭函数 11

5.3:读流--字符IO 12

5.4.行读出IO(数据还在原文件中) 13

缓冲区与缓存的本质区别 13

5.5行输出IO 14

5.6错误信息打印函数 14

作业:文件内容复制 15

文件行数计数 16

5.7二进制IO 16

作业:文件拷贝(利用返回值作为循环判断条件) 17

5.8文件指针定位函数 17

5.9默认打开的文件指针 18

5.10文件重定向 19

作业:用标准IO计算最多可打开多少个文件 19

作业:每个一秒打印系统时间--周末联系 20

二〉文件IO(系统调用函数) 20

1. 定义 20

2. 文件描述符(一切皆文件,需要区分) 20

1. 文件IO函数 21

3.1文件打开函数 21

5.2文件关闭函数 22

5.3文件内容读取函数 22

5.4文件内容写入函数 22

注:LINUX串口一次不能全部读取串口内容 23

作业: 23

5.5文件定位函数 23

标准IO与文件IO的区别 24

四〉目录 24

1. 目录操作函数 24

1.1目录打开函数 24

1.2目录读取函数 25

1.3关闭目录函数 26

1.4文件信息查看函数 26

1.5文件属性函数 27

五〉库的制作 28

1. 什么是库 28

2. 静态库(省时间,花空间) 28

3. 制作静态库 29

7. 使用共享库 31

并发程序设计: 31

>:多进程 31

1. 预备知识 31

**1.1面试-----进程与程序的区别 31

*****1.2进程类型 32

1.3进程状态 33

1.4进程模式 33

1.5启动进程 34

1.6调度进程 34

***2进程的系统调用 35

2.1fork 35

2.2多进程特点: 35

*****2.3forkvfork的区别----查看实例 37

2. exec函数族 37

头文件:<unistd.h> 38

2.1execlexeclp 38

3. Wait(),witpid()---解决僵尸进程 39

4.守护进程(Daemon 40

5.exit_exit 42

***6.进程间如何解决资源抢占问题 42

作业: 42

二〉多线程 44

1. 线程基础 44

线程与进程执行的区别 44

1.1定义 45

1.2优点与特点 46

1.3多线程实现 46

1.4线程资源 46

1.5第三方线程库 46

1.5.1创建线程 46

1.5.2:阻塞进程等待线程结束,自动回收资源 47

1.5.3结束线程 47

1.5.4:取消线程 47

1.5.5:线程分离函数 48

作业:在一个进程中的两个线程实现 48

1.6线程池 48

1.6.1简介 48

1.6.3实现代码 49

2线程间同步和互斥机制 61

1. 线程间机制 61

2. 线程间同步 61

2.1同步机制 61

2.2信号量 62

----决定线程是继续运行还是阻塞等待 62

2.2.1定义 62

2.2.2访问方式 62

1) 初始化 62

2P 操作(申请资源(消费者)--信号量-1 62

Int sem_wait(sem_t * sem)//阻塞等待申请资源 62

3V操作(释放资源(生产者)--信号量+1 63

3. 线程间互斥 63

3.1互斥锁: 63

1) 目的 63

2)互斥锁定义 63

3)临界资源 63

(4)临界区 63

5) 函数 63

A:定义互斥锁 63

B:<初始化化互斥锁> 64

C:<申请互斥锁> 64

D:〈释放互斥锁〉 64

4.线程间如何解决资源抢占问题 64

作业1;锁粒度 64

作业2:产生死锁的原因,如何避免死锁 65

作业3:使用多线程实现图片拷贝 65

4. 进程间通信 67

1. 进程间通信简述 67

2.传统进程间通信方式 68

*****2.1无名管道 68

2.1.1:特点 68

2.1.2:创建与关闭 68

1) 创建管道---fork之前创建 68

2)管道读写 68

读写数据特点: 69

2.1.3管道缓冲区的大小 69

****2.2有名管道 69

2.2.1特点与简介 69

2.2.2函数 70

1) 创建管道文件(p 70

2.3:信号 70

2.3.1:定义 70

2.3.2:特点 70

2.3.3生存周期 70

2.3.4处理流程 71

2.3.5使用场合 71

2.3.6经常使用的信号 71

2.3.7发送与捕捉 71

1> kill()  raise() 71

2alarm()  pause() 72

2.3.8用途 72

2.3.9处理 72

*******1signal函数 72

2. IPC对象 72

2.1共享内存 73

2.1.1特点 73

2.1.2实现 73

1) 创建/打开共享内存 73

2映射共享内存 74

3) 撤销共享内存映射 75

4) 删除共享内存对象 75

2.2消息队列(msg) 75

2.2.1定义与特点 75

2.2.2操作(队列操作) 76

1)创建或打开消息队列 76

2) 添加消息 76

3) 读取消息(队头取出) 77

4) 控制消息队列 78

2. 3信号灯/量 78

2.3.1种类 78

3. 2系统5的信号灯 78

1) 定义: 78

2)函数 78

1〉创建信号灯 79

2) P/V操作--申请/释放资源 79

3) 管理信号灯 80

 

 

IO---输入输出

一:标准(C库函数标准)Io

一〉文件

1. 系统调用与库函数的区别

系统调用:(是什么)

用户空间进程访问内核的接口,是操作系统的一部分

(为什么)

将用户程序从底层的硬件编程中解放出来

使用户程序具有更好的可移植性

极大的提高了系统的安全性

(什么时候使用)

库函数:为实现某种功能而封装在内核的API集合

提供统一的编程接口,更加便于应用程序的移植

是语言或应用程序的一部分

 

2.文件定义

 

狭义文件:硬盘上存储的东西(数据文件)

广义文件:一切皆文件

目录:归类普通文件

块设备:管理大数据

符号连接:windows下的图标,快捷方式

二〉标准IO 

(一般指普通文件)

1.介绍

1.1标准IO定义

ANSI CC语言标准)中定义的用于I/O操作的一系列函数

1.2标准IO的优点

使用户程序具有更好的可移植性

可以减少系统调用的次数,提高系统效率-----在用户空间创建了缓冲区,读写时先操作缓冲区,在合适的时机再通过系统调用访问实际的文件

2. 文件指针(流)

定义:当用标准IO打开一个文件时就会创建一个FILE结构体描述该文件,该FILE结构体就形象的称为流

 

内核用来描述存储文件信息的结构体指针(FILE结构体)

 

2.1提出理由:

2.1.1有多个函数操作的是同一个对象,则必须提出标识操作对象的概念(文件指针)

2.1.2需要一个东西(文件指针)来保存每次操作(读写)文件当前信息。2.1.3文件指针(一次将文件读入内存(buffer))可新增一个缓存(内存数据区),增加读写速度

保存:内存回写到硬盘  文件读写在内存中,因为读写快,当内存与硬盘读写速度不匹配时,需要缓存。

 

3. 

文本流/文件(文本文件):操作过程中数据以字符存在(以记事本可以打开,且不为乱码的文件)在流中处理的数据是以字符出现 ,存储编码的文件

 二进制流/文件(数据文件)操作过程中数据以二进制存在用记事本打不开的文件,图片,exe可执行文件等(相同的二进制序列根据编码规则不同,相同的文本,输出的结果不同,“中国”用GB23与国外编码规则输出的16进制数不同)

 

.dat文件是程序使用的数据,它只是开发者为了清楚文件的意思而自己定义的,也可以命名成别的扩展名

 

 

文本文件---对应的都是字符(GB2312编码中文占3个字节)

整型0=0

读写文件写在一个程序时,需要自己定位---将文件指针返回到文件开头

读文件结束是通过文件内指针所指字节与文件的字节大小相匹配

4.缓冲

4.1分类

文件IO不带缓存(一般指相应设备:键盘输入直接回显),标准IO带缓存,Fllush--强制刷新

 

 

全缓存:缓冲区满,文件关闭,强制刷新  文件才会存储-32ubntu全缓存为4096

行缓存:缓冲区满,遇到回车,强制刷新,文件关闭,文件内容会存储----刷新缓冲区,32ubntu行缓冲为1024

无缓存:没有缓存,输入/输出立即完成,stderr(标准错误输出)

 

 

4.计算标准IO缓存的大小

#include <stdio.h>

int main(int argc, char *argv[])

{

//标准输入大小,没有输入内容时,标准输入缓冲是测不出大小的

    getchar(); //在终端获取一个字符,标准输入内没有内容时,大小为0,存在数据时才会测试出其大小

    printf("stdin_size:%d\n",stdin->_IO_buf_end - stdin->_IO_buf_base);//1024

    

    //标准输出大小

    printf("2\n");

    printf("stdout_size:%d\n",stdout->_IO_buf_end - stdout->_IO_buf_base);//标准输出缓冲区大小1024

 

    //标准出错1024

    printf("stderr_size:%d\n",stderr->_IO_buf_end stderr->_IO_buf_base);//标准出错缓冲区大小

 

    //全缓冲,只有对文件进行读写操作,buf才会为非0//4096

    //文件流,创建一个文件流 ,追加方式写入时默认文件中存在内容

    FILE *fp = NULL;

    // w 方式每次打开,先把文本流清空在向里面写入数据

    if((fp = fopen("1.txt", "w")) != NULL)

    {

        //把文件的大小输入到文件中,开始文本中并没有数据查看文件时,其内容为0,写入数据后在通过printf打印其大小就会显示4096

        fprintf(fp,"%d\n",fp->_IO_buf_end - fp->_IO_buf_base);

        printf("fp_size: %d\n",fp->_IO_buf_end - fp->_IO_buf_base);

    }

}

 

4. 文件函数

#include <stdio.h>//标准输入输出头文件

 

头文件:函数原型声明---编译时编译器可检查函数是否用错

.:当前路径别

  vim在环境变量文件中已经配置好了,输入vim直接接在path路径下寻找

返回值:一般情况下是利用来报错

5.1文件打开函数

 

FILE:存放文件信息的结构,并创建一个缓存空间来存放

 

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

头文件:函数的原型声明

 

/*

 *功能:打开/创建打开文件(一般用于普通文件)

 *参数:b操作文本文件  当给定b参数时,表示操作二进制文件

const char *path - 带路径的文件名

如果没有指定路径,那么就是当前目录

const char *mode - 操作的方式

 

 * 返回值:

成功:文件指针(抽象打开的文件)---

失败:NULL(仅仅是说在错了)--并返回错误信息 错哪里呢?全局变量errno保存了错误码,打印标准错误信息perror()

 */

5.2文件关闭函数

int fclose(FILE *fp)

/*

 *功能:关闭文件(一般用于普通文件)

 *参数:

FILE *fp - 文件指针--指向内核描述文件信息的结构体,关闭fp则关闭相关文件

 * 返回值:

成功:0

失败:EOF, 错哪里呢?全局变量errno保存了错误码,打印标准错误信息perror()

 */

 

 

5.3:读流--字符IO

 

Feoffp--判断文件是否到文件末尾,到文件尾返回非0

Ferror(fp)--判断文件是否出错,出错返回EOF

 

While( !feof(fp) && ferror(fp) != EOF)

{

Char c=Fgetc(fp);

}

 

重点fgetc()

 

Fputc/fgetc----输入/输出从标准输入/输出,也可向文件流输入输出

Eg:fputc(fgetc(stdin),stdout)

5.4字符写入IO

 

 

5.4.行读出IO(数据还在原文件中)

fgets重点()

char *fgets(char *s, int size, FILE *stream);//每次操作一行数据,Null结束,(size<一行数据,实际读取size-1)

失败或读到文件末尾返回NULL,成功返回S,

当读完一行数据时,s的最后一个有效字节为\n,fgets自动返回

Gets--从标准输入读  fgets--从指定文件

//!!!注意:不安全版本,因为读出的内容是存在参数s指针指向的首地址,但是没有说缓存的长度,则可能会越界

char *gets(char *s);

// 向输出终端输出字符串,输出时加回车

int puts(const char *s);

 

 

缓冲区与缓存的本质区别

,缓冲(buffer)技术和缓存(cache)技术都是十分重要的,它们对于提高IO吞吐效率是非常关键的。但是缓冲和缓存是一对不同的概念的。缓冲,主要是用于传输效率不同步或优先级别不相同设备之间传输数据,一般是先将一方数据临时存放,然后待时机合适时再将数据统一发送到另一方,从而降低了系统的等待时间。而缓存主要是将在传输速度比较高的设备中为传输速度比较低的设备开辟一定的空间,用于存放速度低设备中数据的副本,这样当要访问数据时,就可以从速度快设备访问得到,无需访问低速度的设备了,这样一来就提高了数据的访问效率。

  

 

 

5.5行输出IO

 

5.6错误信息打印函数

void perror(const char *s);---输出errno对应的错误信息与用户信息--用户存在或不存在

/打印标准的错误信息:参数在打印输出前附加信息

#include <errno.h>

Strerror(errno);---仅仅映射errno对应的错误信息

int errno;   //保存当前操作的错误码

 

作业:文件内容复制    

fgets/fputs----以返回值作为循环结束条件 fgetc/fputc---feof()ferror()作为循环结束条件)

 

 

文件行数计数

 

 
5.7二进制IO

 

文件内容读取函数---每次操作一个结构数据(char )

Size_t==int

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

*

 *功能:从文件指针指定的文件中把内容读出(一般用于普通文件)

 *参数:

void *ptr - 保存读出数据的缓存首地址

size_t size - 单个数据块的字节数(类似数组成员类型的效果)

size_t nmemb - 想读的数据块的数量(类似数组的成员数量的效果)

缓存大小(字节数):size*nmemb

FILE *stream - 文件指针(指向被打开的读的文件)

 

 * 返回值:

成功:实际读的数量,实际读的对象数,第三个参数

失败或文件末尾0或负数

 */

 

向文件写入内容的函数--每次操作一个结构数据

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

 

*

 *功能:向文件指针指定的文件中写入内容(一般用于普通文件)

 *参数:

const void *ptr - 写入数据的缓存首地址

size_t size - 单个数据块的字节数(类似数组成员类型的效果)

size_t nmemb - 想写的数据块的数量(类似数组的成员数量的效果)

缓存大小(字节数):size*nmemb

FILE *stream - 文件指针(指向被打开的要写的文件)

 

 * 返回值:

成功:实际写的数量(一般的是想写多就要写的)

失败:0或负数

 */

作业:文件拷贝(利用返回值作为循环判断条件)

 

5.8文件指针定位函数

 

文本文件---对应的都是字符(GB2312编码中文占3个字节)

 

 

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

// *功能:设置文件指针位置(一般用于普通文件),stream流对应的内核操作文件的指针(非文件指针),whence为基准点,偏移offset

 *参数:

FILE *stream - 文件指针

long offset - 偏移字节数

int whence - 相对位置

SEEK_SET - 文件头位置

SEEK_END - 文件尾位置

SEEK_CUR - 文件中指针当前位置

 

 * 返回值:

成功:0

失败:-1,错误码在errno

 */

Int  ftell(FILE *stream)

 

能:计算流对应文件中指针的位置,(定位这个指针)

返回值:

成功:指针位置

失败:-1

 

作用:计算文件大小/图片打码/提取轮廓/定位所想提取的内容/

 

输出函数:

Fprintf();

Len=2

Sprinf(buf,len=%d,len);//len=2语句写入到buf地址上中-----用在数据装载(将整数写入字符串)

Snprintf(char  *buf,char *format,...);

Len=13;

Snprintf(buf,15,len=%d,len);//len=13,这条语句的前15个字节数据写到buf中;

5.9默认打开的文件指针

用户程序加载可以通过shell终端,shell终端程序已经打开的文件指针,那么用户程序就继承这些文件指针

 

标准输入:stdinPC对应的就是键盘

标准输出:stdoutPC对应的就是显卡管理的显示器

标准报错:stderr,逻辑设备,输出错误信息

 

5.10文件重定向

 

输出重定向:’〉‘  freopen:文件重定向-------用于多个文件在执行时,将输出结果放到指定文件

 

FILE *freopen(const char *path, const char *mode, FILE *stream);(将文件指针所指的文件内容指定输出到指定路径的文件)

*

 *功能:文件重定向

 *参数:

const char *path - 文件名

const char *mode - 读写方式

FILE *stream     - 文件指针

 

 * 返回值:

成功:文件指针

失败:-1,错误码在errno

 */

 

 

NULL是一个宏值,LINUX下值为0,不同的系统下值不同也可报错,仅仅报错

perror可以打印错误信息,error全局变量存储错误信息

保存数据的内存空间描述:缓存首地址+空间长度

想读的数据块的数量------因为不知道文件本身有多大

失败---没有读到数(本身有3个字节,但你想读的最小字节为4个字节,读取失败)

 

 

作业:用标准IO计算最多可打开多少个文件(不管是否为同一文件)

 

作业:每个一秒打印系统时间--周末联系

 

二〉文件IO(系统调用函数)

LINUX下的文件分为6---普通文件 设备文件(字符/块) 目录文件 符号链接文件 管道文件 套接字文件

1. 定义

不带缓存的IO,系统调用

 

2. 文件描述符(一切皆文件,需要区分)

当前系统未用的最小非负整数

--最多有1024个,标识正在访问的文件

 

2.2标准输入 标准输出 标准出错

0/1/2

 

 

1. 文件IO函数

UNIX继承而来,在UNIX下也可用

错误码的宏定义所在文件目录:/usr/include/asm-generic/errno-bash.h

 

3.1文件打开函数

//所在头文件

#include <sys/types.h>//宏定义变量的头文件

#include <sys/stat.h>

#include <fcntl.h〉

 

int open(const char *pathname, int flags);

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

/*

 * 功能:打开文件(两个参数)/创建打开(三个参数)(一般针对设备使用)

 * 参数:

const char *pathname - 文件名(可包含路径)

int flags - 操作方式

O_RDWR | O_FREAT | O_TRUNC:文件存在则清空文件,不存在则创建,以可读可写方式操作

O_RDONLY - 只读打开

O_WRONLY - 只写打开

O_RDWR   - 可读可写打开

 

O_APPEND - 追加

O_CREAT  - 创建文件

O_EXCL   - 文件存在不能创建(提醒文件是否存在)

O_LARGEFILE - 大文件支持

O_TRUNC - 如果文件存在,则清空内容

...

mode_t mode - 创建文件时,创建的文件就存在创建者,所属群组,其他账号(文件权限)

 * 返回值

成功:>=0 非负的整型值,文件描述符

失败:-1(系统调用出错都是-1,其错误码在errno中)

 */

5.2文件关闭函数

#include <unistd.h>

int close(int fd);

5.3文件内容读取函数

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

 

#include <unistd.h>//头文件

 

/*

 * 功能:从fd对应的文件中读取count个字节数据到buf地址上

 * 参数:

 fd - 文件描述符

buf 存放读取出来的数据的地址

count - 要读取字节数

 * 返回值

成功:实际读出的字节(当文件原有数据<count

失败:-1并设置错误信息(系统调用出错都是-1,其错误码在errno中)

末尾:返回0

注:循环读取结束条件-----while(read() > 0)

5.4文件内容写入函数

 

ssize_t write(int fd, const void *buf, size_t count);//当想写count大于实际的个数剩余的以乱码的形式写入

*

 * 功能:buf地址上的前count个字节写入fd对应的文件中

 * 参数:

int fd - 文件描述符open函数的返回值

const void *buf, size_t count - 写入数据的缓存

buf存放写入文件的数据来源地址 

count - 要写入的字节数

 * 返回值

成功:>=0 实际写的字节

失败:-1(系统调用出错都是-1,其错误码在errno中)

 */

注:LINUX串口一次不能全部读取串口内容

https://blog.csdn.net/u012123768/article/details/81949168

while(count < length/2){//保证能将数据读完,判断作用

ret = read(fdr,p+count,length/2-count);

if(ret < 0){

perror("read");

return -1;

}

count += ret;//保存读取的字节数}

Buf[max];//当长度为变量时不能初始化

作业:

实现图片马赛克打码(lseek==fseek+ftell  read write open close

拷贝图片(read/write--char buf[max]错的,max不能为变量.(int max = lseek(fp,0,SEK_END)) ;char *buf=char *malloc(max);read(fp,buf,max);write(fd,buf,max);)--可以但是浪费堆区空间,大才小用

 

5.5文件定位函数

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

#include <sys/types.h>

#include <unistd.h>

/*

 * 功能:文件定位:设置文件指针的位置

 * 参数:

int fd - 文件描述符

off_t offset - 偏移量

int whence   - 相对位置

SEEK_SET - 文件开始

SEEK_CUR - 文件当前位置

SEEK_END - 文件尾巴

 * 返回值

成功:移动后的文件位置(字节数)

失败:-1(系统调用出错都是-1,其错误码在errno中)

 *

_exit(1);退出程序,不做回收清理

Printf(sadasfsd);行缓存没有输出条件

Fflush(stdout);强刷

标准IO与文件IO的区别

1)文件IO又称为低级磁盘IO,遵循POSIX相关标准

标准IO又称为高级磁盘IO,遵循ANSI C标准

2)文件IO无缓冲--读写文件时都会执行相关的系统调用,增加系统开销

 标准IO有缓冲--首先读取缓冲区,减少系统调用此时,系统开销较小

3)文件IO用文件描述符表示一个打开的文件

标准IO用文件指针(FILE流)表示一个打开的文件

4)文件IO可以访问不同类型的文件

标准IO只能访问普通文件

 

标准IO:有缓存,全缓存(缓存满、fflush()、关闭文件、程序退出), 行缓存(遇到回车,fflush()、关闭文件、程序退出)

文件IO:没有缓存

       没缓存----用户操作与底层代码同步

    缓存-----一种标准,可用于从硬盘读取(行缓存  全缓存)(自己  可以设置缓存规则)缓存的目的-----加快处理速度(CPU 理速度快,而硬盘的处理慢),使软件等更流畅

 

四〉目录

1.目录操作函数

#include <sys/types.h>

#include <dirent.h>

DIR 目录指针(利用其对目录操作) -一个结构体

1.1目录打开函数

 

DIR *opendir(const char *name);

目录名文件名个数都有上限(d_name[256])

文件打开次数上限测试 最多可打开1024个文件(文件可相同可不同) 文件描述符0-1023

 

/*

 * 功能:打开目录

 * 参数:

const char *name - 指向要打开的目录名

 * 返回值

成功:目录指针

失败:NULL,错误原因是errno  打开目录--失败一般是因为路径不对

 

 */

 

1.2目录读取函数

 

struct dirent *readdir(DIR *dirp);

读文件一般为以下三种:目录 符号链接 普通文件

读完或目录没东西:返回NULL

读到了:返回文件类型与名字

 

读目录    (打开文件数有上限)

1〉读目录目录(1)(默认按时间排序打印出)(2)可以自己设定排序的规则(字母名,等)

2〉遍历所有目录(需要过滤当前目录与上一目录的别名--.和..)  

遍历一个目录下的多级目录--递归(层级容易栈溢出)

栈区--记录函数调用前的位置,递归会不断压栈 

解决方法:不断的malloc free

Snprintf(sudirname,%s/%s,dirname,info->d_name

字符串格式化函数,将目录的上下目录都打印出来

Readdir//只能知道文件名与文件类型

Linux 下只有7类文件

.当前目录别名  ..上一目录的别名

 

/*

 * 功能:读目录

 * 参数:

DIR *dirp - 目录指针

 

 * 返回值

读到了:struct dirent

读完了:NULL

 */

struct dirent *readdir(DIR *dirp);

 

结构体说明:

struct dirent {

#if 0

d_type文件类型:

DT_BLK      块设备This is a block device.

DT_CHR      字符设备This is a character device.

DT_DIR      目录This is a directory.

DT_FIFO     管道This is a named pipe (FIFO).

DT_LNK      符号链接This is a symbolic link.

DT_REG      普通文件This is a regular file.

DT_SOCK     套接字This is a UNIX domain socket.

DT_UNKNOWN  The file type is unknown.

#endif

unsigned char  d_type;

char           d_name[256]; /* "文件"*/

...

};

 

1.3关闭目录函数

int closedir(DIR *dirp);

 

 

/*

 * 功能:关闭目录

 * 参数:

DIR *dirp - 目录指针

 * 返回值

失败:-1,错误码在errno

 */

1.4文件信息查看函数

通过&操作返回对应的类型宏

真正的权限权限二进制:~掩码&权限二进制数 

计算机中只有二进制:10进制左移一位*10右移一位/10,类似2进制左移一位*2,右移一位/2

置0:0‘&’  置1:1 ‘|’

 

 

查找文件权限:info->st_mode & 0700

st_mode结构体中包含文件类型与文件权限

利用‘|’可将多个权限组合判断

 

1.5文件属性函数

ld--查看usr grp所对应的ID

Lsc  -al  filename:查看文件属性

32位系统4字节对齐,位宽为4,当输出为%llu时会自动将位宽增大到16个字节

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

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

 

#include <sys/types.h>

#include <sys/stat.h>

#include <unistd.h>

 

/*

 * 功能:得到文件属性

 * 参数:

const char *pathname - 输入参数

指向文件名的指针(带路径,如果没有路径,默认是当前)

struct stat *buf - 输出参数

文件属性结构

 * 返回值:

成功:输出参数buf指向有效的文件信息结构

失败:-1,错误码在errno

 */

 

struct stat {

mode_t    st_mode;        /* 类型+权限protection */

uid_t     st_uid;         /* 用户IDuser ID of owner */

gid_t     st_gid;         /* IDgroup ID of owner */

off_t     st_size;        /* 字节数total size, in bytes */

 

//struct timespec == time_t;文件时间信息

struct timespec st_atim;  /* time of last access */

struct timespec st_mtim;  /* time of last modification */

struct timespec st_ctim;  /* time of last status change */

 

#define st_atime st_atim.tv_sec      /* Backward compatibility */

#define st_mtime st_mtim.tv_sec

#define st_ctime st_ctim.tv_sec

};

 

类型测试

st_mode,测试宏

S_ISREG(m)  返回非0则是普通文件,0则不是

        S_ISDIR(m)  directory?

S_ISCHR(m)  character device?

S_ISBLK(m)  block device?

S_ISFIFO(m) FIFO (named pipe)?

S_ISLNK(m)  symbolic link?  (Not in POSIX.1-1996.)

S_ISSOCK(m) socket?  (Not in POSIX.1-1996.)

 

//判断是否可写可读可执行

if(S_IRWXU == (stat->st_mode & 00700))

//判断是否可写可读

if((S_IRUSR | S_IWUSR) == (stat->st_mode & 00700))

S_IRWXU     00700   owner has read, write, and execute permission

S_IRUSR     00400   owner has read permission

S_IWUSR     00200   owner has write permission

S_IXUSR     00100   owner has execute permission

S_IRWXG     00070   group has read, write, and execute permission

S_IRGRP     00040   group has read permission

S_IWGRP     00020   group has write permission

S_IXGRP     00010   group has execute permission

S_IRWXO     00007   others (not in group) have read,  write,  and

       execute permission

S_IROTH     00004   others have read permission

S_IWOTH     00002   others have write permission

S_IXOTH     00001   others have execute permission

 

//通过文件描述符得到文件信息

 

五〉库的制作

1.什么是库

  c语言,面向过程设计的函数库

预先编译好的,没有入口函数,提供一堆函数的可执行代码

2.静态库(省时间,花空间)

编译时候,把库提供的代码链接(拷贝一份)进程序中,编译依赖,运行不依赖

3.制作静态库

# gcc -c file1.c file2.c ...//完成宏替换  预处理  检查语法错误

结果file1.o file2.o

# ar csr libdemo.a file1.o file2.o

可执行代码静态库libdemo.a

 

命令规范:lib库名.a

注:gcc -E filename.c:预处理将头文件的程序链接到相要执行的文件(静态库编译时,将相应的程序拷贝到本文件中)

 

 

4.使用静态库

如果没有将库放入编译器默认的头文件下,就用如下命令指定库,再使用

# gcc test.c -I  /home/test  -L  /home/test  -ldemo

-I 指定头文件路径

-L 指定库路径

-l 指定库名

 

如果库名不符命名规范,假设库文件名是demo.a,在/home/test目录

# gcc test.c  -I  /home/test /home/test/demo.a

 

5.共享库(节省空间,花时间)

编译时候,在库提供的代码查找并添加需调用的代码地址,运行是去共享库找并调用,运行时都需要

 

6.制作共享库

Makefile:是make使用的一个文件,文件中编写了编译规则

 

变量   

CC = gcc//指定编译器

LIBS = libdemo.so//指定编译的文件结果

OBJS = $(patsubst %.c, %.o, $(wildcard *.c))//将所有*.c文件生成的.o文件给OBJS

 

#-fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code)

# 则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意

# 位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的,‘+’必须要编译和运行时共享库的位置一般有偏差,需用“+”找到新的地址

CFLAGS  += -Wall -O2 -g -fPIC -std=gnu99 -DMYDBG -I.   //gnu制作c语言编译器的工具

LDFLAGS += -shared

 

$(LIBS): $(OBJS)

$(CC) $(LDFLAGS) -o $@ $^

 

clean:

rm -f $(OBJS) $(LIBS)

 

 

patsubst:这个makefile中标准的一个函数,作用是把当前目录下的所有.c文件编译成对应的.o文件

wildcard:这个makefile中标准的一个函数,作用是把当前目录下的所有.c找到

 

$@:目标

$^:依赖体

 

7.使用共享库

AB两种方法会影响系统配置,最好不用此方法使用自己创建的共享库  C临时改变环境参数变量,不影响系统配置,优先选用

 

注:系统配置文件修改后需要重新启动系统或重新加载才会生效

 

A、把共享库拷贝到系统库路径下,系统库路径:

/lib

/usr/lib

 

B、在系统etc目录是存在系统的配置文件,系统配置文件所在路径:

/etc/ld.so.conf

 

C、环境变量修改

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD

并发程序设计:

>:多进程

————大型公司使用

 

1. 预备知识

**1.1面试-----进程与程序的区别

进程:具有并发性 动态性 交互性 独立性

动态的,是程序的一次执行过程

是一个抽象的可调度的实体

系统在执行某个程序时,为程序运行分配和释放资源

是程序执行的最小单位

程序

静态的,保存在磁盘上的指令的有序集合,没有任何的执行该念(如*.c--源代码不能执行

 

正文段---程序中的代码     用户数据段----变量/常量/动态分配的数据空间

系统数据(堆栈段)

----系统为程序分配的资源,存放函数的返回地址,函数的参数以及局部变量

 

进程标识:

PID:进程号----识别标识进程(0-70000)getpid()获取

PPID:父进程号---每个进程都有父进程---getppid()获取

在当前终端执行的进程的PPID就是本终端的PIDPID01被系统进程占用,用户ID1000,用户产生的进程ID1000

 

*****1.2进程类型

交互进程

--与用户进行交流的进程,由shell控制与运行。可在前台与后台运行,如输入输出终端--可同时打开多个终端,当前使用的终端就在前台运行,其他终端就在后台  文本编辑器 图形应用程序等

批处理进程

---不属于某个终端,他被提交到一个队列中以便顺序执行。如系统开机的自启动进程,如window的开机进程,鼠标设备,编译器的编译操作,数据库的搜索引擎等

守护进程

---一直在后台运行。一般在系统(Linux)启动时执行,关机结束。系统自己的进程,不能被用户修改

1.3进程状态

运行态--正在运行(就绪态)--准备运行

等待态--此时进程在等待一个事件的发生或某种系统资源(可中断/不中断等待)

停止态--此时进程被中止,但可再次启动

死亡态--已终止的进程,描述进程的任务结构体(task_struct)还存在在进程向量数组中

(消亡态)--父进程调用wait函数回收之后,子进程彻底被系统删除

 

进程状态图解释:

就绪态在cpu分配时间片后会变成运行态,当所需资源不足时会变成等待态(可中断/不可中断等待),当系统有所需资源时又变成运行态

运行态在cpu的调度后,由内核发出的信号可变成停止态,当内核再次发出某种信号后又会变成运行态

运行态在cpu的调度后,没有内核信号可变成僵尸态

 

(就绪态--cpu分配时间片--运行态----资源不足---等待态-----有资源---运行太---内核信号---停止太---内核信号---运行太)

Linux  init进程号为1了解;

1.4进程模式

进程的内存结构--4G的虚拟内存

用户空间:0-3G   内核空间:3G-4G

用户模式:

在用户空间

内核模式:

在内核空间

注:两个模式可以切换,如在用户空间调入系统调用函数(如当用户空间的程序调用read等系统函数,通过系统调用转换为内核模式,在内核空间执行系统函数,得到返回值在返回到用户模式)

 

1.5启动进程

手动启动:

使用命令行输入,ls等,前台与后台运行

调度启动:

系统调度启动(系统开机自启,调用已写入程序)

 

1.6调度进程

 

Ps -ef:查看系统中的进程

Top:动态查看进程,含NI(优先级:-20~190为最高优先级,用户不能指定负的优先级????)

Nice(需要退出程序再设置):  nice  -n  num(修改后的优先级) ./filename(./a.out)

Renice(改变正在运行进程的优先级):

renice  -n num  PID  

./a.out &”---使程序在后台运行(若加了打印输出只有关闭此终端或再另一个终端输入“kill  -9 PID(不可被阻止)”才能强制杀死该进程;若不加打印信息该终端可以用,并且会输出后台运行编号与进程号”[1]  74132”)

Kill  -15 PID:普通的杀死一个进程

fg 后台运行编号“-----将后台运行转为前台运行

Jobs  PID---查看后台运行的进程

Pg----查看后台运行进程

***2进程的系统调用

2.1fork

----父进程就是打开编写代码并运行的程序

子进程是利用fork函数重新创建的新的进程,创建子进程的瞬间,会复制一份子进程创建前的父进程空间的内容到子进程,之后两个进程的地址空间与内容是私有的,大家互不干涉

兄弟进程---同一父进程调用fork创建的子进程

 

Pid_t  fork(void)pif_t==int

 

头文件:sys/types.h<unistd.h>

功能:创建一个进程

参数:无

返回值:

失败:-1

成功:0,表示创建的子进程空间

0(一般大于1000),子进程的PID: (表示父进程)

 

Getpid(void)--获取进程号  getppid()--获取父进程号

Init进程ID1,系统控制块

僵尸进程:

子进程先于父进程结束,父进程不会回收子进程的资源

解决僵尸进程------利用wait,waitpid通知父进程回收,当父进程等待子进程结束再执行父进程,然后利用这两个函数调用资源回收程序

 

孤儿进程:

父进程先于子进程结束,子进程被init进程托管

孤儿进程----当父进程结束后子进程还存在,不受本终端管理---可强制杀死,可用于守护进程创建

2.2多进程特点:

各进程地址空间相对独立,

是有私有空间,执行速度慢,消耗资源多

任务切换时,系统开销较大----------需要刷新cache高速缓存与TLB页表,浪费时间资源,解决方法---多线程

进程内部定义的变量不会共享

多进程切换:利用cache高速缓冲器 TLB页表

 

 

TLB页表--保存虚拟地址与物理地址之间的映射关系

int main()

{

        int a=12;//fork函数之前的变量可被子父进程访问使用

        pid_t pid = fork();//创建进程,两个返回值,完全拷贝父进程的空间

        if( pid < 0 ){

                perror("fork");

                return -1;

        }  //多进程并发,cpu进程调度控制执行优先级(随机)--由抢占资源的先后来执行。子父进程空间可以调用其他函数,同时进行函数的调用

        else if( pid == 0 ){//表示创建的子进程空间

                while(1){

                        printf("++++++++\n");

                        sleep(1);

                }           

        }           

        else {//表示父进程空间

                while(1){

                        printf("---------\n");

                        sleep(1);

                }           

 

 

 

*****2.3forkvfork的区别----查看实例

 vfork保证子进程先被调度,由于vfork的怪异语法一般不使用
1.  fork  ():子进程拷贝父进程的数据段,代码段 
    vfork ( ):子进程与父进程共享数据段 
2.  fork ()父子进程的执行次序不确定 
    vfork 保证子进程先运行,在调用exec exit 之前与父进程数据是共享的,在它调用execexit 之后父进程才可能被调度运行。且必须调用exit/_exit否则程序会陷入死循环 
3.  vfork ()保证子进程先运行,在她调用exec exit 之后父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。 

2.4子父进程

1)如何避免孤儿进程

当父进程退出时,通知子进程也退出

利用函数prctl(PR_SET_PDEATHSIG,SIGKILL),在子进程空间中通知子进程

2)如何避免僵尸进程

当子进程退出时通知父进程回收资源

Signal(SIGCHLDsignal_handler)

Void signal_handler()

{

Wait(NULL);//

}

 

 

2. exec函数族

------当想在一个进程中想要运行多个程序时,exec函数族提供了一种在进程中启动另一个程序的执行方法。

--

使用场景:

 

头文件:<unistd.h>

2.1execlexeclp

Int execl(const char *path,const char *arg,..)

参数:

Path:带路径的文件名   arg:执行程序的命令(如./a.out)  ...:命令参数,以NULL表示命令输入结束

返回值:

成功:0

失败:-1

Eg:execl(“/bin/ls”,”ls”,”-l”,NULL);

 

Int  execlp(const  char *file,const  char* arg);--可自动查找系统搜索路径

参数:

file (当在系统自动搜索路径下如ls,不带路径的文件名----ls;不再系统搜索路径下,带路径的文件名----如自己创建的文件)

Arg:arg:命令(如./a.out)  ...:命令参数,以NULL表示命令输入结束

返回值:成功:0

 失败:-1

Eg:execlp(“./a.out”,”a.out”,”1.count_ine.c”,NILL)

Gcc  count.c -o cnt     

调用execlp时:execlp(“./cnt”,”cnt”,”count.c”,NULL),不能用./a.out因为本程序也会生成a.out可执行程序,会造成覆盖

Execlp(“ls”,”ls”,”-l”,NULL);

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

参数:

Path::带路径的文件名

Argv:指向参数列表

返回值:成功:0

 失败:-1

Eg:char argv[]={“/bin/ls”,”ls”,”-l”,NULL};

Int execvP(const  char * path,const char *argv[]);

参数:

file (当在系统自动搜索路径下如ls,不带路径的文件名----ls;不再系统搜索路径下,带路径的文件名----如自己创建的文件)

Argv:指向参数列表

返回值:成功:0

 失败:-1

 

总结:

lexec函数:表示后边的参数以可变参数的形式给出且都以一个空指针结束

p的exec函数:表示第一个参数path若为系统搜索路径不用输入完整路径,但自己创建的文件需写完整

不带l的函数:表示命令所需参数以char *argv[]形式给出,且argv最后一个元素必须是NULL

 

 

3. Wait(),witpid()---解决僵尸进程

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

 

为了避免产生僵尸进程,通知父进程回收子进程的资源

Wait();

Pid_t wait(int  *status );

函数功能:

 

参数:指向的对象用来保存子进程退出时的状态

Status==NULL:忽略子进程退出时的状态

Status!=NULL:保存子进程退出时的状态

状态由一些特殊的宏保存(WIFEXITEDstatus-- 状态检测宏(成功1失败0WEXITSTATUSstatus----检测子进程退出时传递的参数信息)

返回值:成功:子进程的进程号

       失败:-1

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

函数功能:

 

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

参数:pid:

>0:等待进程ID等于pid的子进程

-1:等待任何一个子进程

Status:

指向的对象用来保存子进程退出时的状态

Status==NULL:忽略子进程退出时的状态

Status!=NULL:保存子进程退出时的状态

Optition:

WNOHANG:不阻塞

0:阻塞父进程

返回值:正常,结束的子进程的ID

NOHANG且没有子进程结束时:0

调用出错:-1

 

用阻塞或非阻塞状态等待子进程结束回收子进程资源

4.守护进程(Daemon

 

是一段连续运行的程序,用于处理计算机系统希望接收到的阶段性的服务需求的。Daemon程序段将请求提交给其他合适的程序(或者进程)。网络上每个页面的服务器都有一个HTTPD或者是超文本传输协议daemon,持续地等待从网络客户端及其用户发送来的请求。 

守护进程编写步骤:sedsid??????????

会话 ---由一个或多个进程组的集合

进程组---一个或多个进程的集合,由进程组ID来唯一标识

1〉创建子进程,父进程退出

2〉在子进程中创建新会话----setsid

使孤儿进程完全脱离原终端的控制

3〉改变当前目录为根目录---chdir

脱离原终端后目录不确定,指定为根目录或/tmp,/tmp下查看

4〉重设文件权限掩码--umask(0 )

5〉关闭文件描述符

守护进程编写实例:

Int main(int argc,char* argv[])

{

//创建孤儿进程

pid_t  pid = for(k);

If( 0 > pid){

Perror(“fork”);

Return-1;

}

Else If(0 < pid) exit(0);

 

Else{

//创建新的对话,setsid()可以使进程独立出来,完全脱离其他进程的控制

If (setsid() < 0){

Perror(“setsid”);

}

//更改工作目录

Chdir(“/tmp”);

 

//设置权限掩码

Umask(0);

//关闭多余文件描述符

Int i,fdtablesize = getdtablesize();

For(i=0;i<fdtablesize;i++)

Close(i);

 

//做某种操作

FILE *fp = fopen(“test.c”,”a+”);

If(NULL = fp){

Perror(“fopen”);

Return -1;

}

Char buf[32]={0};

Int line = 0;

While(fgets(buf,32,fp) ! = NULL){

If(buf[strlen(buf)-1] == ‘\n’)

Line++;

}

While(1){

Time_t t;

Time(&t);

Struct tm *t1;

T1 = localtime(&t);

Sprintf(buf,”%d  %d/%d/%d/%d  %d:%d:%d\n”,++line,t1->tm_year+1900,t1->tm_mon,t1->tm_mday,t1->tm_hour,t1->tm_min,t1->tm_sec);

Printf(“%s”,buf);

Fputs(buf,fp);

Fflush(1);

Sleep(1);

}

Fclose(fp);

}

Return 0;

}

5.exit_exit

----用于错误处理返回

#include <stdlib.h>//声明库函数

Void exit(int status);

Exit:

库函数,退出进程,刷新缓存区

 

#include <unistd.h>//声明系统调用函数

Void _exit(int  status)

_exit:

系统调用函数,退出进程,不会刷新缓存区

参数:status

利用该参数传递进程结束时的标志

 

Fllush()---将缓存的东西写入某个流中

Exit(0)  _exit(0)

--------表示异常退出

Printf(“12345”);exit(0)-------printf输出到标准输出流--输出终端)打印出12345(带缓存)

Printf(“12345”);_exit(0)-----不会打印任何东西(不带缓存)

Return 0;----结束后,会自动刷新缓存

***6.进程间如何解决资源抢占问题

1〉利用睡眠 2〉利用wait  waitpid  2>共享内存+信号灯集 3〉消息队列

作业:

1创建一个多进程,子进程实现文件行数的统计,父进程实现统计一个文件的大小

int main()

{

        pid_t pid = fork();

        if(0 > pid){

                perror("fork");

                return -1;

        }   

        else if(0 == pid){

                if(execl("./cnt","cnt","count.c",NULL) < 0){

                        perror("execl");

                        return -1;

                }   

        }   

        else {

#if 0

                wait(NULL);

#else

                int status;

                pid_t PID = wait(&status);

                printf("child process status number:%d\n",WEXITSTATUS(status));

                printf("child ID:%d\n",PID);

                printf("child open status:%d\n",WIFEXITED(status));

#endif

                if(execlp("./size","size","file_size.c",NULL) < 0){

                        perror("execlp");

                        return -1;

                }   

        }   

        return 0;

}

 

2.(查看守护进程实例)cat查看(标准IO上机实验)-------用守护进程实现:

每隔一秒,向文件写入:行数+日期

思路:

  1. 创建守护进程
  2. 在守护进程第五步操作之后,加入文件读写
  3. 打开文件
  4. 计算文件行数
  5. 循环获取系统时间

 

把指针函数定义别名为函数指针PRNMSG(函数类型别名)

 

将所有*.CONF包含到当前文件中

 

 

问题:动态库的制作与使用  文件配置扩展问题  文件属性函数测试--如何将打印日期的换行符吃掉(利用字符串将倒数第二个字符置0

 

Linux  init进程

 

init是所有进程的父进程,它由内核执行,可以启动其他所有的进程。

initLinux系统操作中不可缺少的程序之一。

  所谓的init进程,它是一个由内核启动的用户级进程。

  内核自行启动(已经被载入内存,开始运行,并已初始化所有的设备驱动程序和数据结构等)之后,就通过启动一个用户级程序init的方式,完成引导进程。所以,init始终是第一个进程(其进程编号始终为1)。

  内核会在过去曾使用过init的几个地方查找它,它的正确位置(对Linux系统来说)是/sbin/init。如果内核找不到init,它就会试着运行/bin/sh,如果运行失败,系统的启动也会失败。

二〉多线程

1. 线程基础

线程与进程执行的区别

进程:

是程序的一次执行过程

 

各进程地址空间相对独立,

是有私有空间,执行速度慢,消耗资源多

任务切换时,系统开销较大----------需要刷新cache高速缓存与TLB页表,浪费时间资源,解决方法---多线程

进程内部定义的变量不会共享

 

线程:

轻量级的进程

大大提高任务切换的效率,共用系统空间

避免了额外的TLB页表与cache高速缓冲区(缓存)的刷新

在同一个进程中创建的线程共享该进程的地址空间

 

1.1定义

 

轻量级的进程。

为了解决多进程任务切换时系统开销较大的问题

 

 

 

 

多线程执行

 

 

线程池

 

1.2优点与特点

大大提高任务切换的效率,共用系统空间

避免了额外的TLB页表与cache高速缓冲区(缓存)的刷新

在同一个进程中创建的线程共享该进程的地址空间

 

1.3多线程实现

通过第三方线程库(非标准C库)实现

1.4线程资源

共享资源:

可执行指令  静态数据(全局变量(需放在函数使用之前)可以,局部变量不可)进程中打开的文件描述符

私有资源:

线程id  堆栈--局部变量与返回地址

1.5第三方线程库

---别人写的库需要手动链接 (-lpthread

头文件:<pthread.h>

1.5.1创建线程

--所需线程数目较小的程序

缺点:多次调用,浪费时间资源,解决方案---线程池

Int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void * *routine(void * ),void *arg)

Pthread_t==int

功能:

创建一个线程被称为主线程

参数:

Thread:创建的线程ID

Arrt:指向的线程属性,NULL表示缺省属性

Routine:线程执行的函数(写时直接用函数名,代表整个函数)---线程的执行空间

Arg:传递线程执行函数的参数,若不传参数,直接写NULL

返回值:

失败:失败错误号

成功:0

注意:创建线程时若主进程直接结束,子线程还来不及执行就结束销毁,因此需要在让进程(while(1))不提前结束,调用线程的同时进程也会向下进程。-----并发程序设计

1.5.2:阻塞进程等待线程结束,自动回收资源

回收资源与pthread_exit()一起使用

Int pthread_join(pthread_t thread,void **value_ptr)

参数:

Thread:要等待的线程

Value_ptr:指向线程返回的参数

返回值:

失败:失败错误号

成功:0

 

1.5.3结束线程

Int pthread_exit(pthread_t threadvoid *value_ptr)

函数功能:退出线程

参数:

Thread:要等待的线程号

Value_ptr:保存线程推出时的值

返回值:

失败:失败错误号

成功:0

 

 

1.5.4:取消线程

--在主进程中使用

Int  pthread_cancel(pthread_t  thread)

函数功能:取消创建了的线程(在主线程中取消子线程--回收描述子线程的结构体)

1.5.5:线程分离函数

Int pthread_detach(pthread_t thread)

参数:要分离的线程号

返回值:

失败:失败错误号

成功:0

 

不会阻塞主进程向后运行,当子进程退出时,回收线程资源

作业:在一个进程中的两个线程实现

------------一个线程完成一个全局变量的自加,一个线程完成该变量的打印。结果打印出的数据不一定是连续的-----因为两个线程抢占同一个进程的地址空间与资源,因此第二个线程可能会比第一个线程执行。

------解决以上结果:分别让两个线程睡眠200ms等但是比较麻烦。实际用同步与互斥解决(1.7

1.6线程池

https://www.jb51.net/article/41375.htm

1.6.1简介

多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。    
    假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。

    如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能
                一个线程池包括以下四个基本组成部分:
                1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
                2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
                3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
                4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

1.6.2线程池的创建

  typedef struct tp_work_desc_s tp_work_desc; //应用线程执行任务时所需要的一些信息
       typedef struct tp_work_s tp_work; //线程执行的任务
       typedef struct tp_thread_info_s tp_thread_info; //描述了各个线程id,是否空闲,执行的任务等信息
       typedef struct tp_thread_pool_s tp_thread_pool; // 有关线程池操作的接口信息
         //thread parm
       struct tp_work_desc_s{应用线程执行任务时所需要的一些信息
                  ……
        };
       //base thread struct
       struct tp_work_s{//希望线程执行的任务    

              //main process function. user interface
                  void (*process_job)(tp_work *this, tp_work_desc *job);
        };
        tp_thread_pool *creat_thread_pool(int min_num, int max_num);

 

 //main thread pool struct
        struct tp_thread_pool_s{//创建一个线程池的实例
             TPBOOL (*init)(tp_thread_pool *this);
             void (*close)(tp_thread_pool *this);
             void (*process_job)(tp_thread_pool *this, tp_work *worker, tp_work_desc *job);
             int  (*get_thread_by_id)(tp_thread_pool *this, int id);
             TPBOOL (*add_thread)(tp_thread_pool *this);
             TPBOOL (*delete_thread)(tp_thread_pool *this);
              int (*get_tp_status)(tp_thread_pool *this); 
              int min_th_num;                //min thread number in the pool
              int cur_th_num;                 //current thread number in the pool
              int max_th_num;         //max thread number in the pool
              pthread_mutex_t tp_lock;
              pthread_t manage_thread_id;  //manage thread id num
              tp_thread_info *thread_info;   //work thread relative thread info
};
         结构tp_thread_info_s描述了各个线程id、是否空闲、执行的任务等信息,用户并不需要关心它。
         //thread info
         struct tp_thread_info_s{
              pthread_t          thread_id;         //thread id num
             TPBOOL                   is_busy;    //thread status:true-busy;flase-idle
             pthread_cond_t          thread_cond;
             pthread_mutex_t               thread_lock;
             tp_work                      *th_work;
             tp_work_desc            *th_job;
         };

 

1.6.3实现代码

1>Thread-pool.h(头文件):

复制代码代码如下:


#include <stdio.h>   
#include <stdlib.h>   
#include <sys/types.h>   
#include <pthread.h>   
#include <signal.h>   

#ifndef TPBOOL   
typedef int TPBOOL;  
#endif   

#ifndef TRUE   
#define TRUE 1   
#endif   

#ifndef FALSE   
#define FALSE 0   
#endif   

#define BUSY_THRESHOLD 0.5  //(busy thread)/(all thread threshold)   
#define MANAGE_INTERVAL 5   //tp manage thread sleep interval   

typedef struct tp_work_desc_s tp_work_desc;  
typedef struct tp_work_s tp_work;  
typedef struct tp_thread_info_s tp_thread_info;  
typedef struct tp_thread_pool_s tp_thread_pool;  

//thread parm   
struct tp_work_desc_s{  
    char *inum; //call in   
    char *onum; //call out   
    int chnum;  //channel num   
};  

//base thread struct   
struct tp_work_s{  
    //main process function. user interface   
    void (*process_job)(tp_work *this, tp_work_desc *job);  
};  

//thread info   
struct tp_thread_info_s{  
    pthread_t       thread_id;  //thread id num   
    TPBOOL          is_busy;    //thread status:true-busy;flase-idle   
    pthread_cond_t          thread_cond;      
    pthread_mutex_t     thread_lock;  
    tp_work         *th_work;  
    tp_work_desc        *th_job;  
};  

//main thread pool struct   
struct tp_thread_pool_s{  
    TPBOOL (*init)(tp_thread_pool *this);  
    void (*close)(tp_thread_pool *this);  
    void (*process_job)(tp_thread_pool *this, tp_work *worker, tp_work_desc *job);  
    int  (*get_thread_by_id)(tp_thread_pool *this, int id);  
    TPBOOL (*add_thread)(tp_thread_pool *this);  
    TPBOOL (*delete_thread)(tp_thread_pool *this);  
    int (*get_tp_status)(tp_thread_pool *this);  

    int min_th_num;     //min thread number in the pool   
    int cur_th_num;     //current thread number in the pool   
    int max_th_num;         //max thread number in the pool   
    pthread_mutex_t tp_lock;  
    pthread_t manage_thread_id; //manage thread id num   
    tp_thread_info *thread_info;    //work thread relative thread info   
};  

tp_thread_pool *creat_thread_pool(int min_num, int max_num); 

Thread-pool.c(实现文件):

复制代码代码如下:


#include "thread-pool.h"   

static void *tp_work_thread(void *pthread);  
static void *tp_manage_thread(void *pthread);  

static TPBOOL tp_init(tp_thread_pool *this);  
static void tp_close(tp_thread_pool *this);  
static void tp_process_job(tp_thread_pool *this, tp_work *worker, tp_work_desc *job);  
static int  tp_get_thread_by_id(tp_thread_pool *this, int id);  
static TPBOOL tp_add_thread(tp_thread_pool *this);  
static TPBOOL tp_delete_thread(tp_thread_pool *this);  
static int  tp_get_tp_status(tp_thread_pool *this);  

/** 
  * user interface. creat thread pool. 
  * para: 
  *     num: min thread number to be created in the pool 
  * return: 
  *     thread pool struct instance be created successfully 
  */  
tp_thread_pool *creat_thread_pool(int min_num, int max_num){  
    tp_thread_pool *this;  
    this = (tp_thread_pool*)malloc(sizeof(tp_thread_pool));   

    memset(this, 0, sizeof(tp_thread_pool));  

    //init member function ponter   
    this->init = tp_init;  
    this->close = tp_close;  
    this->process_job = tp_process_job;  
    this->get_thread_by_id = tp_get_thread_by_id;  
    this->add_thread = tp_add_thread;  
    this->delete_thread = tp_delete_thread;  
    this->get_tp_status = tp_get_tp_status;  

    //init member var   
    this->min_th_num = min_num;  
    this->cur_th_num = this->min_th_num;  
    this->max_th_num = max_num;  
    pthread_mutex_init(&this->tp_lock, NULL);  

    //malloc mem for num thread info struct   
    if(NULL != this->thread_info)  
        free(this->thread_info);  
    this->thread_info = (tp_thread_info*)malloc(sizeof(tp_thread_info)*this->max_th_num);  

    return this;  
}  

  
/** 
  * member function reality. thread pool init function. 
  * para: 
  *     this: thread pool struct instance ponter 
  * return: 
  *     true: successful; false: failed 
  */  
TPBOOL tp_init(tp_thread_pool *this){  
    int i;  
    int err;  

    //creat work thread and init work thread info   
    for(i=0;i<this->min_th_num;i++){  
        pthread_cond_init(&this->thread_info[i].thread_cond, NULL);  
        pthread_mutex_init(&this->thread_info[i].thread_lock, NULL);  

        err = pthread_create(&this->thread_info[i].thread_id, NULL, tp_work_thread, this);  
        if(0 != err){  
            printf("tp_init: creat work thread failed\n");  
            return FALSE;  
        }  
        printf("tp_init: creat work thread %d\n", this->thread_info[i].thread_id);  
    }  

    //creat manage thread   
    err = pthread_create(&this->manage_thread_id, NULL, tp_manage_thread, this);  
    if(0 != err){  
        printf("tp_init: creat manage thread failed\n");  
        return FALSE;  
    }  
    printf("tp_init: creat manage thread %d\n", this->manage_thread_id);  

    return TRUE;  
}  

/** 
  * member function reality. thread pool entirely close function. 
  * para: 
  *     this: thread pool struct instance ponter 
  * return: 
  */  
void tp_close(tp_thread_pool *this){  
    int i;  

    //close work thread   
    for(i=0;i<this->cur_th_num;i++){  
        kill(this->thread_info[i].thread_id, SIGKILL);  
        pthread_mutex_destroy(&this->thread_info[i].thread_lock);  
        pthread_cond_destroy(&this->thread_info[i].thread_cond);  
        printf("tp_close: kill work thread %d\n", this->thread_info[i].thread_id);  
    }  

    //close manage thread   
    kill(this->manage_thread_id, SIGKILL);  
    pthread_mutex_destroy(&this->tp_lock);  
    printf("tp_close: kill manage thread %d\n", this->manage_thread_id);  

    //free thread struct   
    free(this->thread_info);  
}  

/** 
  * member function reality. main interface opened.  
  * after getting own worker and job, user may use the function to process the task. 
  * para: 
  *     this: thread pool struct instance ponter 
  * worker: user task reality. 
  * job: user task para 
  * return: 
  */  
void tp_process_job(tp_thread_pool *this, tp_work *worker, tp_work_desc *job){  
    int i;  
    int tmpid;  

    //fill this->thread_info's relative work key   
    for(i=0;i<this->cur_th_num;i++){  
        pthread_mutex_lock(&this->thread_info[i].thread_lock);  
        if(!this->thread_info[i].is_busy){  
            printf("tp_process_job: %d thread idle, thread id is %d\n", i, this->thread_info[i].thread_id);  
            //thread state be set busy before work   
            this->thread_info[i].is_busy = TRUE;  
            pthread_mutex_unlock(&this->thread_info[i].thread_lock);  

            this->thread_info[i].th_work = worker;  
            this->thread_info[i].th_job = job;  

            printf("tp_process_job: informing idle working thread %d, thread id is %d\n", i, this->thread_info[i].thread_id);  
            pthread_cond_signal(&this->thread_info[i].thread_cond);  

            return;  
        }  
        else   
            pthread_mutex_unlock(&this->thread_info[i].thread_lock);       
    }//end of for   

    //if all current thread are busy, new thread is created here   
    pthread_mutex_lock(&this->tp_lock);  
    if( this->add_thread(this) ){  
        i = this->cur_th_num - 1;  
        tmpid = this->thread_info[i].thread_id;  
        this->thread_info[i].th_work = worker;  
        this->thread_info[i].th_job = job;  
    }  
    pthread_mutex_unlock(&this->tp_lock);  

    //send cond to work thread   
    printf("tp_process_job: informing idle working thread %d, thread id is %d\n", i, this->thread_info[i].thread_id);  
    pthread_cond_signal(&this->thread_info[i].thread_cond);  
    return;   
}  

/** 
  * member function reality. get real thread by thread id num. 
  * para: 
  *     this: thread pool struct instance ponter 
  * id: thread id num 
  * return: 
  *     seq num in thread info struct array 
  */  
int tp_get_thread_by_id(tp_thread_pool *this, int id){  
    int i;  

    for(i=0;i<this->cur_th_num;i++){  
        if(id == this->thread_info[i].thread_id)  
            return i;  
    }  

    return -1;  
}  

/** 
  * member function reality. add new thread into the pool. 
  * para: 
  *     this: thread pool struct instance ponter 
  * return: 
  *     true: successful; false: failed 
  */  
static TPBOOL tp_add_thread(tp_thread_pool *this){  
    int err;  
    tp_thread_info *new_thread;  

    if( this->max_th_num <= this->cur_th_num )  
        return FALSE;  

    //malloc new thread info struct   
    new_thread = &this->thread_info[this->cur_th_num];  

    //init new thread's cond & mutex   
    pthread_cond_init(&new_thread->thread_cond, NULL);  
    pthread_mutex_init(&new_thread->thread_lock, NULL);  

    //init status is busy   
    new_thread->is_busy = TRUE;  

    //add current thread number in the pool.   
    this->cur_th_num++;  

    err = pthread_create(&new_thread->thread_id, NULL, tp_work_thread, this);  
    if(0 != err){  
        free(new_thread);  
        return FALSE;  
    }  
    printf("tp_add_thread: creat work thread %d\n", this->thread_info[this->cur_th_num-1].thread_id);  

    return TRUE;  
}  

/** 
  * member function reality. delete idle thread in the pool. 
  * only delete last idle thread in the pool. 
  * para: 
  *     this: thread pool struct instance ponter 
  * return: 
  *     true: successful; false: failed 
  */  
static TPBOOL tp_delete_thread(tp_thread_pool *this){  
    //current thread num can't < min thread num   
    if(this->cur_th_num <= this->min_th_num) return FALSE;  

    //if last thread is busy, do nothing   
    if(this->thread_info[this->cur_th_num-1].is_busy) return FALSE;  

    //kill the idle thread and free info struct   
    kill(this->thread_info[this->cur_th_num-1].thread_id, SIGKILL);  
    pthread_mutex_destroy(&this->thread_info[this->cur_th_num-1].thread_lock);  
    pthread_cond_destroy(&this->thread_info[this->cur_th_num-1].thread_cond);  

    //after deleting idle thread, current thread num -1   
    this->cur_th_num--;  

    return TRUE;  
}  

/** 
  * member function reality. get current thread pool status:idle, normal, busy, .etc. 
  * para: 
  *     this: thread pool struct instance ponter 
  * return: 
  *     0: idle; 1: normal or busy(don't process) 
  */  
static int  tp_get_tp_status(tp_thread_pool *this){  
    float busy_num = 0.0;  
    int i;  

    //get busy thread number   
    for(i=0;i<this->cur_th_num;i++){  
        if(this->thread_info[i].is_busy)  
            busy_num++;  
    }  

    //0.2? or other num?   
    if(busy_num/(this->cur_th_num) < BUSY_THRESHOLD)  
        return 0;//idle status   
    else  
        return 1;//busy or normal status       
}  

/** 
  * internal interface. real work thread. 
  * para: 
  *     pthread: thread pool struct ponter 
  * return: 
  */  
static void *tp_work_thread(void *pthread){  
    pthread_t curid;//current thread id   
    int nseq;//current thread seq in the this->thread_info array   
    tp_thread_pool *this = (tp_thread_pool*)pthread;//main thread pool struct instance   

    //get current thread id   
    curid = pthread_self();  

    //get current thread's seq in the thread info struct array.   
    nseq = this->get_thread_by_id(this, curid);  
    if(nseq < 0)  
        return;  
    printf("entering working thread %d, thread id is %d\n", nseq, curid);  

    //wait cond for processing real job.   
    while( TRUE ){  
        pthread_mutex_lock(&this->thread_info[nseq].thread_lock);  
        pthread_cond_wait(&this->thread_info[nseq].thread_cond, &this->thread_info[nseq].thread_lock);  
        pthread_mutex_unlock(&this->thread_info[nseq].thread_lock);        

        printf("%d thread do work!\n", pthread_self());  

        tp_work *work = this->thread_info[nseq].th_work;  
        tp_work_desc *job = this->thread_info[nseq].th_job;  

        //process   
        work->process_job(work, job);  

        //thread state be set idle after work   
        pthread_mutex_lock(&this->thread_info[nseq].thread_lock);          
        this->thread_info[nseq].is_busy = FALSE;  
        pthread_mutex_unlock(&this->thread_info[nseq].thread_lock);  

        printf("%d do work over\n", pthread_self());  
    }     
}  

/** 
  * internal interface. manage thread pool to delete idle thread. 
  * para: 
  *     pthread: thread pool struct ponter 
  * return: 
  */  
static void *tp_manage_thread(void *pthread){  
    tp_thread_pool *this = (tp_thread_pool*)pthread;//main thread pool struct instance   

    //1?   
    sleep(MANAGE_INTERVAL);  

    do{  
        if( this->get_tp_status(this) == 0 ){  
            do{  
                if( !this->delete_thread(this) )  
                    break;  
            }while(TRUE);  
        }//end for if   

        //1?   
        sleep(MANAGE_INTERVAL);  
    }while(TRUE);  
}  

2> 

 

2线程间同步和互斥机制

1. 线程间机制

多线程共享同一个进程空间

优点:线程间很容易进行通信(通过全局变量(写在头文件里或头文件下面)实现数据共享与交换)

缺点:由于存在资源抢占问题,多个线程同时引用共享数据时需要引用同步与互斥机制

 

2. 线程间同步

2.1同步机制

定义:多个任务(线程)按照约定的顺序相互的配合来完成一件事

2.2信号量

----决定线程是继续运行还是阻塞等待

2.2.1定义

表示某个资源(抽象),其值(非负整数)代表系统中某个资源的数量

2.2.2访问方式

(类似于生 产者与消费者进货卖货之间的关系)

信号量是贯穿于主进程与线程的全局量;

应用:当多个线程交替进行时,用PV操作可以保证其按照约定的顺序执行(------------一个线程完成一个全局变量的自加,一个线程完成该变量的打印。结果打印出的数据不一定是连续的-----因为两个线程抢占同一个进程的地址空间与资源,因此第二个线程可能会比第一个线程先执行。)

 

头文件:〈semaphore.h

(1)初始化

 

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

参数:

Sem:初始化的信号量(信号量名)

Pshared:信号量共享的范围(0:线程间使用 非0:进程间使用)

Value:信号量初值

返回值:

成功:0

失败:-1

2P 操作(申请资源(消费者)--信号量-1

If(信号量的值大于0{申请资源的任务继续运行;

信号量的值-1

}

Else{

申请资源的任务阻塞;

}

Int sem_wait(sem_t * sem)//阻塞等待申请资源

参数:

Sem:信号量

返回值:

成功:0

失败:-1

 

3V操作(释放资源(生产者)--信号量+1

If(没有任务在等待该资源){

信号量+1

}

Else{唤醒第一个等待的任务,让其继续运行}

Int sem_post(sem_t *sem)

参数:

Sem:信号量

返回值:

成功:0

失败:-1

 

3. 线程间互斥

3.1互斥锁:

(1)目的

引入互斥锁的目的保证共享数据的完整性

(如子线程打印主线程两个相同赋值的变量,但打印的值不一定相同)

2)互斥锁定义

主要用来保护临界资源(最多能有一个线程使用该资源)

3)临界资源

多个任务共享的资源

必须先获得互斥锁才能访问临界资源

(4)临界区

访问或者操作临界资源的代码

(5)函数

//头文件 〈pthread.h

A:定义互斥锁

Pthread_mutex_t  mutex买了一把锁)//全局

B:<初始化化互斥锁>

//主进程开始

Int pthread_mutex_init(pthread_mutex_t * mutex, pthread_mutexattr_t *attr )

函数功能:

初始化已经定义的互斥锁

参数:

Mutex:互斥锁

Attr:互斥锁属性//NULL表示缺省属性--常用

返回值:

成功:0

失败:-1//返回错误码

C:<申请互斥锁>

--加锁//临界区之前----不必须

Int pthread_mutex_lock(pathred_mutex_t *mutex)

参数:

Mutex:互斥锁

返回值:

成功:0

失败:-1//返回错误码

D:〈释放互斥锁〉

--解锁//临界区之后----必须(有枷锁就必须解锁否则为死锁

Int pthread_mutex_unlock(pthread_mutex_t  * mutex)

参数:

Mutex:互斥锁

返回值:

成功:0

失败:-1//返回错误码

 

注意:某一个临界区必须同时加锁与解锁

 

4.线程间如何解决资源抢占问题

1)使用睡眠--效率不高 2)使用信号量 3)使用互斥锁

作业1;锁粒度

https://www.cnblogs.com/gcczhongduan/p/4278945.html

所谓粒度锁的作用范围,即细化的程度。锁的粒度越大,则并发性越低且开销大;锁的粒度越小,则并发性高且开销小。

锁的粒度主要有下面几种类型:

1)行锁,行锁是粒度中最小的资源。行锁就是指事务在操作数据的过程中,锁定一行或多行的数据,其它事务不能同一时候处理这些行的数据。行级锁占用的数据资源最小,所以在事务的处理过程中,同意其它事务操作同一表的其它数据。

2)页锁,一次锁定一页。25个行锁可升级为一个页锁。

3)表锁,锁定整个表。当整个数据表被锁定后,其它事务就不可以使用此表中的其它数据。使用表锁可以使事务处理的数据量大,而且使用较少的系统资源。可是在使用表锁时,会延迟其它事务的等待时间,减少系统并发性。

4)数据库锁,防止不论什么事务和用户对此数据库进行訪问。可控制整个数据库的操作。

 

数据库锁效率会降低,可通过使用表锁来降低锁的使用从而保证效率。

 

作业2:产生死锁的原因,如何避免死锁

 http://www.penglixun.com/tech/database/lock_granularity_deadlock_probability.html

死锁:当多个操作竞争资源,每个操作都无法获得全部所需资源时,系统进入死锁,如无外力作用,系统将无限等待下去,死锁的四个必要条件:
1) 互斥条件:一个资源每次只能被一个进程使用。
2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
打破任何一个条件就不会发生死锁。

不能就认为所有情况都是锁的粒度越大死锁概率越大,而是要看操作的粒度,如果锁的粒度比操作的粒度大或相同,操作就会变成串行,根本不可能发生死锁。

如何避免死锁:

把锁的粒度提高到跟操作的粒度大,并发一个表,我就一次锁一个表,并发一个库,我就一次锁一个库,这实际上是什么呢,就是把并行弄成串行了,一般没人这么做,并发只有1,效率太低。
一般是多管齐下避免死锁,一是使用不同等级的锁,例如意向锁,互斥锁等,其实就是打破死锁第一条件——互斥条件,资源某些时候可以共享,例如读锁可以共享读,不过因为还是存在互斥锁,依然可能死锁。
第二就是可以按顺序锁资源,例如锁表只能按A->B->C的顺序锁,如果我要A,B表,锁了A表后发现B表已经被锁了,就释放A表的锁(或者使用意向锁),延时再尝试,直到所有资源都可以锁住(互斥锁/共享锁),这就是打破了死锁第二条件——请求与保持条件,不能获得全部资源就先释放锁,等能获得了再说,这样操作可以完全避免死锁。

作业3:使用多线程实现图片拷贝

线程1拷贝图片的前一半,线程2拷贝图片的后一半

结和文件IO

步骤:1〉文件IO打开创建文件(设置全局变量文件描述符)

2〉求文件大小(设置文件长度全局变量)

3〉创建线程

#includec <string.h>

#iinclude <stdlib.h>

#include  <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <pthread.h>

#include <semaphore.h>

 

Int fd,fp;

Char buf[32];

Void *pthread_coopy1(void *arg)//线程1

{

Int count = *(int *)arg;

While(read(fd,buf,sizeof(buf)) > 0){

Write(fp,buf,sizeof(buf));

Memset(buf,0,sizeof(buf));

If(lseek(fd,0,sizeof(buf)))//必须在write后,否则会造成拷贝不完整

Break;

}

Printf(“1 over\n”);

}

Void *pthread_copy2()//线程2

{

While(read(fd,buf,sizeof(buf))){

Write(fp,buf,sizeof(buf));

Memset(buf,0,sizeof(buf));

}

Printf(“2 over\n”);

}

 

Int  main(int argc,char *argv[])

{

If(argc < 2)

{

Printf(“参数个数太少\n”);

Return -1;

}

Fd = open(argv[1],O_RDONLY);

If(-1 == fd){

Perror(“open”);

Return -1;

}

Int count = lseek(fd,0,SEEK_END)//统计文件大小

Lseek(fd,0,SEEK_SET);

 

Pthread_t tid;

If(pthread_create(&tid,NULL,pthread_copy1,&count) < 0){

Perror(“pthread_create”);

Return -1;

}

//

Void *result;

If(pthread_join(tid,&result) < 0){//阻塞进程等待线程结束

Perror(“pthread_join”);

Return -1;

}

Pthread_t tid2;

If(pthread_create(&tid2,NULL,pthread_copy2,NULL)  < 0 ){

Perror(“pthread_create”);

Return -1;

}

 

While(1);

Return  0;

}

4. 进程间通信

1〉传统进程间通信方式:

无名管道pipe 有名管道fifo  信号signal

2〉系统五(system V )的IPC对象

共享内存()  消息队列  信号灯

3S 套接字

 

Pipe mkfifo  kill  

1. 进程间通信简述

早期进程间进程间通信方式------传统通信方式

AT&T贝尔实验室---system V IPC (进程主要局限在单个计算机内)

BSD----基于套接字的进程间通信

 

 

 

 

2.传统进程间通信方式

*****2.1无名管道

 

2.1.1:特点

只能用于具有亲缘关系的进程之间的通信---(子进程会拷贝父进程的空间内容因此两个进程中都有管道读写端口)通信范围小

半双工(单工)的通信模式,具有固定的读端与写端----通信不灵活

管道可以看成是一个特殊的文件(存在于内核),对于它 的读写用文件IOread write

2.1.2:创建与关闭

管道基于文件描述符的通信方式。管道建立时,会创建两个文件描述符(系统创建)----fd[0]-3固定用于读管道(从管道读)fd[1]-4固定用于写管道(写入管道)

(1)创建管道---fork之前创建

Int pipe(int  fd[2])

头文件:<unistd.h

参数:整型数组,存放文件描述符

返回值:成功:0

出错:-1

例子: int fd[2];fi(pipe(fd)== 0){

Perror(“pipe”); retrun -1;

}

2)管道读写

写管道:write(fd[1],buf,sizeof(buf))//管道中数据满时,阻塞等待

读管道:read(fd[0],buf,sizeof(buf))//(将管道中的数据取出来,管道中就没数据)管道中无数据则阻塞等待

读写数据特点:

当管道中无数据时,读端会阻塞

读端存在,向管道中写入数据时,若管道缓冲区的数据已满写端就会阻塞

读端不存在,向管道中写入数据,管道会破裂,程序会收到内核传来的SIGPIPE信号(l管道破裂信号)终止运行

无名管道是不存在管道文件的,数据交互在内核中。

2.1.3管道缓冲区的大小 

65536字节 = 64k;(利用不断向管道写入1个字节的字符,当输入阻塞时就不能再写入)

1024字节=1k

Printf()--不加’\n’只有一行满了才刷新每一次会少读一个字节

****2.2有名管道

2.2.1特点与简介

有名管道可以使互不相关的两个进程互相通信,可以通过路径名指出,并且在文件系统中可见。

进程通过文件IO来操作有名管道,遵循先进先出规则,不支持lsek()

 

利用管道文件实现多机通信(多个进程实现通信)

 

2.2.2函数

对有名管道文件读写时需分两个文件(实现互不相关的进程间通信)--则需要开两个终端显示读写过程且生成的执行文件名需不同,否则会造成执行结构覆盖

(1)创建管道文件(p

Int mkfifo(const char *filenamemode_t  mode)

头文件:<sys/types.h> <sys/stat.h>

函数功能:

创建一个有名管道文件

参数:

Filename:(可带路径的)文件名

Mode :指定创建的管道的访问权限,一般用8进制数表示

返回值:

成功:0

失败:-1

 

注意:

可以使不相关(如两个.c文件一个向管道文件写数据,一个从管道文件读数据)的两个进程互相通信。---两个文件编译为不同的可执行文件名

有名管道文件数据交互时在内核中交互,在本地只会存在管道文件名。

 

2.3:信号

2.3.1:定义

在软件层次上对中断机制的一种模拟,是一种异步通信方式

2.3.2:特点

直接进行用户空间进程和内核进程之间的交互(与管道的区别)

2.3.3生存周期

 

信号由内核产生

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

忽略信号:SIGKILL SIGSTOP不能忽视

捕捉信号(注册信号)

执行缺省操作:对信号规定的默认操作

2.3.4处理流程

----类似中断处理过程

2.3.5使用场合

后台进程

若两个进程无亲缘关系,无法使用无名管道

通信进程之一只能使用标准输入输出,则无法使用有名管道(FIFO

2.3.6经常使用的信号

LINUX内核有64种信号,由内核产生并发出

SIGINT-----在用户输入INRT字符(ctrl+c,内核发出该信号,终止进程

SIGQUIT----ctrl+\SIGINT类似

SIGILL---(了解)在一个进程企图执行一条非法指令时

SIGKILL---立即结束程序运行,不能被阻塞 处理 忽略

SIGALRM(了解)

SIGSTOP---暂停当前进程,不能被阻塞 处理 忽略

SIGCHLD---子进程改变壮态时,父进程会收到这个信号-----默认操作忽略

2.3.7发送与捕捉

1>kill()  raise()

头文件  sys/types.h<signal.h>

kill更灵活

(1)Int KILL(pid_t pid,int sig)--通知内核向进程发送信号

kill命令只是kill函数的一个借口

Int kill(pid_t pid,int  sig);

返回值:

成功:0

失败:-1

Kill(getpid(),SIGKILL)//通知内核向进程ID号发送信号,杀死本进程

Kill(217632,SIGKILL)

(2)Int raise(int sig)---通知内核向本进程发送信号

 

返回值:

成功:0

失败:-1

Raise(SIGKILL);//默认内核向当前进程发送信号

2alarm()  pause()

头文件<unistd.h>

(1) alarm()

Unsigned int alarm(unsigned int seconds)

参数:seconds:指定秒数

返回值:成功:如果此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0

(2) pose()

将进程挂起,与alarm结合使用,闹钟时间结束将退出程序

Alarm(3);pose();//3秒后退出进程

2.3.8用途

不能使用管道时,信号用于用户与内核进程通信,但通行内容固定(64种信号)

2.3.9处理

*******1signal函数

---信号注册处理函数

Void *signal (int signum,void (*handler)(int))(int);

头文件: <signal.h>

参数:

Signum:指定信号

Handler:SIG_IGN忽略该信号

SIG_DFL:采用系统默认方式处理信号

自定义的信号处理函数指针

返回值:出错:-1

成功:设置信号处理方式

如:signal(SIGINT,signl_hander);--注册信号和信号处理函数。当进程接受到SIGINT信号时就会跳转到信号处理函数,若信号没有注册就会采取默认方式处理

注:信号处理函数例子--void  handler(int  no)

2. IPC对象

注:KEY私有则创建的IPC对象只能在本.c 文件使用

创建的IPC对象系统不会自动回收,需手动删除

2.1共享内存

---就是一个内存地址可以被直接读写

共享内存需要与信号灯集一起使用,解决进程抢占资源问题

2.1.1特点

最为高效的进程间通信方式,进程可以直接读写内存,不需要任何数据的拷贝(效率最高---切换次数少)

在多个进程间交换信息,内核专门留出一块内存,可以由需要访问的进程将其映射到自己的私有地址空间

由于多个进程共享一块内存,因此需要依靠某种同步机制,如互斥锁信号量等

 

2.1.2实现

1)创建/打开共享内存

Int  shmget(key_t key ,int size,int shmflg)

函数功能:打开或创建打开共享内存

参数:key----IPC_PRIVATE(创建的共享内存只能在本.c中使用) 或 ftok的返回值

Size:共享内存区大小,单位为字节

Shmflg:同open函数的权限位,可以用8进制数表示

函数返回值:

成功:共享内存段标识符

出错:-1

例子:key_t key = ftok(“./”,’a’);

Shmget(key,120,IPC_CREAT |0640)--非私有共享内存

Shmget(IPC_PRIVATE,120,IPC_CREAT | 0640)--私有共享内存

Ipcrm  -m  空间字节:删除共享内存

Ipcs  -m:查看共享内存

注:创建非私有共享内存时,需要key(共享内存空间首地址或称共享内存的建值)

 

Eg:key_t key = ftok(“.”,’a’); ----通过“.”的i节点( inode i节点是指对文件的索引)与'a'的低8位,组合生成key值

2)映射共享内存

---把指定的共享内存映射到进程的地址空间用于访问

在此步骤后就可使用共享内存

 

例子:char *p = shmat(shm_id,NULL,0) if(p == (char *)-1) return ;

3)撤销共享内存映射

 

4)删除共享内存对象

 

sysytem--可以执行系统命令

#include <stdlib.h>

 

int system(const char *command);

如:system(“ipcs  -m”);

 

 

2.2消息队列(msg)

2.2.1定义与特点

IPC对象的一种,由消息队列ID唯一标识,是一个消息的列表,用户可以在消息队列中添加读取消息等,可按照类型来发送/接受消息

 

2.2.2操作(队列操作)

1)创建或打开消息队列

--在内核空间创建,可直接对其进行操作

 

2)添加消息

--向队列发送消息(只能尾插法,不能操作next指针(由系统指定的),所以不能在中间插入)

 

注:每条消息的类型必须为long

3)读取消息(队头取出)

--按照消息类型将消息从消息队列中取走

 

注:msgtype(只管0/>0

Ipcs -q : 查看消息队列

Ipcrm -q+size:删除字节

4)控制消息队列

 

2.3信号灯/量

定义:不同进程间或一个给定进程内部不同线程间同步的机制

2.3.1种类

有名信号灯(决定线程间同步机制的信号量)

无名信号灯(了解)--存在于内存的信号灯

系统5的信号灯(IPC对象)

1)二值信号灯

值为0或1.与互斥锁类似,资源可用时值为1,不可用时值为0.

2)计数信号灯

值在0-n之间。用来统计资源,其值代表资源数

3.2系统5的信号灯

1)定义:

一个或者多个信号灯的一个集合。其中每一个都是单独的计数信号灯。由内核维护

2)函数

 

1〉创建信号灯

 

2) P/V操作--申请/释放资源

 

注:flg:0---阻塞  IPC——NOWAIT--非阻塞

 

3) 管理信号灯

posted @ 2020-01-12 16:56  糖糖_彭  阅读(36)  评论(0编辑  收藏  举报