1.基本文件I/O
文件操作相关的最基本元素:目录结构、索引节点、文件数据本身
[1]open()
#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);
pathname:文件路径;flags:打开标志;mode:文件权限(创建文件时,需要设置)
- 以只读(
O_RDONLY
)方式打开文件,文件不存在打开失败,存在则打开成功,文件指针指向开始位置; - 以只写(
O_WRONLY
)方式打开文件,文件不存在打开失败,存在则打开成功,文件指针指向开始位置,默认不清空源文件内容; - 一般以写方式打开文件,包含创建、只写、清空这三个宏,也就是
O_CREAT|O_RDONLY|O_TRUNC
,并且函数第三个参数为设置权限; - 默认新建文件权限为:mode&(~umask) 怕麻烦可以直接在创建前,将权限掩码改为0---->
umask(0);
[2]write()
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
fd :文件标识符 buf:写入数据起始地址 count:数据长度(字节)
返回值:写入字节数 -1:出错
[3]read()
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
fd :文件标识符 buf:读取存储起始地址 count:读取数据长度(字节)
返回值:表示读取字节数 0:到达文件末尾
[4]access()
#include <unistd.h>
int access(const char *pathname, int mode);
mode:F_OK、R_OK……
返回值:文件权限存在则返回0,不存在返回-1;
[5]带参main函数
int main(int argc,char *argv[])
argc:传入参数个数 argv:字符串数组,保存每个字符串起始地址
命令行执行程序时,后面跟上传输的参数!!!!!argv[1]:为传入的第一个参数
argv[0]:是执行程序的那个命令字符串,例如:./a.out
1>实现与Linux系统下cp指令相同的功能(拷贝文件)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char *argv[]){
umask(0);
int fd1 = open(argv[1],O_RDONLY);
if(fd1 < 0){
perror("open");
return 0;
}
int fd2 = open(argv[2],O_CREAT|O_WRONLY|O_TRUNC,0777);
if(fd2 < 0){
perror("open");
return 0;
}
char c;
int i = 0;
char buff[] = "\\/\\/";
while(read(fd1,&c,sizeof(char))){
if(write(fd2,&c,sizeof(char)) > 0){
printf("[%c]拷贝中\r",buff[i++%4]);
continue;
}
puts("拷贝失败!");
close(fd1);
close(fd2);
return 0;
}
puts("拷贝成功!");
close(fd1);
close(fd2);
return 0;
}
2.进程
概念:进程是一个有独立功能的运行处于运行状态的程序,每个进程可以并发执行。
特点:每个进程之间相互独立,一个进程崩溃了,不会影响其他进程的运行;进程之间可以通过内核机制相互通讯。
查看所有进程信息:ps -aux
ps -ef
查看进程树:pstree -p
查看动态进程:top
进程目录:/proc中对应进程号文件夹中
[1]fork()
功能:创建子进程
- 在父进程中,fork()函数返回值为子进程的进程号pid;
- 在子进程中fork()返回值为0:表示子进程创建成功;-1:创建失败;
- 子进程会将父进程的内存四区全部copy(包括在缓冲区的内容哦),但只执行fork()函数之后的语句。
- 所以我们可以通过,fork()返回值的不同控制父子进程执行不同的程序。
[2]exit()
功能:结束进程
exit(0):表示正常运行结束进程
[3]wait()
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
功能:回收子进程资源或僵尸进程(当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源)
注意事项:
-
如果没有子进程,wait是一个非阻塞函数
-
如果有子进程,wait 是一个阻塞函数, 如果有子进程退出,wait 立马解除阻塞,回收子进程的资源
-
返回值的返回 0-255
WIFEXITED(wstatus):如果子进程是调用exit 正常退出,该宏为真
WEXITSTATUS(wstatus): 如果上面的宏为真,调用该宏过滤退出返回值
WIFSIGNALED(wstatus):如果子进程是由于信号退出的,该宏为真
WTERMSIG(wstatus):如果上面的宏为真,可以调用该宏过滤信号值;例如:子进程是被kill -9结束的,过滤出的信号就是9
[4]system()和exec()
- system(const char *command):
- 串行执行新的程序,执行完继续执行剩余原程序;
- 参数为字符串起始地址或者字符串常量
- 字符串内容可以是系统命令例如:touch data.txt
- exec():
- 执行其他程序,会影响当前程序,所以要手动开一个子进程给它用,才能不影响当前进程的程序;
- 清空当前程序所有的数据,将要执行的程序数据加载过来, 一旦调用成功,就回不到之前的程序;
几种调用形式:
-
int execl(const char *path, const char *arg, …);
int execlp(const char *file, const char *arg, …);
- 上面两种的第一个参数为:执行的命令程序文件地址,如:
"/bin/ls"
- 后面的参数为每个命令,但最后一个参数要为NULL,如:
execl("/bin/ls","ls","-l",NULL);
- 上面两种的第一个参数为:执行的命令程序文件地址,如:
-
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
-
这两个相比较前两个,不同的就是,将后面的所有参数用一个字符串指针数组保存;
如:
char *argv[] = {"ls","-l",NULL};
execv("/bin/ls",argv);
-
3.线程
线程创建
[1]pthread_create()
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
参数说明:
- pthread : 传出新建线程的标识符
- attr: 设置线程属性
- start_routine: 线程入口函数
- arg: 给新建线程传递参数
- 返回值:成功返回0,失败返回错误编号
PS: 编译时要手动连接到线程库(gcc XX.c -o xx -lpthread)
pthread = pth;
if(0 != pthread_create(&pth,NULL,pthread_task,NULL)){
perror("pthread_create");
return 0;
}
[2]pthread_exit()
功能:线程退出
- 在线程入口函数,return和pthread_exit()效果一样,只要在线程函数中调用了pthread_exit(),该线程就结束了
- 线程中谨慎使用exit(),这会使整个进程结束
[3]pthread_join()
功能:阻塞,等待回收线程资源
线程同步
[1]互斥锁
是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将不会循环等待,不占用cpu,cpu可以去执行其他线程,cpu利用率高
-
定义互斥锁:
pthread_mutex_t mutex;
-
初始化互斥锁:
int pthread_mutex_init(pthread_mutex_ t *restrict mutex,const pthread_mutexattr_t *restrict attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;ps : 第1 、2步在创建线程之前完成!
-
加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
-
解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
-
销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
[2]自旋锁
当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。就是说,循环等待时会独占cpu,一旦可以加锁了,就执行线程。
- int pthread_spin_destroy(pthread_spinlock_t *);
- int pthread_spin_init(pthread_spinlock_t *, int);
- int pthread_spin_lock(pthread_spinlock_t *);
- int pthread_spin_trylock(pthread_spinlock_t *);
- int pthread_spin_unlock(pthread_spinlock_t *)
[3]读写锁
与互斥量类似,但读写锁允许更高的并行性。
其特性为:写独占,读共享。
- 写与写互斥
- 写与读互斥
- 读与读共享
读写锁状态:一把读写锁具备三种状态:
- 读模式下加锁状态 (读锁)
- 写模式下加锁状态 (写锁)
- 不加锁状态读写锁特性:
- 读写锁是“写模式加锁”时, 解锁前,所有对该锁加锁的线程都会被阻塞。
- 读写锁是“读模式加锁”时, 如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞。
- 读写锁是“读模式加锁”时, 既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。
thread_rwlock_init()
pthread_rwlock_destroy()
pthread_rwlock_rdlock()
pthread_rwlock_wrlock()
pthread_rwlock_unlock()
pthread_rwlock_tryrdlock()
pthread_rwlock_trywrlock()
[4]条件变量
-
定义条件变量,和互斥锁,并初始化
int pthread_cond_destroy(pthread_cond_t *cond); //销毁锁
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//定义条件变量+初始化 -
生产者
-
加锁
-
操作资源
-
解锁
-
发送信号通知阻塞在条件变量上的消费者
int pthread_cond_broadcast(pthread_cond_t *cond);
-
-
消费者
-
加锁
-
while判断是否有资源
- 没有,则调用wait阻塞在条件变量上等待被唤醒
- 有,直接消费
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
-
解锁
-
4.进程间的通信
1信号
[1]安装信号处理函数
#include <signal.h>
typedef void (*sighandler_t)(int); //
sighandler_t signal(int signum, sighandler_t handler);
signnum:信号值
SIG_IGN
:忽略信号SIG_DFL
:执行默认操作
handler : 函数入口地址
2定时器
[1] alarm()定时器
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
功能:设置闹钟(定时器),只能发送一次信号
[2]setitimer()安装定时器
int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);
函数功能: 设置定时器,可以自定义每隔几秒发送信号
参数说明:
-
which : 时间类型
ITIMER_REAL
-
new_value: 设置定时器时间
-
old_value: 返回之前的时间
*struct itimerval {
struct timeval it_interval; / 间隔时间 /
struct timeval it_value; / 开始时间/
};struct timeval {
time_t tv_sec; / seconds /
suseconds_t tv_usec; / microseconds /
};*
3管道pipe
[1]创建无名管道(有血缘关系的进程间)
#include <unistd.h>
int pipe(int piped[2]);
参数说明:传出参数(读写标识符)
pipefd[0]
:读标识符pipefd[1]
:写标识符
- 管道两端都是正常的, 如果管道里没有数据, read函数是阻塞的,
如果写端断开或者出现异常, read函数解除阻塞,返回值为0 - 如果读端异常, 写端继续往管道中写数据,内核会发送SIGPIPE信号,
该信号的默认操作是程序退出, 所以写端一定要捕捉 SIGPIPE信号
[2]数据封包和解包
用结构体打包包头信息,字符数组data长度为0,表示数据的起始地址也就是包头和数据的分界线
struct pack{
unsigned char ver; //版本号
unsigned char type; //类型
unsigned int len; //长度
char data[0];};
-
数据封包
-
申请空间
p = (struct pack*)malloc(sizeof(struct pack)+数据长度);
assert(p!= NULL); -
填充数据
p->ver = 0;
p->type = 0;
p->len = 数据长度;
strcpy(p->data, buff); //根据包头中数据起始地址,将数据放到那块内存中 -
写入打包好的数据
ret = write(fd[1], p, sizeof(struct pack)+数据长度);
-
-
数据解包
-
先读包头
struct pack pk;
ret = read(fd[0], &pk, sizeof(struct pack));if(ret<0)
{
perror(“read”);
break;
}if(0 == ret)// 管道断裂
{
printf(“pipe broken…\n”);
break;
} -
根据包头信息中的长度读取数据
ret = read(fd[0], buff, pk.len);
if(ret == 0)
{
printf(“pipe borken…\n”);
break;
}
-
[3]有名管道FIFO
#include <sys/types.h>
#include <sys/stat.h>int mkfifo(const char *pathname, mode_t mode);
-
创建管道
int ret = mkfifo(“./pipe”,0777);
if(ret < 0){
if(errno != 17){
printf(“errno = %d\n”, errno);
perror(“mkfifo”);
return -1;
}
} -
打开管道文件(读或者写)
int fd = open(“./pipe”,O_RDONLY);
if(fd < 0){
perror(“open”);
return -1;
}
4文件锁
多个进程间通过磁盘文件的读写进行通信
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, … /* arg / );
struct flock {
…
short l_type; /* Type of lock: F_RDLCK, F_WRLCK, F_UNLCK /
short l_whence; / How to interpret l_start: SEEK_SET, SEEK_CUR, SEEK_END /
off_t l_start; / Starting offset for lock /
off_t l_len; / Number of bytes to lock /
pid_t l_pid; / PID of process blocking our lock(set by F_GETLK and F_OFD_GETLK) */
…
};
-
函数功能:设置文件锁
-
参数说明:
- fd :文件描述符
- cmd :命令
F_SETLKW
:设置文件锁
-
写进程文件锁
int fd = open(“./fcntl.txt”,O_CREAT|O_WRONLY|O_TRUNC,0777);
if(fd < 0){
perror(“open”);
return -1;
}struct flock fk;
fk.l_type = F_WRLCK;
fk.l_whence = SEEK_SET;
fk.l_start = 0;
fk.l_len = 100;
fcntl(fd,F_SETLKW,&fk);
write(fd,“hello”,5);
fk.l_type = F_ULOCK;
sleep(10);
fcntl(fd,F_SETLKW,&fk);
close(fd); -
读进程文件锁
int fd = open(“./fcntl.txt”,O_RDONLY);
if(fd < 0){
perror(“open”);
return -1;
}struct flock fk;
fk.l_type = F_RDLCK;
fk.l_whence = SEEK_SET;
fk.l_start = 0;
fk.l_len = 100;
char buff[1024];
fcntl(fd,F_SETLKW,&fk);
int ret = read(fd,buff,100);
buff[ret] = ‘\0’;
printf(“buff:%s\n”,buff);
fk.l_type = F_ULOCK;
fcntl(fd,F_SETLKW,&fk);close(fd);
5消息队列
查看内核中消息队列:ipcs
-
创建消息队列
int msgget(key_t key, int msgflg);
-
函数功能: 创建 或者 获取消息队列的标识符
-
参数说明:
- key:键值, 一个键值和一个消息队列是一一对应的关系
- msgflg:设置标志, 一般使用 IPC_CREAT|0777
- 返回值:
- 成功返回消息队列的标识符
- 失败返回-1
-
-
往消息队列发送消息
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
-
函数功能: 往消息队列中发送一个消息
-
参数说明:
-
msqid: 消息队列标识符
-
msgp: 打包消息信息结构体的起始地址
-
msgsz: 消息大小
-
msgflg: 设置阻塞标志, 0 表示阻塞
-
返回值:成功返回0, 失败返回-1
-
struct msgbuf {
long mtype; /* message type, must be > 0 /
char mtext[1]; / message data */
};
-
-
-
-
读取消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
-
函数功能: 从消息队列中读取数据
-
参数说明:
-
msqid : 消息队列的标识符
-
msgp: 接收消息数据结构体的起始地址
-
msgtype: 消息类型
- 如果类型是0, 默认读取第一个消息
- 如果类型是非零值, 从第一个消息开始查找,读取指定类型的消息
-
msgflg: 设置阻塞标志, 0表示阻塞
-
返回值: 成功返回读到的实际大小, 失败返回-1
-
struct msgbuf {
long mtype; /* message type, must be > 0 /
char mtext[1]; / message data */
};
-
-
-
移除消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>int msgctl(int msqid, int cmd, struct msqid_ds *buf);
-
程序调用
msgctl(消息队列id,IPC_RMID,NULL);
-
命令删除:ipcrm -q msqid
-
6共享内存
-
创建共享内存
int shmget(key_t key, size_t size, int shmflg);
- 函数功能: 创建共享内存
- 参数说明:
- key : 键值
- size : 内存大小
- shmflg: 设置标志
IPC_CREAT|0777
- 返回值:
- 成功返回共享内存的标识符
- 失败返回-1
int shmid = shmget(0x110,1024,IPC_CREAT|0777); if(shmid < 0){ perror("shmget"); return 0; }
-
系统映射共享内存地址给程序
void *shmat(int shmid, const void *shmaddr, int shmflg);
- 函数功能: 将共享内存映射到程序中
- 参数说明:
- shmid : 共享内存标识符
- shmaddr :指定映射地址, 一般使用NULL,由系统指定
- shmflg: 设置标志 ,一般使用 0
- 返回值:
- 成功返回映射的虚拟地址
- 失败返回 (void*)-1
void *ps = shmat(shmid,NULL,0);
-
可以实现一对多,多对多通信,但是要通过文件锁或者信号量实现多个写进程之间的互斥。
7信号量
1.创建信号量
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>int semget(key_t key, int nsems, int semflg);
int semid = semget(0x100,个数,IPC_CREAT|0777);
2.信号量操作
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);
- sops:指向sembuf结构的数组指针
- unsigned short sem_num:信号量信号量集合中信号量的索引号
- short sem_op:操作类型的整数整数:加到信号量的值上负数:信号量的值减去绝对值,如果小于零,进程阻塞,直到信号量的值至少等于其绝对值0:导致操作阻塞,直到信号量的值为0才继续。
- short sem_flg:一个符号位
- IPC_NOWAIT: 非阻塞操作
- IPC_UNDO:进程退出的时候自动撤销该次操作
- nsop s:第二个参数数组的长度