I/O进程(全)
I/O
一、标准IO
1.概念
2.特点
(1).通过缓冲机制减少系统调用,提高效率
(2.)围绕流进行操作,流用FILE *来描述 (3).标准IO默认打开了三个流,stdin(标准输入)、stdout(标准输出)、stderr(标准错误) (4).一般操作普通文件 3.缓冲区
(1).全缓冲:与文件相关 缓冲区刷新条件: 程序正常退出:return(main) exit 缓冲区溢出 强制刷新fflush fclose关闭对应的流 (2).行缓冲:与终端相关 缓冲区刷新条件: \n 程序正常退出:return(main) exit 缓冲区溢出 强制刷新fflush fclose关闭对应的流 (3).不缓冲:没有缓冲区,标准错误 计算缓冲区大小(1kb)
4.函数接口
4.1打开文件fopen
FILE *fopen(const char *path, const char *mode) 功能:打开文件 参数:path:打开的文件 mode:打开的方式 r:只读,当文件不存在时报错,文件流定位到文件开头 r+:可读可写,当文件不存在时报错,文件流定位到文件开头 w:只写,文件不存在创建,存在清空 w+:可读可写,文件不存在创建,存在清空 a:追加(在末尾写),文件不存在创建,存在追加,文件流定位到文件末尾 a+:读和追加,文件不存在创建,存在追加,读文件流定位到文件开头,写文件流定位到文件末尾 注:当a的方式打开文件时,写只能在末尾进行追加,定位操作是无法改变写的位置,但是可以改变读的位置 返回值:成功:文件流;失败:NULL,并且会设置错误码。 4.2关闭文件 fclose
int fclose(FILE* stream); 功能:关闭文件 参数:stream:文件流
4.3读写操作fread fwrite fgets fputs
(1).按照字符串读写
char * fgets(char *s, int size, FILE * stream); 功能:从文件中每次读取一行字符串 参数:s:存放字符串的地址 size:一次读取的字符个数 stream:文件流 返回值:成功:s的地址;失败或读到文件末尾:NULL特性:每次实际读取的字符个数为size-1个,会在末尾自动添加\0 每次读一行,遇到\n后不再继续,读下一行。int fputs(const char *s, FILE * stream); 功能:向文件中写字符串 参数:s:要写的内容 stream:文件流 返回值:成功:非负整数;失败:EOF char * fgets(char *s, int size, FILE * stream); 功能:从文件中每次读取一行字符串 参数:s:存放字符串的地址 size:一次读取的字符个数 stream:文件流 返回值:成功:s的地址;失败或读到文件末尾:NULL 特性:每次实际读取的字符个数为size-1个,会在末尾自动添加\0 每次读一行,遇到\n后不再继续,读下一行。 int fputs(const char *s, FILE * stream); 功能:向文件中写字符串 参数:s:要写的内容 stream:文件流 返回值:成功:非负整数;失败:EOF
练习:通过fgets实现"wc -l 文件名"命令功能(计算文件行数)
(2).按照二进制的方式进行读写:一般操作二进制文件
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); 功能:从文件流读取多个元素 参数:ptr :用来存放读取元素 size :元素大小 sizeof(数据类型) nmemb :读取元素的个数 stream :要读取的文件 返回值:成功:读取的元素的个数; 读到文件尾: 0 失败: -1 size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); 功能:按对象写 参数:同上 返回值:成功:写的元素个数;失败 :-1
4.4文件定位操作
void rewind(FILE *stream); 功能:将文件位置指针定位到起始位置 int fseek(FILE *stream, long offset, int whence); 功能:文件的定位操作 参数:stream:文件流 offset:偏移量:正数表示向后文件尾部偏移,负数表示向文件开头偏移 whence:相对位置: SEEK_SET:相对于文件开头 SEEK_CUR:相对于文件当前位置 SEEK_END:相对于文件末尾 返回值:成功:0;失败:-1 注:当打开文件的方式为a或a+时,fseek不起作用 long ftell(FILE *stream); 功能:获取当前的文件位置 参数:要检测的文件流 返回值:成功:当前的文件位置,出错:-1
二、文件IO
1.概念
在系统中(posix)定义的一组用于输入输出的接口 posix:可移植操作系统的接口 2.特点
(1).没有缓冲机制,每次调用都会引起系统调用 (2).围绕文件描述符进行操作,文件描述符都是非负整数(>=0),依次分配 (3).文件IO默认打开了三个文件描述符,分别是0(标准输入),1(标准输出),2(标准错误) (4).可以操作任意类型的文件,目录文件除外。 3.函数接口
3.1打开文件 open
int open(const char *pathname, int flags); 功能:打开文件 参数: pathname:文件路径名 flags:打开文件的方式 O_RDONLY:只读 O_WRONLY:只写 O_RDWR:可读可写 O_CREAT:创建 O_TRUNC:清空 O_APPEND:追加 O_EXCL:判错 返回值:成功:文件描述符;失败:-1 当第二个参数中有O_CREAT选项时,需要给open函数传递第三个参数,指定创建文件的权限。 int open(const char *pathname, int flags, mode_t mode); 创建出来的文件权限为指定权限值&(~umask) //umask为文件权限掩码 3.2关闭文件 close
int close(int fd); 功能:关闭文件 参数:fd:文件描述符
3.3读写操作 read write
ssize_t read(int fd, void *buf, size_t count); 功能:从一个已打开的可读文件中读取数据 参数: fd 文件描述符 buf 存放位置 count 期望的个数 返回值: 成功:实际读到的个数 返回-1:表示出错,并设置errno号 返回0:表示读到文件结尾 ssize_t write(int fd, const void *buf, size_t count); 功能:向指定文件描述符中,写入 count个字节的数据。 参数: fd 文件描述符 buf 要写的内容 count 期望值 返回值:成功:实际写入数据的个数。失败 : -1 3.4定位操作 lseek
off_t lseek(int fd, off_t offset, int whence); 功能:设定文件的偏移位置 参数: fd:文件描述符 offset偏移量 正数:向文件结尾位置移动 负数:向文件开始位置 whence 相对位置 SEEK_SET 开始位置 SEEK_CUR 当前位置 SEEK_END 结尾位置 返回值:成功:文件的当前位置 。失败:-1
三、文件IO与标准IO对比
四、库
1.库的定义
通俗讲将用户写好的程序打包形成一个整体;当其他用户或其他模块使用时,只要有这个库文件就可以,不需要源代码。也可以理解为一组预先编译好的方法集合。 本质上来说库就是一种可执行代码的二进制形式。 当要使用别人的函数的时候除了包含头文件还要有库 。 linux系统存储库的位置一般在/lib或/usr/lib 2.库的分类
静态库和动态库,本质区别是代码被载入时刻不同。 (1).静态库在程序编译时会被连接到目标代码中。 优点:程序运行时将不再需要该静态库;运行时无需加载库,运行速度更快 缺点:静态库中的代码复制到了程序中,因此体积较大; 静态库升级后,程序需要重新编译链接 (2).动态库是在程序运行时才被载入代码中。 优点:程序在执行时加载动态库,代码体积小; 程序升级更简单; 不同应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。 缺点:运行时还需要动态库的存在,移植性较差 3.静态库的制作
(1).将源文件生成目标文件(.o) gcc -c xxx.c -o xxx.o (2).创建静态库文件,用ar指令,它会将许多.o文件转换为.a文件 ar crs libxxx.a xxx.o 静态库文件名命名规范:lib为前缀,紧跟的是静态库名 ,.a为扩展名 (3).测试静态库使用 gcc xxx.c -L指定库的路径 -l指定库名
4.动态库制作
(1).用gcc创建共享库/动态库 gcc -fPIC -c xxx.c -o xxx.o -fPIC:创建与地址无关的编译程序 gcc -shared -o libxxx.so xxx.o (2).测试动态库的使用 gcc xxx.c -L指定库的路径 -l指定库名
解决方法
(1).将库拷贝到系统库路径下(/lib /usr/lib)(编译时不需要指定库的路径) (2).更改环境变量,将库所在的路径添加到环境变量内 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. (只更改当前终端的环境变量,切换终端环境变量就不存在了) (3).添加/etc/ld.so.conf.d/*.conf文件。把库所在的路径加到文件末尾,并执行ldconfig刷新。 sudo vi /etc/ld.so.conf.d/*.conf 添加动态库存在的路径 刷新:sudo ldconfig 五、进程
1.进程、程序
程序:编译好的可执行文件 存放在磁盘上的指令和数据的有序集合 程序是静态的,没有任何执行的概念 进程:一次程序的运行过程,一个独立的可调度的任务 执行一个程序所分配资源的总称 进程是动态的,包括创建、调度、执行和消亡。 2.进程的特点
(1).系统会为每个进程分配0-4G的虚拟空间,其中0-3G为每个进程所独有,3-4G为所有进程所共有。
(2).CPU调度进程时会为进程分配时间片(几毫秒-十几毫秒之间),当时间片用完之后,CPU会再进行其他进程的调度,实现进程的轮转,进而实现多任务操作。 3.进程段
Linux中的进程包含五个段: “BSS段”存放程序中未初始化的全局变量的一块内存区域 “数据段”已初始化的全局变量的一块内存区域。 “代码段”存放程序执行代码的一块内存区域 “堆段”存放进程运行中被动态分配的内存段 “栈段”又称为“堆栈段”存放的是函数的返回地址、函数的参数以及程序中的局部变量 4.进程分类
交互进程:该类进程是由shell控制和运行的。交互进程既可以在前台运行,也可以在后台运行。该类进程经常与用户进行交互,需要等待用户的输入,当接收到用户的输入后,该类进程会立刻响应,典型的交互式进程有:shell命令进程、文本编辑器等。 批处理进程:该类进程不属于某个终端,它被提交到一个队列中以便顺序执行。 守护进程:该类进程在后台运行。它一般在Linux启动时开始执行,系统关闭时才结束。 5.进程状态
(1).运行态(TASK_RUNNING):R 指正在被CPU运行或者就绪的状态。这样的进程被成为runnning进程。 (2).睡眠态(等待态): 可中断睡眠态(TASK_INTERRUPTIBLE)S:处于等待状态中的进程,一旦被该进程等待的资源被释放,那么该进程就会进入运行状态。 不可中断睡眠态(TASK_UNINTERRUPTIBLE)D:该状态的进程只能用wake_up()函数唤醒。 (3).暂停态(TASK_STOPPED):T 当进程收到信号SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU时就会进入暂停状态。可向其发送SIGCONT信号让进程转换到可运行状态。 (4).死亡态:进程结束 X (5).僵尸态(TASK_ZOMBIE):Z 当进程已经终止运行,但还占用系统资源,要避免僵尸态的产生 6.进程状态切换
进程创建后,进程会进入就绪态,当CPU调度到此进程进入执行态,当时间片用完,此进程会进入就绪态等待CPU的下一次调度;当此进程在执行过程中需要执行某些IO操作(阻塞操作)会进入阻塞态,当完成IO操作时又可进入就绪态,等待CPU调度,当进程运行结束进入结束态。
7.函数接口
7.1创建子进程
pid_t fork(void); 功能:创建子进程 返回值: 成功:在父进程中:返回子进程的进程号 >0 在子进程中:返回值为0 失败:-1并设置errno
特点
1.子进程几乎拷贝了父进程的所有内容,包括代码、数据,缓冲区,系统数据段中的值,栈中的数据,父进程打开的文件,但是PID,PPID不同 2.fork之前的代码会被复制但是不会被重新执行,fork之后的代码会被复制,并且父子进程分别执行一遍。 3.父子进程的空间相互独立,互不影响,当在相应的进程中改变全局变量,静态变量,都互不影响 4.fork之前打开的文件,fork之后拿到的是同一个文件描述符,操作的是同一个文件指针 5.若父进程先结束,子进程成为孤儿进程,被init进程所收养,会变成后台进程。 6.若子进程先结束,父进程不结束,父进程没有及时回收,子进程就会变成僵尸进程(避免僵尸进程的产生) 8.回收进程
pid_t wait(int *status); 功能:回收子进程资源(阻塞) 参数:status:子进程退出状态,不接受子进程状态设为NULL 返回值:成功:回收的子进程的进程号 失败:-1 pid_t waitpid(pid_t pid, int *status, int options); 功能:回收子进程资源 参数: pid:>0 指定子进程进程号 =-1 任意子进程 =0 等待其组ID等于调用进程的组ID的任一子进程 <-1 等待其组ID等于pid的绝对值的任一子进程 status:子进程退出状态 options:0:阻塞 WNOHANG:非阻塞 返回值:正常:结束的子进程的进程号 当使用选项WNOHANG且没有子进程结束时:0 出错:-1
9.结束进程
void exit(int status); 功能:结束进程,刷新缓存 参数:退出的状态 不返回。
10.获取进程号
pid_t getpid(void); 功能:获取当前进程的进程号 pid_t getppid(void); 功能:获取当前进程的父进程号
六、进程间通信
1.为什么要进行进程间通信
2.进程间通信方式(7)
(1).早期进程间通信 无名管道(pipe)、有名管道(fifo)、信号(sem) (2).system V IPC通信 共享内存(share memory)、消息队列(message queue)、信号灯集(semaphore) (3).BSD: 套接字(socket) 3.无名管道
3.1原理图
通信原理:一个进程的输出可以当作另一进程的输入 3.2特点
(1).只能用于具有亲缘关系的进程间通信 (2).半双工通信模式,具有固定的读端和写端 {单工:只能单方向通信, 广播 半双工:可以双向通信,但是同一时间不可以 对讲机 全双工:可以双向同时通信 打电话} (3).无名管道可以被看做一个特殊的文件,对于他的读写可以使用文件IO函数 (注意:不是文件,他只是存在于内核空间的一部分,无实际文件) (4).管道基于文件描述符进行通信。当一个管道建立的时候,它会自动创建两个文件描述符,一个用于读fd[0],一个用于写fd[1]。 3.3函数
int pipe(int fd[2]) 功能:创建无名管道 参数:文件描述符 fd[0]:读端 fd[1]:写端 返回值:成功 0 失败 -1 4.有名管道(FIFO)
4.1特点
(1).可以使互不相干的两个进程通信 (2).有名管道可以通过路径名来指出,并在文件系统中可见,但是内容存储在内存中 (3).进程通过文件IO操作有名管道 (4).有名管道遵循先进先出的原则,不支持lseek()操作 (5).半双工通信 4.2函数
int mkfifo(const char *filename,mode_t mode); 功能:创建有名管道 参数:filename:有名管道文件名 mode:权限 返回值:成功:0 失败:-1,并设置errno号 七、信号
1.例子
kill -l:查看系统中信号 kill -num pid:给pid进程发送pid信号 2.概念
(1).信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式。 (2).信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。 (3).如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。 3.信号响应方式
(1.)忽略信号:对信号不做任何处理,但是有两个信号不能做忽略处理:SIGKILL和SIGSTOP (2).捕捉信号:定义信号处理函数,当信号发生的时候,执行相应的处理函数,但是有两个信号不能做捕捉处理:SIGKILL和SIGSTOP (3).执行缺省操作:linux对每种信号都规定了默认信号。 4.信号的种类
2)SIGINT:结束进程,对应快捷方式ctrl+c 3)SIGQUIT:退出信号,对应快捷方式ctrl+\ 9)SIGKILL:结束进程,不能被忽略不能被捕捉 14)SIGALRM:闹钟信号,alarm函数设置定时,当到设定的时间时,内核会向进程发送此信号结束进程。 15)SIGTERM:结束终端进程,kill 使用时不加数字默认是此信号 17)SIGCHLD:子进程状态改变时给父进程发的信号 19)SIGSTOP:暂停进程,不能被忽略不能被捕捉 20)SIGTSTP:暂停信号,对应快捷方式ctrl+z 信号的种类 在Linux中,信号被分为不可靠信号和可靠信号,一共64种,可以通过kill -l命令来查看 ●不可靠信号:也称为非实时信号,不支持排队,信号可能会丢失,比如发送多次相同的信号,进程只能收到一次,信号值取值区间为1~31 ●可靠信号:也称为实时信号,支持排队,信号不会丢失,发多少次,就可以收到多少次,信号值取值区间为32~64 信号产生的方式有如下几种: ● 对于前台进程,用户可以输入特殊终端字符来发送,比如输入Ctrl+C ● 系统异常,比如浮点异常和非法内存段访问 ● 系统状态变化,比如alarm定时器到期时将引起SIGALRM信号 ● 在终端运行kill命令或在程序中调用kill函数 5.函数接口
5.1发送信号
int kill(pid_t pid, int sig); 功能:信号发送 参数:pid:指定进程 sig:要发送的信号 返回值:成功 0 失败 -1 int raise(int sig); 功能:进程向自己发送信号 参数:sig:信号 返回值:成功 0 失败 -1 int pause(void); 功能:用于将调用进程挂起,直到收到信号为止。
5.2定时器
unsigned int alarm(unsigned int seconds) 功能:在进程中设置一个定时器 参数:seconds:定时时间,单位为秒 返回值:如果调用此alarm()前,进程中已经设置了闹钟时间,则 返回上一个闹钟时间的剩余时间,否则返回0。 注意:一个进程只能有一个闹钟时间。如果在调用alarm时 已设置过闹钟时间,则之前的闹钟时间被新值所代替
5.3信号处理
#include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); 功能:信号处理函数 参数: signum:要处理的信号 handler:信号处理方式 SIG_IGN:忽略信号 SIG_DFL:执行默认操作 handler:捕捉信号 void handler(int sig){} //函数名可以自定义 返回值:成功:设置之前的信号处理方式 失败:-1
八、共享内存
1.特点
(1).共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,不需要进行任何数据的拷贝 (2).为了在多个进程间进行数据的交互,内核专门留了一块内存区,可以由需要访问的进程将其映射到自己的地址空间 (3).由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等 2.编程步骤
(1).创建或者打开共享内存shmget (2).映射共享内存到自己的用户空间shmat (3).使用共享内存 (4).撤销映射 (5).删除共享内存
3.函数接口
int shmget(key_t key, size_t size, int shmflg); 功能:创建或打开共享内存 参数: key 键值 size 共享内存的大小 shmflg IPC_CREAT|IPC_EXCL(判错)|0666 返回值:成功 shmid 出错 -1
void *shmat(int shmid,const void *shmaddr,int shmflg); 功能:映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问 参数: shmid 共享内存的id号 shmaddr 一般为NULL,表示由系统自动完成映射,如果不为NULL,那么由用户指定 shmflg:SHM_RDONLY就是对该共享内存只进行读操作 ,0 可读可写 返回值:成功:完成映射后的地址, 失败:-1的地址 用法:if((p = (char *)shmat(shmid,NULL,0)) == (char *)-1) int shmdt(const void *shmaddr); 功能:取消映射 参数:要取消的地址 返回值:成功0 失败的-1 int shmctl(int shmid,int cmd,struct shmid_ds *buf); 功能:(删除共享内存),对共享内存进行各种操作 参数: shmid 共享内存的id号 cmd IPC_STAT 获得shmid属性信息,存放在第三参数 IPC_SET 设置shmid属性信息,要设置的属性放在第三参数 IPC_RMID:删除共享内存,此时第三个参数为NULL即可 返回:成功0 失败-1 用法:shmctl(shmid,IPC_RMID,NULL);
4.命令
ipcs -m:查看系统中共享内存 ipcrm -m shmid :删除共享内存 九、信号灯集
1.概念
信号灯(semaphore),也叫信号量。它是不同进程间或一个给定进程内部不同线程间同步的机制;System V的信号灯是一个或者多个信号灯的一个集合。其中的每一个都是单独的计数信号灯。 通过信号灯集实现共享内存的同步操作。 2.编程
(1).创建key值 (2).创建或打开信号灯集:semget(semaphore) (3).初始化信号灯:semctl (4).PV操作:semop (5).删除信号灯集:semctl 3.函数接口
int semget(key_t key, int nsems, int semflg); 功能:创建/打开信号灯 参数: key:ftok产生的key值 nsems:信号灯集中包含的信号灯数目 semflg:信号灯集的访问权限,通常为IPC_CREAT |IPC_EXCL |0666 返回值:成功:信号灯集ID 失败:-1 int semctl ( int semid, int semnum, int cmd…/*union semun arg*/); 功能:信号灯集合的控制(初始化/删除) 参数: semid:信号灯集ID semnum: 要操作的集合中的信号灯编号 cmd: GETVAL:获取信号灯的值,返回值是获得值 SETVAL:设置信号灯的值,需要用到第四个参数:共用体 IPC_RMID:从系统中删除信号灯集合 返回值:成功 0 失败 -1 用法:初始化: union semun{ int val; }mysemun; mysemun.val = 10; semctl(semid, 0, SETVAL, mysemun); 获取信号灯值:函数semctl(semid, 0, GETVAL)的返回值 删除信号灯集:semctl(semid, 0, IPC_RMID); int semop ( int semid, struct sembuf *opsptr, size_t nops); 功能:对信号灯集合中的信号量进行PV操作 参数: semid:信号灯集ID opsptr:操作方式 nops: 要操作的信号灯的个数 1个 返回值:成功 :0 失败:-1 struct sembuf { short sem_num; // 要操作的信号灯的编号 short sem_op; // 0 : 等待,直到信号灯的值变成0 // 1 : 释放资源,V操作 // -1 : 申请资源,P操作 short sem_flg; // 0(阻塞),IPC_NOWAIT, SEM_UNDO }; 用法: 申请资源 P操作: mysembuf.sem_num = 0; mysembuf.sem_op = -1; mysembuf.sem_flg = 0; semop(semid, &mysembuf, 1); 释放资源 V操作: mysembuf.sem_num = 0; mysembuf.sem_op = 1; mysembuf.sem_flg = 0; semop(semid, &mysembuf, 1); 十、消息队列
1.特点
(1).消息队列是一种IPC对象,由消息队列ID来唯一标识 (2).消息队列就是一个消息的列表,用户可以在消息队列中添加消息,读取消息 (3).消息队列可以按照类型来进行消息的添加与读取 (4).消息队列是存在linux内核中,以链表的形式进行存放 2.编程步骤
(1).创建key值 (2).创建或打开消息队列 (3).使用:添加消息:按照类型将消息添加到已打开的消息队列末尾 读取消息:按照类型从消息队列中读取消息 (4).删除消息队列 3.函数接口
int msgget(key_t key, int flag); 功能:创建或打开一个消息队列 参数: key值 flag:创建消息队列的权限IPC_CREAT|IPC_EXCL|0666 返回值:成功:msgid 失败:-1 int msgsnd(int msqid, const void *msgp, size_t size, int flag); 功能:添加消息 参数: msqid:消息队列的ID msgp:指向消息的指针。常用消息结构msgbuf如下: struct msgbuf{ long mtype; //消息类型 char mtext[N]}; //消息正文 size:发送的消息正文的字节数 flag:IPC_NOWAIT消息没有发送完成函数也会立即返回 0:直到发送完成函数才返回 返回值:成功:0 失败:-1 使用:msgsnd(msgid, &msg,sizeof(msg)-sizeof(long), 0) 注意:消息结构除了第一个成员必须为long类型外,其他成员可以根据应用的需求自行定义。 int msgrcv(int msgid, void* msgp, size_t size, long msgtype, int flag); 功能:读取消息 参数: msgid:消息队列的ID msgp:存放读取消息的空间 size:接受的消息正文的字节数 msgtype:0:接收消息队列中第一个消息。 大于0:接收消息队列中第一个类型为msgtyp的消息. 小于0:接收消息队列中类型值不小于msgtyp的绝对值且类型值又最小的消息。 flag:0:若无消息函数会一直阻塞 IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG 返回值:成功:接收到的消息的长度 失败:-1 int msgctl ( int msgqid, int cmd, struct msqid_ds *buf ); 功能:对消息队列的操作,删除消息队列 参数: msqid:消息队列的队列ID cmd: IPC_STAT:读取消息队列的属性,并将其保存在buf指向的缓冲区中。 IPC_SET:设置消息队列的属性。这个值取自buf参数。 IPC_RMID:从系统中删除消息队列。 buf:消息队列缓冲区 返回值:成功:0 失败:-1 用法:msgctl(msgid, IPC_RMID, NULL)
4.命令
ipcs -q:查看系统中消息队列 ipcrm -q msgid:删除系统中的消息队列
十一、线程:实现多任务编程 1.概念
线程是一个轻量级的进程,为了提高系统性能引入的线程 Linux里同样用task_struct来描述一个线程。 线程和进程都参与统一的调度。 2.进程与线程的区别
共性:都为操作系统提供了并发执行能力。 不同点: 调度和资源:线程是系统调度的最小单位,进程是资源分配的最小单位。地址空间方面:同一个进程创建的多个线程共享进程的资源;进程的地址空间相互独立。通信方面:线程通信相对简单,只需要通过全局变量可以实现,但是需要考虑临界资源访问的问题;进程通信比较复杂,需要借助进程间的通信机制(借助3g-4g内核空间)。安全性方面:线程安全性差一些,当进程结束时会导致所有线程退出;进程相对安全。 3.线程资源
共享的资源:可执行的指令、静态数据、进程中打开的文件描述符、信号处理函数、当前工作目录、用户ID、用户组ID 私有的资源:线程ID (TID)、PC(程序计数器)和相关寄存器、堆栈、错误号 (errno)、信号掩码和优先级、执行状态和属性 4.函数接口
4.1创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); 功能:创建线程 参数: thread:线程标识 attr:线程属性,NULL:代表设置默认属性 start_routine:函数名:代表线程函数 arg:用来给前面函数传参 返回值:成功:0 失败:错误码
4.2退出线程
int pthread_exit(void *value_ptr) 功能:用于退出线程的执行 参数:value_ptr:线程退出时返回的值(任意类型) 返回值:成功 : 0 失败:errno 4.3线程回收
int pthread_join(pthread_t thread, void **value_ptr) 功能:用于等待一个指定的线程结束,阻塞函数 参数: thread:创建的线程对象 value_ptr:指针*value_ptr指向线程返回的参数 返回值:成功 : 0 失败:errno int pthread_detach(pthread_t thread); 功能:让线程结束时自动回收线程资源,让线程和主线程分离 参数:thread:线程ID
4.4获取线程号
pthread_t pthread_self(void); 功能: 获取线程号 返回值: 成功:调用此函数线程的ID
4.5练习: 通过线程实现数据的交互,主线程循环从终端输入,线程函数将数据循环输出,当输入quit结束程序。(输入一次,打印一次) 标志位:输入是否完成/输出是否完成
5.线程同步:信号量
5.1linux信号量的分类
(1). 内核信号量 由内核控制路径使用,类似于自旋锁 (2). Posix信号量 a. 无名信号量:数据存储在内存中,通常在线程间使用或父子进程间 函数接口:sem_init\sem_wait\sem_post b. 有名信号量:数据存储在文件中,在进程间线程间都可以使用 函数接口:sem_open\sem_wait\sem_post\sem_close (3). System V信号量 是信号量的集合,叫信号灯集,属于IPC对象 函数接口:semget\semctl\semop 5.2无名信号量基础
通过信号量实现线程间同步。 信号量:通过信号量实现同步操作;由信号量来决定线程是继续运行还是阻塞等待。信号量代表某一类资源,其值表示系统中该资源的数量,信号量值>0,表示有资源可以用,可以申请到资源,继续执行程序,信号量值<=0,表示没有资源可以用,无法申请到资源,阻塞。 信号量是一个受保护的变量,只能通过三种操作来访问:初始化sem_init、P操作(申请资源)sem_wait、V操作(释放资源)sem_post。信号量的值为非负整数。 5.3函数接口
int sem_init(sem_t *sem, int pshared, unsigned int value) 功能:初始化信号量 参数: sem:初始化的信号量对象 pshared:信号量共享的范围(0: 线程间使用 非0:1进程间使用) value:信号量初值 返回值:成功 0 失败 -1 int sem_wait(sem_t *sem) 功能:申请资源 P操作 参数:sem:信号量对象 返回值:成功 0 失败 -1 注:此函数执行过程,当信号量的值大于0时,表示有资源可以用,则继续执行,同时对信号量减1;当信号量的值等于0时,表示没有资源可以使用,函数阻塞。 int sem_post(sem_t *sem) 功能:释放资源 V操作 参数:sem:信号量对象 返回值:成功 0 失败 -1 注:释放一次信号量的值加1,函数不阻塞 十二、线程同步
1.互斥
1.1概念
临界资源:一次仅允许一个进程所使用的资源 临界区:指的是一个访问共享资源的程序片段 互斥:多个线程在访问临界资源时,同一时间只能一个线程访问 互斥锁:通过互斥锁可以实现互斥机制,主要用来保护临界资源,每个临界资源都由一个互斥锁来保护,线程必须先获得互斥锁才能访问临界资源,访问完资源后释放该锁。如果无法获得锁,线程会阻塞直到获得锁为止。 1.2函数
int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr) 功能:初始化互斥锁 参数: mutex:互斥锁 attr: 互斥锁属性 // NULL表示缺省属性 返回值:成功 0 失败 -1 int pthread_mutex_lock(pthread_mutex_t *mutex) 功能:申请互斥锁 参数:mutex:互斥锁 返回值:成功 0 失败 -1 注:和pthread_mutex_trylock区别:pthread_mutex_lock是阻塞的;pthread_mutex_trylock不阻塞,如果申请不到锁会立刻返回 int pthread_mutex_unlock(pthread_mutex_t *mutex) 功能:释放互斥锁 参数:mutex:互斥锁 返回值:成功 0 失败 -1 int pthread_mutex_destroy(pthread_mutex_t *mutex) 功能:销毁互斥锁 参数:mutex:互斥锁 1.3练习
int a[10]={0,1,2,3,4,5,6,7,8,9}; 两个线程:一个线程打印,一个线程倒置
1.4死锁
1.4.1概念
是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。 1.4.2死锁产生的四个必要条件
(1)、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用 (2)、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。 (3)、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。 (4)、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。 注意:当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。 2.条件变量
2.1步骤
pthread_cond_init:初始化 pthread_cond_wait:阻塞等待条件产生,没有条件产生时阻塞,同时解锁,当条件产生时结束阻塞,再次上锁。 pthread_mutex_lock(); //上锁 pthread_cond_wait(cond, lock); //如果没有条件产生时,解锁,当等待到条件产生时,上锁。 pthread_cond_signal:产生条件,不阻塞 pthread_cond_wait先执行,pthread_cond_signal再产生条件 2.2函数接口
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr); 功能:初始化条件变量 参数: cond:是一个指向结构pthread_cond_t的指针 restrict attr:是一个指向结构pthread_condattr_t的指针,一般设为NULL 返回值:成功:0 失败:非0 int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); 功能:等待信号的产生 参数: restrict cond:要等待的条件 restrict mutex:对应的锁 返回值:成功:0,失败:不为0 注:当没有条件产生时函数会阻塞,同时会将锁解开;如果等待到条件产生,函数会结束阻塞同时进行上锁。 int pthread_cond_signal(pthread_cond_t *cond); 功能:给条件变量发送信号 参数:cond:条件变量值 返回值:成功:0,失败:非0 注:必须等待pthread_cond_wait函数先执行,再产生条件才可以 int pthread_cond_destroy(pthread_cond_t *cond); 功能:将条件变量销毁 参数:cond:条件变量值 返回值:成功:0, 失败:非0
十三、linux IO模型
1.阻塞式IO:是常见,效率低,不浪费CPU 2.非阻塞式IO:轮询、耗费CPU、可以处理多路IO
2.1通过函数自带参数进行设置
2.2.通过设置文件描述符属性设置非阻塞
int fcntl(int fd, int cmd, ... /* arg */ ); 功能:设置文件描述符属性 参数: fd:文件描述符 cmd:设置方式 - 功能选择 F_GETFL 获取文件描述符的状态信息 第三个参数化忽略 F_SETFL 设置文件描述符的状态信息 通过第三个参数设置 O_NONBLOCK 非阻塞 O_ASYNC 异步 O_SYNC 同步 arg:设置的值 in 返回值: 特殊选择返回特殊值 - F_GETFL 返回的状态值(int) 其他:成功0 失败-1,更新errno 使用: 0为例 0-原本:阻塞、读权限 修改或添加非阻塞 int flags=fcntl(0,F_GETFL);//1.获取文件描述符原有的属性信息 flags = flags | O_NONBLOCK;//2.修改添加权限 fcntl(0,F_SETFL,flags); //3.将修改好的权限设置回去
3.信号驱动IO:异步通知方式,底层驱动的支持
异步通知:异步通知是一种非阻塞的通知机制,发送方发送通知后不需要等待接收方的响应或确认。通知发送后,发送方可以继续执行其他操作,而无需等待接收方处理通知。
(1). 通过信号方式,当内核检测到设备数据后,会主动给应用发送信号SIGIO。 (2). 应用程序收到信号后做异步处理即可。 (3).应用程序需要把自己的进程号告诉内核,并打开异步通知机制。
//1.设置将文件描述符和进程号提交给内核驱动 //一旦fd有事件响应, 则内核驱动会给进程号发送一个SIGIO的信号 fcntl(fd,F_SETOWN,getpid()); //2.设置异步通知 int flags; flags = fcntl(fd, F_GETFL); //获取原属性 flags |= O_ASYNC; //给flags设置异步 O_ASUNC 通知 fcntl(fd, F_SETFL, flags); //修改的属性设置进去,此时fd属于异步 //3.signal捕捉SIGIO信号 --- SIGIO:内核通知会进程有新的IO信号可用 //一旦内核给进程发送sigio信号,则执行handler signal(SIGIO,handler);
4.IO多路复用:select poll epoll
4.1
●应用程序中同时处理多路输入输出流,若采用阻塞模式,得不到预期的目的; ● 若采用非阻塞模式,对多个输入进行轮询,但又太浪费CPU时间; ● 若设置多个进程/线程,分别处理一条数据通路,将新产生进程/线程间的同步与通信问题,使程序变得更加复杂; ● 比较好的方法是使用I/O多路复用技术。其基本思想是: ○ 先构造一张有关描述符的表(最大1024),然后调用一个函数。 ○ 当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。 ○ 函数返回时告诉进程那个描述符已就绪,可以进行I/O操作。 4.2select
4.2.1特点
(1).一个进程最多只能监听1024个文件描述符 (2).select被唤醒之后要重新轮询,效率相对低 (3).select每次都会清空未发生响应的文件描述符,每次拷贝都需要从用户空间到内核空间,效率低,开销大 4.2.2编程步骤
(1).先构造一张关于文件描述符的表 (2).清空表 FD_ZERO (3).将关心的文件描述符添加到表中 FD_SET (4).调用select函数 (5).判断式哪一个或者式哪些文件描述符产生了事件 FD_ISSET (6).做对应的逻辑处理 4.2.3函数接口
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); 功能: 实现IO的多路复用 参数: nfds:关注的最大的文件描述符+1 readfds:关注的读表 writefds:关注的写表 exceptfds:关注的异常表 timeout:超时的设置 NULL:一直阻塞,直到有文件描述符就绪或出错 时间值为0:仅仅检测文件描述符集的状态,然后立即返回 时间值不为0:在指定时间内,如果没有事件发生,则超时返回0,并清空设置的时间值 struct timeval { long tv_sec; /* 秒 */ long tv_usec; /* 微秒 = 10^-6秒 */ }; 返回值: 准备好的文件描述符的个数 -1 :失败: 0:超时检测时间到并且没有文件描述符准备好 注意: select返回后,关注列表中只存在准备好的文件描述符 操作表: void FD_CLR(int fd, fd_set *set); //清除集合中的fd位 void FD_SET(int fd, fd_set *set);//将fd放入关注列表中 int FD_ISSET(int fd, fd_set *set);//判断fd是否在集合中 是--》1 不是---》0 void FD_ZERO(fd_set *set);//清空关注列表 4.2.4练习
输入鼠标的时候,响应鼠标事件,输入键盘的时候,响应键盘事件 (两路IO)
4.2.5超时检测
(1).概念
什么是网络超时检测呢,比如某些设备的规定,发送请求数据后,如果多长时间后没有收到来自设备的回复,那么需要做出一些特殊的处理 比如: 链接wifi的时候,等了好长时间也没有连接上,此时系统会发送一个消息: 网络连接失败 (2).必要性
(1). 避免进程在没有数据时无限制的阻塞; (2).规定时间未完成语句应有的功能,则会执行相关功能
4.3poll
特点
(1).优化文件描述符的限制,文件描述符的限制取决于系统 (2).poll被唤醒之后要重新轮询一遍,效率相对低 (3).poll不需要重新构造表,采用结构体数组,每次都需要从用户空间拷贝到内核空间 4.4epoll:百万级
特点
(1).监听的最大的文件描述符没有个数限制 (2).异步IO,epoll当有事件产生被唤醒之后,文件描述符主动调用callback函数(回调函数)直接拿到唤醒的文件描述符,不需要轮询,效率高 (3).epoll不需要重新构造文件描述符表,只需要从用户空间拷贝到内核空间一次。 4.5总结
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2335272.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!