IO与进程

IO

学习IO目的:为了将数据存储以及对于文件的操作

标准IO:

  1. ANSI(美国标准协会)联合ISO(国际化标准组织)所形成的的一个C标准

   (C标准:属于C库,含有一些列输入输出函数),只要操作系统支持C库,就可以使用这一类标准IO提供的函数----》移植性比较高

  1. 操作的文件一般是普通文件
  2. 属于高级磁盘IO--》存在缓冲区,减少了用户态切换至内核态,最后又返回用户态这样的频繁操作,意味着减少了系统开销。
  3. 通过文件流(FILE *)操作文件(打开文件时,系统会自动将该文件的信息定义结构体类型struct FILE 来进行存储,因此可以通过FILE *文件指针来操作文件)

文件IO

POSIX(可移植操作系统接口)推出的对于支持POSIX标准的系统可以操作文件的一系列函数(UNIX系统一般都会支持POSIX的标准)---》移植性不高(只能应用于UNIX系统)

  1. 操作的文件可以普通文件或者设备文件(硬件)
  2. 低级磁盘IO---》没有缓冲区,每一次都是系统调用,都会存在用户空间和内核空间的频繁切换工作,好处就是可以直接对于设备进行读写操作。
  3. 通过文件描述符来(非负的数字)操作文件

概念:文件被打开时,创建的结构体名为FILE的结构体指针,形象的称为“流”

分析:为啥称结构体指针为流?

--》因为标准IO存在缓冲区,所以每一次向缓冲区不断放入数据(每一次的放入数据:均是需要通过文件指针来进行读写指向的文件),存在三个特点:

  1. 有源头:APP
  2. 有目的:缓冲区
  3. 持续性:不断放入数据到缓冲区

---》一旦具备以上3个特点,就会形成流,所以通过文件指针操作文件可以理解为是通过操作流来操作文件

特性函数:

  1. perror(“string”);---》可以输出出错的原因
  2. feof(FILE* Stream);

   作用:判断文件是否抵达末尾(不管是文本文件还是二进制文件,都可以判断)

   返回值:

          抵达文件末尾---》返回值为非零

          未抵达文件末尾---》返回值0

操作文件IO:open   close   read   write

头文件:

       #include <sys/types.h>

       #include <sys/stat.h>

       #include <fcntl.h>

打开文件

函数原型:

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

       功能:打开指定的文件

       参数:

             参数1:所需打开文件的名字(包含路径)

             参数2:打开文件的方式---》主标志 | 副标志

       返回值:成功代表一个大于0 的数字(文件描述符),失败返回-1(errno ie set...)

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

       功能:打开指定的文件

       参数:

             参数1:所需打开文件的名字(包含路径)

             参数2:打开文件的方式---》主标志 | 副标志

             参数3:当需要O_CREAT时,就要写该参数来为创建的新文件指明权限(八进制表示)

       返回值:成功代表一个大于0 的数字(文件描述符),失败返回-1(errno ie set...)

关闭文件

       #include <unistd.h>

       int close(int fd);

       功能:关闭一个文件描述符

       参数:打开文件成功之后的文件描述符

  1. 写文件

       #include <unistd.h>

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

       功能:向指定的文件描述符的文件内写入内容

进程

进程:正在运行的程序.它是动态的、资源管理的最小单位  

交互进程:    

shell命令进程,文本编辑器(vim),图形应用程序等

既有前台进程,又有后台进程

批处理进程     

        gcc(预处理,汇编,编译,链接)  gcc 1.c -o 1 (四步合四为1)

        数据库搜索引擎

守护进程

        这类进程一直在后台运行,和任何终端都不关联,很多系统进程(各种服务)都是以守护进程的形式存在

进程间的状态

1.就绪态

        就绪状态:进程已获得除CPU外的所有必要资源,只等待CPU时的状态。一个系统会将多个处于就绪状态的进程排成一个就绪队列。

2.运行态  (R) TASK_RUNNING

                执行状态:进程已获CPU,正在执行。单处理机系统中,处于执行状态的进程只一个;多处理机系统中,有多个处于执行状态的进程。

3.等待态 (阻塞态)

        阻塞状态:正在执行的进程由于某种原因而暂时无法继续执行,便放弃处理机而处于暂停状态,即进程执行受阻。(这种状态又称等待状态或封锁状态)

4.停止态          TASK_STOPPED

5.死亡态           EXIT_DEAD

6.僵尸态           EXIT_ZOMIE

进程函数: 进程创建 fork vfork()

进程执行

1.父子执行顺序:不确定

2.若父进程先结束,子进程未结束,子进程就会称为孤儿进程

3.若子进程先结束,父进程未结束,但父进程没有回收子进程的内核资源,子进程会称为僵尸进程进程

