文件IO编程
linux
下⼀切皆⽂件,我们操作外设(⿏标、键盘、磁盘等外设)就像操作⽂件⼀样。要如何操作⽂件与外设,就必须熟练掌握⽂件IO(input写、output读)
1.多文件编程
多⽂件编程:把⼀个程序的源代码,根据程序员的想法进⾏归类,把相关的功能函数添加在⼀个⽂件中,⼀个项⽬⼯程程序可能就有多个⽂件构成,这就叫做多⽂件编程
如:
1.c 2.c 3.c
每个⽂件中写⼀部分功能函数,三个⽂件合起来就是⼀个完整的功能程序
编译:
gcc xxx1.c xxx2.c xxx3.c
编译机制:
gcc
进⾏编译时,
存在4个步骤
:前三个步骤是程序中的每个⽂件独⽴执⾏编译
1、预处理步骤:就是把⽤特殊标识符(以警号#开头)表⽰的内容还原
gcc -E xxx.c -o xxx.i
2、编译步骤:把C语⾔的源代码先翻译为汇编代码
gcc -S xxx.i -o xxx.s
3、汇编步骤:把汇编代码翻译为⼆进制代码
gcc -c xxx.s -o xxx.o
4、链接步骤:把刚才编译过程中的每个⽂件链接在⼀起形成⼀个可执⾏程序
gcc xxx1.o xxx2.o xxx3.o -o a.out
由于在编译时,虽然是同时编译多个⽂件,但是编译步骤完成时前三个步骤是每个⽂件⾃⼰完成⾃⼰的步骤操作,最后在链接起来。在源⽂件中使⽤时是交叉使⽤的(1可能⽤2
的函数,
2
可能⽤
3
的函数,
3
可能⽤
1
的函数),单个⽂件单独编译时,在⾃⼰⽂件中找不到对应的函数(因为在其他⽂件中)就会报错,所以需要在⽂件中使⽤了其他⽂件的函数就进⾏声明,进⾏说明,那每个⽂件如果⽤到了其他⽂件的函数都需要进⾏
声明
在
C
中有⼀种⽂件,
专门⽤于书写声明
,叫做头⽂件
xxx.h
。
作⽤就是可以⽤于多⽂件编程中声明编写只写⼀份,让后在多个⽂件中进⾏
include,
在编译时预处理阶段就展开声明
#include “xxx.h”
就表⽰要使⽤这个
xxx.h
中的声明
预处理命令:
在编译过程中,在预处理阶段执⾏的操作
#include
表⽰在预处理阶段就加载对应⽂件中的声明
#if else endif
表⽰在预处理阶段只保留某段代码,另外⼀段代码不保留
#defi ne
宏定义
(
宏替换
)
2.文件IO
IO
:输⼊输出
在程序中如果要操作磁盘上的⽂件,对⽂件就是读写操作
读:程序从磁盘上的⽂件中获取到内容放⼊到程序中----输⼊
写:程序把程序中的数据存放到磁盘上的⽂件中---------输出
Linux系统提供⼀套系统调⽤API,⽂件IO:open、read、write、close、lseek等
(1)文件操作
打开⽂件open
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
打开⽂件:
int open(const char *pathname, int flags);
参数:
参数1:
const char *pathname:字符指针,表⽰的是字符的地址,
字符串的⾸地址,要打开的⽂件路径字符串的地址
参数2:
int flags:整数,打开⽂件的选项
O_RDONLY:只读
O_WRONLY:只写
O_RDWR:读写
O_TRUNC:清空⽂件(在有 写 ⽅式 有效)
O_APPEND:追加⽂件(在有 写 ⽅式 有效),在写⽂件时,在
⽂件末尾位置添加写
O_CREAT:如果⽂件不存在则,创建⽂件,存在则直接打开,
如果要使⽤当前选择,则需要第三个参数:创建⽂件权限
返回值:
失败,返回-1
成功打开⽂件,返回⽂件描述符 >= 0
创建且打开⽂件:
int open(const char *pathname, int flags, mode_t mode);
读取⽂件(输⼊到程序)read
#include <unistd.h>
从打开的⽂件中读取⽂件内容,输⼊到程序中
ssize_t read(int fd, void *buf, size_t count);//从指定的
fd(打开的⽂件中,读取count个字节数据,存放到程序的内存buf地址开始位置)
参数:
参数1:
int fd:⽂件描述符,表⽰打开的⽂件
参数2:
void *buf:指针,表⽰把从⽂件中读取的内容,存放到程序
指定的内存地址中
参数3:
size_t count:整数,表⽰从⽂件中读取多少个字节的数据内容
返回值:
成功:返回读取到的字节数,如果返回值为0表⽰本次读取是从⽂件末尾开始读取,没有内容
失败:-1
写⼊⽂件(输出到⽂件)write
#include <unistd.h>
从程序中把内存数据写⼊到⽂件中,程序输出到⽂件中
ssize_t write(int fd, const void *buf, size_t count);//把buf这个内存地址的中的数据,拿出 count字节数,写⼊到fd⽂件中
参数:
参数1:
int fd:要写⼊哪个⽂件
参数2:
const void *buf:要写⼊的内容在哪个内存地址(把哪个内存地址的内容,写⼊⽂件)
参数3:
size_t count:要写⼊内容的⼤⼩
返回值:
成功:返回写⼊的字节数
失败:返回-1
关闭⽂件close
#include <unistd.h>
//把打开的⽂件关闭
int close(int fd);
参数:
参数1:
int fd:⽂件描述符,表⽰关闭哪个打开的⽂件
返回值:
成功:返回0
失败:返回-1
对于程序⽽⾔:系统默认打开了终端⽂件
终端⽂件打开了三次,分别以不同的⽅式打开,⽂件描述符如下:
0:读打开,只读,可以读取终端⽂件内容(命令⾏输⼊的内容)
1:写打开,只写,可以写⼊到终端⽂件(终端上显⽰)
2:写打开,只写,以更⾼权限写,程序出错时,想⽴即显⽰
对于⽂件操作,有⼀个指向⽂件当前操作的位置的指针,描述当前读写⽂件在哪⾥
设置⽂件偏移位置lseek
#include <sys/types.h>
#include <unistd.h>
重新设置⽂件当前操作位置(修改偏移位置)
off_t lseek(int fd, off_t offset, int whence);//设置打开的fd⽂件的偏移位置
参数:
参数1:
int fd:表⽰打开的⽂件,要设置的⽂件
参数2:
off_t offset:整数,偏移量,表⽰偏移多少个字节
+:正数,向⽂件末尾偏移
-:负数,向⽂件开头偏移
参数3:
int whence:基准点,表⽰从哪个位置开始计算
SEEK_SET:从⽂件开始位置计算偏移
SEEK_CUR:从⽂件当前的操作位置计算偏移
SEEK_END:从⽂件末尾位置开始计算偏移
返回值:
成功:返回从⽂件开始位置到新偏移之后位置⼀共多少个字节
失败:返回-1
位置偏移可以超过当前⽂件⼤⼩,叫做空洞⽂件,中间空洞部分每个字节都会补'\0'
(2)目录文件操作
创建⽬录mkdir
#include <sys/stat.h>
#include <sys/types.h>
在指定⽬录中创建⼀个⽬录⽂件
int mkdir(const char *pathname, mode_t mode);
参数:
参数1:
const char *pathname:指针,字符串⾸地址,要创建的⽬录⽂件的路径
参数2:
mode_t mode:创建的⽬录的权限(读写执⾏)
返回值:
成功:返回0
失败:返回-1
删除⽬录rmdir
#include <unistd.h>
int rmdir(const char *pathname);
参数:
参数1:
const char *pathname:字符串⾸地址,表⽰要删除的⽬录
返回值:
成功:返回0
失败:返回-1
打开⽬录⽂件
#include <sys/types.h>
#include <dirent.h>
去打开对应路径下的⽬录
DIR *opendir(const char *name);
参数:
参数1:
const char *name:字符串⾸地址,表⽰要打开的⽬录⽂件路径
返回值:
DIR:⽬录信息结构体类型
成功:返回⽬录信息结构体的地址(指针),标识打开的⽬录⽂件
失败:返回NULL(空指针)
获取打开⽬录中的⽂件readdir
#include <dirent.h>
获取打开的⽬录中,⼀个⽂件
struct dirent * readdir(DIR *dirp);
参数:
参数1:
DIR *dirp:获取哪个(打开的)⽬录中的⽂件
返回值:
成功:返回获取到的这个⽂件的描述(结构体)的地址NULL:表⽰本次获取已经获取到⽬录的结尾了没有⽂件了(已经获取完)
⽂件描述结构体
struct dirent {
ino_t d_ino;//inode号,⽂件系统中对⽂件的唯⼀编号
off_t d_off;//偏移
unsigned short d_reclen;//⻓度⼤⼩
unsigned char d_type;//⽂件类型
char d_name[256];//⽂件名
};
关闭打开的⽬录⽂件closedir
#include <sys/types.h>
#include <dirent.h>
关闭打开的⽬录
int closedir(DIR *dirp);
参数:
参数1:
DIR *dirp:表⽰要关闭的⽬录⽂件
返回值:
成功:返回0
失败:返回-1
#include <string.h>
⽐较两个字符串
int strcmp(const char *s1, const char *s2);
⽐较s1字符串,和s2字符串是否相等,从第⼀个字符开始⽐较,⼀直⽐较到字符串结束,如果每个字符都相等,则整个字符串相等,返回0
⽐较两个字符串前n个字符是否相等,相等返回0
int strncmp(const char *s1, const char *s2, size_t n);
把src字符串内容追加到dest字符串最后字符位置,让dest字符串添加内容
char *strcat(char *dest, const char *src);
把src字符串内容前n个字符追加到dest字符串最后字符位置,让dest字符串添加内容
char *strncat(char *dest, const char *src, size_t n);
把src字符串内容拷⻉到dest中,覆盖原dest内容,让dest变为src字符串内容
char *strcpy(char *dest, const char *src);
把src字符串前n个字符拷⻉到dest中,覆盖原dest内容,让dest变为src字符串内容
char *strncpy(char *dest, const char *src, size_t n);
获取s地址字符串的⻓度(不计算'\0'),返回值就是⻓度
size_t strlen(const char *s);
3.标准IO
库函数:由计算机语⾔标准委员会审核通过,如
C
标准委员会,只要是使⽤
C语⾔就可以使⽤那⼀套函数(平台⽆差异)
系统调⽤:由系统提供,只能在对应的平台中使⽤,这⼀套
API
函数就叫做系统调⽤
标准
IO
:由
C
语⾔标准委员会设计的⼀套⽂件的操作
API
函数
fopen打开文件
#include <stdio.h>
打开指定的⽂件
FILE *fopen(const char *pathname, const char *mode);
参数:
参数1:
const char *pathname:字符串⾸地址,表⽰要打开的⽂件路径
参数2:
const char *mode:字符串⾸地址,通过通过字符串来表⽰打开⽂件的⽅式
"r":只读⽅式打开(⽂件必须存在)---------O_RDONLY
"r+":读写⽅式打开--------O_RDWR
"w":只写⽅式打开(清空⽂件,当⽂件不存在时创建)----O_WRONLY | O_CREAT | O_TRUNC
"w+":读写⽅式打开(清空⽂件,当⽂件不存在时创建)---O_RDWR | O_CREAT | O_TRUNC
"a":追加写⽅式打开(操作位置在⽂件末尾,当⽂件不存在时创建)---O_WRONLY | O_CREAT | O_APPEND
"a+":读写⽅式打开(写为追加写操作位置在⽂件末尾,当⽂件不存在时创建)-----O_RDWR | O_CREAT |
O_APPEND
如果上述字符串中 包含'b'表⽰打开⼆进制⽂件,否则打开是⽂本⽂件
返回值:
FILE:是⼀个结构体,描述打开的⽂件信息(包括了⽂件描述符)
返回值就是返回FILE这个结构体类型变量的地址成功:返回FILE * 指针,⽂件信息结构体地址(能知
道打开的⽂件)
失败:返回NULL(空指针)
在使⽤标准
IO
打开⽂件时,会添加
缓冲区
关闭⽂件fclsoe
#include <stdio.h>
关闭⽂件,则会把当前打开的⽂件的缓冲区存放到⽂件中
int fclose(FILE *stream);
参数:
参数1:
FILE *stream:关闭打开的哪个⽂件
返回值:
成功:返回0
失败:返回-1(EOF)
写⼊⽂件fwrite
#include <stdio.h>
把数据写⼊到⽂件
size_t fwrite(const void *ptr, size_t size, size_tnmemb,FILE *stream);
参数:
参数1:
const void *ptr:要写⼊⽂件的内容对应地址
参数2:
size_t size:每⼀个数据⼤⼩
参数3:
size_t nmemb:写⼊多少个数据
参数4:
FILE *stream:写⼊的⽂件
返回值:
成功:返回写⼊的数据的个数
读取⽂件fread
#include <stdio.h>
从⽂件中读取数据存放到ptr
size_t fread(void *ptr, size_t size, size_t nmemb, FILE*stream);
参数:
参数1:
void *ptr:从⽂件中读取的数据存放的位置(指针)
参数2:
size_t size:每个数据⼤⼩(字节)
参数3:
size_t nmemb:读取多少个数据
参数4:
FILE *stream:读取的⽂件
返回值:
成功:返回读取的数据个数
0:表⽰读取时没有数据可读(到达⽂件末尾) , 或 读取错误
刷新标准io缓冲区(把缓冲区内容写⼊⽂件):
#include <stdio.h>
主动把缓冲区的内容写⼊⽂件
int fflush(FILE *stream);
判断是否错误或读取到⽂件结束
#include <stdio.h>
测试当前是否是⽂件末尾,如果是⽂件末尾返回⾮0(返回真)
int feof(FILE *stream);
测试当前是否是错误,如果是错误返回⾮0
int ferror(FILE *stream);
缓冲区的类型:
⽆缓冲:没有缓冲区,直接写⼊⽂件
⾏缓冲:当内容包含回⻋换⾏符号
(\n),就会⽴即写⼊,或当缓冲区满,也
会写⼊⽂件
全缓冲:当缓冲区满,才会写⼊⽂件
当系把统运⾏程序时,默认会终端⽂件打开
3
次:
0
(终端读⽂件描述符)
--------stdin
(标准输⼊)
----
⾏缓冲
1
(终端写⽂件描述符)
--------stdout
(标准输出)
----
⾏缓冲
2
(终端写⽂件描述符)
--------stderr
(标准错误输出)
----
⽆缓冲
读取单个字符fgetc
#include <stdio.h>
从⽂件中读取⼀个字符,以返回值形式,返回读取到的字符(int)
int fgetc(FILE *stream);
参数:
参数1:
FILE *stream:从哪个⽂件中读取
返回值:
成功:返回读取到的字符,以int类型(字符对应的ASCII码)表⽰
如果本次是在⽂件末尾位置读取(⽂件结束位置),返回EOF(-1)
如果读取失败,返回EOF需要判断 EOF到底是失败还是读取到⽂件末尾
int getc(FILE *stream); ==== fgetc
int getchar(void); == fgetc(stdin):从终端⽂件读取(输⼊)⼀个字符
写⼊单个字符fputc
#include <stdio.h>
往⽂件中写⼊⼀个字符
int fputc(int c, FILE *stream);
参数:
参数1:
int c:要写⼊的字符的ASCII码
参数2:
FILE *stream:要写⼊的⽂件
返回值:
成功:返回写⼊的字符的ASCII码
失败:返回EOF(-1)
int putc(int c, FILE *stream); 等价于 fputc
int putchar(int c);等价于 ===== fputc(c,stdout),往终端⽂件写⼊⼀个字符
读取⼀个字符串fgets
#include <stdio.h>
从⽂件中读取⼀个字符串
char *fgets(char *s, int size, FILE *stream);从⽂件 stream中读取内容,最多读取size-1个字符,存储到s指针这个地址中。具体读取的字符⼤⼩:⼆选⼀
1、读取到⽂件结束
2、读取到⼀⾏结束(\n)
如果在读取过程中当读取到size-1时,两个都不满⾜,则读取size-1个字符(读取最⼤⼤⼩)
注意:在读取的字符串后,加上'\0'字符,表⽰字符串的结束
返回值:
成功:返回 s 指针
NULL:本次读取在⽂件结束位置读取(已经读取到⽂件末尾)
char *gets(char *s);等价于 == fgets(s,,stdin),从终端上读取⼀个字符串,没有限制⼤⼩(没有size-1)容易越界
写⼊⼀个字符串
#include <stdio.h>
把s中的字符串('\0'为⽌),写⼊到⽂件中
int fputs(const char *s, FILE *stream);
参数:
参数1:
const char *s:要写⼊的字符串,到'\0'为⽌
参数2:
FILE *stream:写⼊的⽂件
返回值:
成功:⾮负整数 >= 0
失败:EOF(-1)
int puts(const char *s);等价于 ==== fputs(s,stdout),往终端上写字符串
多任务编程
1.多进程
程序:是⼀些保存在磁盘上的指令的有序集合,是⼀个⽂件,没有任何执⾏的概念,是⼀种静态的表现
如果⼀个程序进⾏执⾏,那我们就把正在执⾏的内容就叫做进程
进程:是⼀个程序的⼀次执⾏过程,当系统在执⾏某个程序是,分配和释放的各种资源
进程是⼀个独⽴的可调度的任务
linux
下的进程结构
进程:包含
3
个部分
数据段:全局变量、常量以及动态分配的空间(malloc)
正⽂段:程序中的代码
堆栈段:局部变量、函数的形式参数、函数返回值
因为要调度,进程除了有内容以外,还有⼀些信息
(
描述进程的信息
)
-----
进程控制块:程序计数器值、
CPU
的寄存器值,以及存储临时数据的进程堆栈
进程号:
linux
对执⾏的程序
(
进⾏
)
,进⾏编号
(PID)
一些关于进程的命令:
调度进程:
ps
:查看当前执⾏的进程
ps axu
ps axj
top
:动态显⽰系统中的进程
kill
:向进程发送信号
kill pid
默认发送结束信号
bg
:将挂起的进程在后台执⾏
bg +
后台编号
(
进程
)
fg
:把后台的进程放在前台执⾏
fg +
后台编号
(
进程
)
进程的运⾏状态:
1. 运⾏态(就绪态)
:此时进程或正在运⾏,或准备运⾏
(
只要获取
cpu
时间就可以运⾏)
2.
等待态:此时进程在等待⼀个事件的产⽣或某种系统资源
1.
可中断
2.
不可中断
3.
停⽌态:当前进程被中⽌
4. 僵⼫态(死亡态)
:这是⼀个已经被终⽌
(
结束
)
的进程,但是进程的资源还
没被完全释放掉
Linux
进程的创建
(
程序的执⾏
)
,是由另⼀个进程来帮助创建,来执⾏当前这个程序,来完成创建的进程就叫做⽗进程,被创建的进程就是⼦进程是由⽗进程去创建⼀个⼦进程,然后才能调度⼦进程
进程创建fork
#include <sys/types.h>
#include <unistd.h>
通过当前执⾏的进程(程序),去创建⼀个新的进程,被创建的进程叫做⼦进程,执⾏创建的这个进程叫做⽗进程
当调⽤fork时,会把⽗进程所有资源(代码,变量,打开的⽂件等),复制⼀份给⼦进程(⼦进程的内存空间中内容是和⽗进程完全⼀致),⼦进程执⾏⾃⼰的内存空间内容,⼦进程拷⻉⽗进程的所有内容,然后从
fork的返回值开始执⾏
pid_t fork(void);
返回值:
成功:在⽗进程中fork的返回值为⼦进程的pid;在⼦进程中fork的返回值为0
失败:⽗进程返回-1,没有⼦进程
结束进程exit
#include <stdlib.h>
结束当前执⾏的进程,有使⽤标准io,在结束时就会刷新缓冲区(会写⼊⽂件)
void exit(int status);
参数:
参数1:
int status:整数,& 0xff,即 只保留低8位⾼位全部清0,作为进程的结束状态,返回给⽗进程
#include <unistd.h>
结束当前执⾏的进程,但是不会刷新缓冲区
void _exit(int status);
在C程序,程序只会执⾏main函数中的内容,在main函数中:return语句---结束当前函数(跳出当前函数),结束main函数,结束当前进程
#include <sys/types.h>
#include <sys/wait.h>
使⽤该函数,使进程阻塞(等待),直到任意⼀个⼦进程结束,才继续执⾏
pid_t wait(int *wstatus);
参数:
参数1:
int *wstatus:指针,变量地址,⽤于存储⼦进程结束状态值的地址
返回值:
成功:返回阻塞等待到的结束的⼦进程的pid
失败:返回-1
waitpid功能和wait函数类似,但是waitpid是可以等待指定⼦进程结束
pid_t waitpid(pid_t pid, int *wstatus, int options);
参数:
参数1:
pid_t pid:进程号
pid > 0:只等待进程id等于pid的⼦进程
pid == -1:等待任何⼀个⼦进程结束,作⽤和wait⼀样
pid < -1:等待其进程组等于pid的绝对值的任意⼦进程
pid == 0:等待其进程组id等于调⽤进程的组id的任意⼦进程
参数2:
int *wstatus:同wait,等待接收⼦进程的状态值
参数3:
int options:选项
0:同wait,表⽰阻塞,⼀直等待⼦进程结束
WNOHANG:表⽰不会阻塞等待。若,对应pid的⼦进程现在没有结束,则不等待⼦进程结
束,当前函数直接返回,若,调⽤时对应pid的⼦进程已经结束,则回收得到
⼦进程状态,当前函数直接返回
返回值:
成功:返回阻塞等待到的结束的⼦进程的pid
失败:返回-1
linux
守护进程
(
精灵进程
)
守护进程:是
Linux
中的后台服务进程,它是⼀个⽣存期较⻓的进程,通常 独⽴与终端并周期性的执⾏某种任何或等待处理某些发⽣的事件-----daemon 进程
守护进程常常在系统运⾏启动时开始运⾏,在系统关闭时终⽌
Linux
系统中有很多守护进程,⼤多数服务都是守护进程实现的
实现守护进程:
在
Linux
中,每⼀个系统与⽤户进⾏交流的界⾯叫做终端,从这个终端开始运⾏的进程都会依附于这个终端,这个终端称为这些进程的控制终端,当控制终端被关闭时,相应的进程都会被⾃动关闭。实现守护进程要突破这个限制,它从开始运⾏,直到整个系统关闭才会退 出。如果想让某个进程不会因为⽤户或终端的改变⽽受到影响,就必须把这 个进程变为守护进程
守护进程:
1. 创建⼦进程,⽗进程退出
⼦进程在形式上脱离了终端,由于⽗进程先退出,⼦进程变为孤⼉进程
(重新认⽗进程
)
p = fork()
if(p >0)
exit(0);
2. 在⼦进程中创建新会话
让⼦进程不受其他控制,由⾃⼰控制
(
会话组组⻓
) 进程组:⼀个或多个进程的集合,进程组由进程组ID
来标识。每个进程 组都有⼀个组⻓进程,进程组ID
就是组⻓进程的进程号
会话:⼀个或多个进程组的集合
setsid()//
在不是进程组组⻓的基础上,创建⼀个新会话,同时当前进程就是新会话组的组⻓
#
include
<sys/types.h>
#
include
<unistd.h>
为当前进程设置新会话
pid_t
setsid
(
void
);
3. 设置守护进程的⼯作⽬录
⽗进程执⾏时,有⼀个⼯作路径,那在创建⼦进程时,也会使⽤
⽗进程的⼯作路径,⼦进程⾃⼰设置对应的⼯作路径
#include <unistd.h>
改变当前进程的⼯作路径
int chdir(const char *path);
参数:
参数1:
const char *path:新的⼯作路径
4. 重设⽂件权限掩码
⽂件权限掩码是指⽂件权限中被屏蔽掉的对应位,改变了设置的⽂件权限,通常把⽂件权限掩码设置为0,
可以增加该守护进程的灵活
#
include
<sys/types.h>
#
include
<sys/stat.h>
设置当前进程执⾏时,⽂件的⽂件权限掩码
mode_t
umask
(
mode_t
mask)
5. 关闭已经打开的⽂件描述符
新建的⼦进程会从⽗进程那⾥继承所有已经打开的⽂件在创建新的会话后(⼦进程),守护进程已经脱离了任何控制终端,应当关闭⽤不到的⽂件
#include <unistd.h>
获取打开的⽂件个数,打开的最⼤的⽂件描述符就是个数
-1
int getdtablesize(void);
进程执⾏的命令⾏参数:
在执⾏程序时,除了有要执⾏的程序,还可以加上参数
参数是添加到执⾏的程序中
------>
执⾏
main
函数
main
函数有参数列表,⽤于在执⾏程序时接收参数
int main(int argc,char * argv[])
argc:表⽰执⾏的命令⾏有多少个参数(包括程序名)
argv:指针数组,数组的每⼀个元素是⼀个指针(char *),数组每⼀个
元素就是⼀个字符串的⾸地址,字符串就是命令⾏上的参数
exec函数族:
#include <unistd.h>
exec函数族,就是⽤⼀个新的程序⽂件来替换当前进程执⾏的代码⽂件,变为执⾏新的程序内容把当前进程执⾏内容替换为新内容(程序⽂件),把参数列举出来
int execl(const char *pathname, const char *arg, .../* (char *) NULL */);
参数:
参数1:
const char *pathname:程序的路径
参数2:
const char *arg, ...:对于应⽤程序⽽⾔,要执⾏时,都可能需要命令⾏参数,即:程序
名 参数1 参数2....
表⽰要执⾏的新的程序的命令⾏参数argv0,argv1,
argv2...NULL(NULL表⽰参数结束)
返回值:
正确,会替换成其他的程序⽂件内容,没有返回
失败:返回-1
int execlp(const char *file, const char *arg, .../* (char *) NULL */);作⽤与execl完
全⼀致,execlp不⽤写路径,只⽤写程序名,会从指定的路径下寻找程序
把当前进程执⾏内容替换为新程序内容,使⽤指针数组存储参数
int execv(const char *pathname, char * argv[]);
参数:
参数1:
const char *pathname:程序路径
参数2:
char *const argv[]:指针数组,每个元素是指针,字符串⾸地址(命令⾏参数)
int execvp(const char *file, char *const argv[]);作⽤与execv完全⼀致,execvp不⽤写路径,只⽤写程序名,会从指定的路径下寻找程序
2.多线程
每个进程都是独⽴的私有的空间,是⼀个独⽴的任务,虽然可能多个进程相互之间需要协同⼯作,还是需要⾃⼰的进程切换,因此在进程的上下⽂切换时,系统开销⽐较⼤为了提供系统的性能,在操作系统中引⼊了轻量级的进程,也叫做线程线程提⾼系统性能的⽅式就是,让多个轻量级进程(线程
)
共享⼀些进程资源,在切换时只⽤切换⾃⼰私有的资源即可在同⼀个进程中创建的多个线程共享进程的地址空间。线程和进程⼀样都是统⼀参与调度执⾏
通常 线程就是指共享相同的地址空间的多个任务,线程依赖于进程
线程基础:
⼀个进程中的多个线程共享资源:
1.
可执⾏的指令
2.
静态数据
3.
进程中打开的⽂件
4.
当前⼯作的⽬录
5.
⽤户
id
6.
⽤户组
id
7.
pid
进程号
每个线程私有的资源:
8.
pc(
程序计数器
)
和相关寄存器
9.
线程
id(tid)
10.
堆栈:局部变量、函数参数、返回值地址
11.
错误码
12.
执⾏状态和属性
NEW POSIX THREAD LIBRARY
(
NPTL
)提供的基本操作
创建线程
删除线程
控制线程
创建线程pthread_create
#include <pthread.h>
在进程中创建线程执⾏
int pthread_create(pthread_t *thread, const pthread_attr_t*attr, void *(*start_routine) (void *),void *arg);
{
*thread = tid;
}
参数:
参数1:
pthread_t *thread:指针,地址,(存储线程id)创建线程后会把线程id存储到这个地址
中
参数2:
const pthread_attr_t *attr:要执⾏的线程的属性,传递的是属性变量值的地址,通常
写 NULL 表⽰使⽤默认属性创建线程
参数3:
void *(*start_routine) (void *):函数指针,函数地址,线程执⾏的起始函数,线程
从哪个函数开始执⾏
参数4:
void *arg:提供给参数3这个函数的参数
返回值:
成功:返回0
失败:返回错误码
关闭当前线程pthread_exit
#include <pthread.h>
结束当前线程,同时把线程结束状态,返回给创建当前线程的进程或线程中
void pthread_exit(void *retval);
参数:
参数1:
void *retval:指针,地址,作为线程的结束状态,返回给创建的线程中
等待线程结束
#include <pthread.h>
等待线程结束,且接收线程的结束状态
int pthread_join(pthread_t thread, void **retval);
参数:
参数1:
pthread_t thread:要等待结束的线程id
参数2:
void **retval:⼆级指针,⼀级指针的地址,把线程结束状态(⼀级指针),存储到这
个地址中
返回值:
成功:返回0
失败:返回错误码
取消线程
#include <pthread.h>
取消线程执⾏,结束指定线程
int pthread_cancel(pthread_t thread);
线程间的同步互斥
对于线程⽽⾔,由于是使⽤的同⼀个进程的地址空间,线程间通信是容易的,通过全局变量实现数据的共享和交换
同步:指多个任务按照⼀定的顺序相互配合完成意⻅⼯作,通过信号量实现
信号量:代表着⼀类资源,其值就是这种资源的数量,是⼀个⼤于等于零
(
⾮负整数)
通过设计⽣产者消费者模型,线程执⾏先⽣产然后再消费,⼀个线程做⽣产,另⼀个线程做消费,存在先后关系
⽣产者消费者模型-----pv操作:
p操作:消费(申请资源)
if(信号量值⼤于0)
{
申请资源,任务继续执⾏
信号量值--(表⽰资源减少)
}
else
{
申请资源,任务阻塞等待
}
v操作:⽣产(释放资源)
if(没有等待资源的任务)
{
信号量++
}
else
{
信号量++
唤醒等待的任务
}
信号量是⼀个受保护的变量值,只允许三个操作:
初始化
#include <semaphore.h>
初始化信号量(设置资源值数⽬)
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
参数1:
sem_t *sem:地址,信号量的地址,把初始化的值存⼊这个变量地址中
参数2:
int pshared:信号量使⽤的范围
0:表⽰在线程间使⽤
⾮0:表⽰在进程间使⽤
参数3:
unsigned int value:信号量初始化的值
返回值:
成功:返回0
失败:返回-1
互斥:每个资源在任意时刻最多只能有⼀个线程进⾏访问,通过互斥锁实现
互斥锁:就是每个线程在⾃⼰线程中访问资源,当能够获取到锁
(
加锁
)
时就访问,访问完就释放锁(
解锁
)
。当线程⽆法获得锁时,就阻塞等待直到获得锁为⽌
互斥锁:保护共享资源,保护数据的完整性
每个线程在访问同⼀个资源时,都使⽤互斥锁机制,就可以达到互斥⽬的
初始化互斥锁:
#include <pthread.h>
初始化互斥锁,设置互斥锁变量
int pthread_mutex_init(pthread_mutex_t * mutex,const
pthread_mutexattr_t * attr);
参数:
参数1:
pthread_mutex_t * mutex:互斥锁变量地址,要初始化的互斥锁变量
参数2:
pthread_mutexattr_t * attr:互斥锁的属性,NULL表⽰默认属性
返回值:
成功:返回0
失败:返回-1
获取互斥锁然后才访问数据(加锁):
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
释放互斥锁(解锁):
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
进程间通信
1.
早期通信⽅式
1.
管道
2.
信号
2.
system V IPC
对象
1.
共享内存
2.
消息队列
3.
信号灯
(
信号量
)
1.共享内存
为了多个进程间能够进⾏通信(交换数据),内核专门留出了⼀块内存空间,可以由访问的进程将其映射到进程中-----
共享内存共享内存的使⽤步骤:
1. 创建/打开共享内存
#include <sys/ipc.h>
#include <sys/shm.h>
//创建或打开指定key的共享内存,如果内核已经存储key的共享内存则打开,不存在则创建
int shmget(key_t key, size_t size, int shmflg);
参数:
参数1:
key_t key:要创建或要打开的内容内存编号
参数2:
size_t size:要创建的共享内存⼤⼩(字节)
参数3:
int shmflg:选项
0666:权限
IPC_CREAT:不存在就创建
返回值:
成功:返回共享内存id
失败:返回-1
2. 映射共享内存,即把共享内存的地址映射到进程中,可以进⾏使⽤
#include <sys/types.h>
#include <sys/shm.h>
映射共享内存
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
参数1:
int shmid:共享内存id,表⽰要进⾏映射的共享内存
参数2:
const void *shmaddr:指针,地址,表⽰共享内存要映射到进程中的哪个地址位置
NULL:由系统随机映射⼀个地址
参数3:
int shmflg:操作共享内存的⽅式
SHM_RDONLY:只读
0:读写
返回值:
成功:返回映射的地址,操作这个地址对应的空间就是操作共享内存
失败:返回-1
3. 使⽤共享内存进⾏数据交换(通信)
4. 解除(撤消)共享内存映射
#include <sys/types.h>
#include <sys/shm.h>
解除映射
int shmdt(const void *shmaddr);
参数:
参数1:
const void *shmaddr:要解除映射的内存地址
返回值:
成功:返回0
失败:返回-1
5. 删除共享内存对象
#include <sys/ipc.h>
#include <sys/shm.h>
控制共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
参数1:
int shmid:要操作的共享内存id
参数2:
int cmd:控制⽅式
IPC_STAT:查看获取shmid共享内存的信息
IPC_SET:设置shmid共享内存的信息
IPC_RMID:删除共享内存,第三个参数为NULL
参数3:
struct shmid_ds *buf:结构体指针
如果是IPC_STAT,表⽰把shmid信息存储到这个地址中
如果是IPC_SET,表⽰把这个地址中的(结构体)信息作为shmid的信息进⾏设置
2.消息队列
消息队列就是⼀种消息的列表,进程间可以通过消息队列,往消息队列中添加消息,读取消息、
1. 创建/打开消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
创建或打开消息队列
int msgget(key_t key, int msgflg);
参数:
参数1:
key_t key:key编号,消息队列的编号
参数2:
int msgflg:选项
返回值:
成功:返回消息队列的id
失败:返回-1
2. 读取/添加消息
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
往消息队列中添加消息
int msgsnd(int msqid, const void *msgp, size_t msgsz, intmsgflg);
参数:
参数1:
int msqid:消息队列id,操作的消息队列是谁
参数2:
const void *msgp:整个消息数据的⾸地址包含:类型和正⽂数据
参数3:
size_t msgsz:消息正⽂⼤⼩
参数4:
int msgflg:选项
0:阻塞等待,直到发送完(往消息队列中添加消息完)这个函数才结束
IPC_NOWAIT:消息还没发送完成(往消息队列中添加消息还未完成)就结束这个函数
返回值:
成功:返回0
失败:返回-1
从消息队列中获取消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, longmsgtyp,int msgflg);
参数:
参数1:
int msqid:要获取的消息队列id
参数2:
void *msgp:获取的消息存放地址
参数3:
size_t msgsz:消息的正⽂⼤⼩
参数4:
long msgtyp:要获取的消息的类型
0:任意消息类型都可以获取
>0:从消息队列中获取第⼀个为对应类型的消息
参数5:
int msgflg:选项
0:阻塞等待,直到接收完(从消息队列中获取消息完)这个函数才结束
IPC_NOWAIT:消息还没接收完成(从消息队列中获取消息还未完成)就结束这个函数
返回值:
成功:返回获取的正⽂⼤⼩
失败:返回-1
3. 删除消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
控制消息队列
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数:
参数1:
int msqid:消息队列id
参数2:
int cmd:控制⽅式
IPC_STAT:获取消息队列信息
IPC_SET:设置消息队列信息
IPC_RMID:删除消息队列
参数3:
struct msqid_ds *buf:结构体地址,⽤于获取消息队列信息(把信息存储到这个地址);
⽤于设置消息队列信息(把地址对应空间信息设置到消息队列)
3.信号灯
在进程间使⽤⼀个特定的信号量值,达到进程间的同步互斥信号量的操作
1. 创建信号量
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
根据key值,创建/打开信号灯
int semget(key_t key, int nsems, int semflg);
参数:
参数1:
key_t key:key编号
参数2:
int nsems:信号灯集中信号量的数⽬
参数3:
int semflg:选项,权限
IPC_CREAT:创建
0666:权限
返回值:
成功:返回semid
失败:返回-1
2. 信号量的操作
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
信号灯的操作
int semop(int semid, struct sembuf *sops, size_t nsops);
参数:
参数1:
int semid:要操作的信号灯
参数2:
struct sembuf *sops:具体的操作⽅式
struct sembuf sops
{
unsigned short sem_num;//要操作信号量的编号
short sem_op;//操作
0:等待信号量的值变为0
-1:p操作,消费,把信号量值-1
1:v操作,⽣产,把信号量值+1
short sem_flg;//选项
0:阻塞等待
IPC_NOWAIT:⾮阻塞,不等待
}
参数3:
size_t nsops:要操作的信号灯的个数
3. 信号量的控制
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
控制信号量
int semctl(int semid, int semnum, int cmd, ...);
参数:
参数1:
int semid:控制的信号量id
参数2:
int semnum:要控制的是信号灯集中的哪个信号量编号
参数3:
int cmd:控制⽅式
IPC_RMID:删除信号灯集,同时第⼆个和第四个参数不起作⽤
GETVAL:获取指定编号的信号量的值
SETVAL:设置指定编号的信号量的值
参数4:
根据参数3决定
返回值:
成功,根据不同的cmd返回不同的内容
失败:返回-1
4.管道
在内核的内存空间中,设计了⼀个通道,这个通道可以让进程使⽤
(
进程可以读写)
,是⼀种半双⼯⽅式,使⽤⽂件的操作⽅式进⾏通信(read、
write
)
有名管道
(fi fo)
管道有⼀个名字,要通信的进程通过这个名字找到管道⽂件
创建管道⽂件
:在⽂件系统中创建⼀个管道⽂件,但是使⽤的时候在内存中使⽤这个管道⽂件
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
参数:
参数1:
const char *pathname:创建的管道⽂件的路径
参数2:
mode_t mode:创建管道⽂件的权限
返回值:
成功:返回0
失败:返回-1
⽆名管道
(pipo)
没有名字,只能存在于内存中
创建⽆名管道
#include <unistd.h>
创建打开⽆名管道,同时获取到打开的⽆名管道的⽂件描述符
int pipe(int pipefd[2]);
参数:
参数1:
int pipefd[2]:存储⽂件描述符
fd[0]----read打开的⽂件描述符
fd[1]----write打开的⽂件描述符
5.信号
控制进程的执⾏,是⼀种异步通知
信号是直接⽤于进程和内核之间进⾏交互,内核通过信号通知进程系统产⽣
了什么事情或变化
进程
1
和进程
2
要通过信号进⾏通信,依靠内核信号来实现
信号:
kill -l
查询
信号的发送与捕捉
信号的发送:
#include <sys/types.h>
#include <signal.h>
给指定的进程发送指定的信号
int kill(pid_t pid, int sig);
参数:
参数1:
pid_t pid:发送给指定pid的进程
参数2:
int sig:发送的信号
返回值:
成功:返回0
失败:返回-1
信号的捕获处理:
对于信号⽽⾔,具体的信号需要和操作关联
⼀个进程可以设定对信号的响应⽅式
进程对信号的响应⽅式有
三种:
忽略:对信号不做任何处理
缺省
(
默认
)
:对信号做默认操作
捕获:定义信号处理函数,当产⽣信号后,使⽤对应的函数进⾏处理
默认:
19) SIGSTOP 暂停
20) SIGTSTP 暂停
17) SIGCHLD 忽略
#include <signal.h>
typedef void (*sighandler_t)(int);
//类型替换: sighandler_t 表⽰⼀个函数指针设定信号的响应功能是什么设定进程接捕获到信号时如何处理信号
sighandler_t signal(int signum, sighandler_t handler);
这个函数不会阻塞等待信号产⽣,调⽤该函数后,就设定信号的处理⽅式,只要进程捕获到对应信号就按照设定的⽅式进⾏处理
参数:
参数1:
int signum:对进程中要捕获的信号
参数2:
sighandler_t handler:函数指针,
函数指针:函数地址,如果接收对应的捕获信号,采⽤这个函数指针的对应函数进⾏处理
SIG_IGN:忽略该信号(信号不能是 9) SIGKILL 19) SIGSTOP 这两个信号不能忽略)
SIG_DFL:使⽤默认的⽅式进⾏处理(当接收到信号后)
发送定时信号alarm
#include <unistd.h>
在指定的时间后,给当前进程发送SIGALRM信号
unsigned int alarm(unsigned int seconds);
阻塞等待信号接收pause
#include <unistd.h>
int pause(void);
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)