主动退出

(1).exit()函数叫C语言库提供,库函数._exit()函数叫系统调用函数,Linux系统提供的

(2).exit()自带清理缓冲区,_exit()不清理缓冲区

如何避免僵尸进程的产生(waitpid()回收子进程资源)

wait()       阻塞回收

waitpid();    非阻塞回收

不接受子进程退出的状态

为什么要使用Exec函数族

(1)fork()子进程是为了执行新的程序

(2)可以直接在子进程if中写入新程序的代码,但是不够灵活,因为只能将子进程程序的源代码贴过来执行(必须知道源码,而且源码太长不好控制)比如要执行 ls -la命令就不行。

(3)使用exec运行新的可执行程序(可以把一个编译好的的可执行程序直接加载运行)

(4)我们有了exec族函数后,我们典型的父子进程就是这样的:子进程需要运行的程序被单独编写、单独编译成一个可执行文件(叫hello),项目是一个多进程项目,主程序叫父进程,fork创建了一个子进程后在进程中exec来执行hello,达到父子进程分别作不同的程序(宏观上)同时运行

什么是守护进程

1.它是守护进程,也就是通常所说的Daemon进程,是Linux三种进程之一,通常在系统启动时运行,在系统关闭时结束.

2.始终运行在后台,后台服务进程

3.独立于任何终端(和终端无关)。

4.周期性地执行某种任务或等待处理特定事件

什么会话

 Linux是以会话(session),进程组的方式管理进程.每个进程属于一个进程组,子进程同属于该进程组.会话是由一个或者多个进程组的集合,通常用户打开一个终端,系统就会创建一个会话,所有通过该终端运行都属于这个会话,shell进程--->会话组的组长,一个会话最多打开一个控制终端,当控制终端结束时,所有的进程也跟着结束。

线程

为什么学习线程

(1).由于进程的地址空间是私有的,因此在进程间上下文切换时,系统开销比较大

(2).为了提高系统的性能,许多操作系统规范里引入了轻量级进程的概念,也被称为线程

(3).在同一个进程中创建的线程共享该进程的地址空间(除了栈区之

2.线程的相关函数

线程的创建 pthread_create 

等待线程结束 pthread_join()

(1).pthread_join函数在线程退出时,用来清理线程资源。

(2).phtread_join函数会阻塞调用方,直至pthread_join所指的线程退出。

pthread_detach()

因为线程默认的状态是结合态的,所以,可以通过pthread_detach函数来设置线程为分离态。

pthread_detach函数的特点

使用pthread_detach函数后,使线程处于分离态;

使用pthread_detach函数后,线程在退出后,会自己清理资源

相较pthread_join,使用pthread_detach函数不会阻塞主线程,但是无法获取线程的返回值

线程出现的问题:

什么是互斥

1.多线程访问临界资源时,没有顺序而言,

若线程A访问临界资源,不允许其他线程访问资源。

2.引入互斥锁:用来保证共享数据操作的完整性。

什么是同步

多线程访问临界资源,按照某种顺序执行

无名信号量:用于线程间的同步

有名信号量:用于进程间的同步

无名信号量

信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问.

当信号量用于同步操作时,往往会设置多个信号量,并安排不同的初始值来实现他们之间的顺序执行

进程间的通讯方式

传统的unix通信方式

无名管道的特点:

具有亲缘关系(父子关系,兄弟关系)间的进程间通信

管道也可以看成是一个特殊的文件,对于它的读写也可以使用普通的read(),wirte()等函数,它不属于任何文件系统,存在内核当中.

它是一个半双工的通信模式,具有固定的读端(fd[0])和写端(fd[1])。

无名管道需要注意的问题

只有在管道的读端存在时,向管道中写入数据才有意义。否则,向管道中写入数据的进程将收到内核传来的SIGPIPE信号(通常Broken pipe错误)

有名管道的特点:

1.适用于两个不相关的进程间的通信,是一个半双工的通信

2.有名管道可以通过路径名来指出,并且在文件系统中可见,两个进程可以把它当成普通文件进行操作,open,read,write,close

3.有名管道必须遵循先进先出的原则,不支持lseek()函数

信号的特点:是唯一一种异步通信方式

SystemV IPC(进程间通信方式)

消息队列

消息队列特点

1.消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等。

2.消息队列可以按照类型来发送/接收消息,相同的类型满足先进先出,不同的类型可以随意存取.

3.全双工通信方式

共享内存

1.共享内存的特点

  • 共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝
  • 为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间

优点:

进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。

缺点:

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

信号量一般和共享内存配合使用

1.无名信号量:适用于线程同步

sem_t

sem_init(); 初始化信号量

sem_wait(); P操作    -1

 

sem_post(); V操作   +1

sem_getvalue() 获得信号的值