Linux系统之I/O进程
- 1. C库和C程序的编译过程
- 2. 标准I/O
- 3. 文件I/O
- 4. 进程
- 5. 线程
1. C库和C程序的编译过程
1.1 C库的分类
1.1.1 静态库的概念
- 静态库在程序的链接阶段被复制到了程序中,和程序运行的时候没有关系;
- 静态库文件以 “.a” 结尾
1.1.2 制作静态库
- 制作静态库的命令
gcc -c fun.c -o fun.o
静态库以== lib ==开头,中间是静态库的名字,以 .a 结尾
ar -crs libfun.a fun.o
- 使用静态库
gcc main.c -L ./ -lfun -o main
1.1.3 动态库的概念
- 动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用。
- 使用动态库的优点是系统只需载入一次动态库,不同的程序可以得到内存中相同的动态库的副本,因此节省了很多内存。
- 静态库文件以 “.so” 结尾
1.1.4 制作动态库
- 制作动态库的命令
gcc -fpic fun.c -c -o fun.o
动态库以 lib 开头,中间是动态库的名字,以 .so 结尾
gcc fun.o shared -o libmyfun.so
- 使用动态库
gcc main.c -L. -libmyfun -o hello
直接编译会导致./main: error while loading shared libraries: libmyfun.so:
cannot open shared object file: No such file or directory
解决方法:把libfund.so动态库文件放入/lib目录或/usr/lib目录,就可以执行了
可以直接使用 gcc main.c -libmyfun -o main 编译
1.2 编译过程(共四步)
预处理:gcc -E **.c -o *.i
原文件——预处理文件
编译: gcc -S **.i -o *.s
预处理文件——汇编文件
汇编: gcc -c **.s -o *.o
汇编文件——二进制文件
链接: gcc **.c -o *.elf
二进制文件——可执行文件
2. 标准I/O
2.1 文件类型
- 磁盘文件:指的是一组相关数据有序的集合,通常存储在外部介质上(磁盘),使用时候才调入内存。
- 设备文件:在操作系统中把每一个与主机相连输入输出设备看作文件,把他们输入输出同等与磁盘文件的读和写。
- 计算机的存储在物理上是二进制,所以物理上所有的磁盘文件本质上都是一样,以字节为单位进行顺序存储。
文本文件:基于字符编码的文件,常见编码ASCII美国信息交换标准代码,Unicode等。
存储的是字符数据,也就是将每一个字符的ASCII码进行存储。
二进制文件:基于值编码的文件,所有的数字,都是以整形数据进行存储,常见的有音频文件、视频文件等。
2.2 文件缓冲区(ANSI C采用缓冲文件系统处理数据文件)
2.2.1 全缓冲4096
- 清除缓冲区的方式
- 当缓冲满时。
- 程序正常结束时。
- 关闭系统时。
- 使用刷新函数 int fflush(FILE *stream)
2.2.2 行缓冲1024
- 清除缓冲区的方式
- 当缓冲满时。
- 程序正常结束时。
- 关闭系统时。
- 使用刷新函数 int fflush(FILE *stream)
- 遇到 \n
- 进程刚刚启动的时候,系统会我们自动打开两个文件,分别是 FILE * stdin 终端输入 和 FILE * stdout 终端输出
2.2.3 无缓冲1
进程刚刚启动的时候,系统会我们自动打开一个文件,FILE * stderr 终端错误输出
2.3 标准I/O相关函数
Mode(方式) 意义
"r" 只读方式打开
"w" 只写方式打开,如果文件存在就清空文件,如果文件不存在就创建一个文件
"a" 以追加方式打开,在末尾添加内容,如果文件不存在就创建文件
"r+" 以读写方式打开文件,如果不存在不会创建
"w+" 以读写方式打开文件,如果存在就清空,不存在就创建
"a+" 以读写方式打开文件,如果不存在就创建,如果存在就追加到尾部
************************************************************************************
"rb" 打开一个用于读取的二进制文件 (只读二进制文件)
"wb" 创建一个用于写入的二进制文件 (没有文件会创建文件,只写)
"ab" 追加到一个二进制文件
"rb+" 打开一个用于读/写的二进制文件
"wb+" 创建一个用于读/写的二进制文件
"ab+" 打开一个用于读/写的二进制文件
2.3.1 打开文件——fopen()函数
#include <stdio.h>
FILE * fopen(const char *pathname, const char *mode);
/*********************************************************************************
* Description: 打开文件
* Input: pathname:文件名,mode:模式
* Return: 成功:一个关联该文件的结构体(流)
* 失败:NULL
* Others:
**********************************************************************************/
2.3.2 关闭文件——fclose()函数
#include <stdio.h>
int fclose( FILE *stream );
/*********************************************************************************
* Description: 关闭文件
* Input: stream:文件流指针
* Return: 成功:0
* 失败:EOF
* Others:
**********************************************************************************/
2.3.3 打印错误信息——perror()函数
#include <stdio.h>
void perror( const char *str );
/*********************************************************************************
* Description: 打印str(字符串)和一个相应的执行定义的错误消息
* Input: 想要打印的str(字符串)
* Return:
* Others: 主要还是用于提示信息
**********************************************************************************/
2.3.4 检查文件是否到文件结尾——feof()函数
#include <stdio.h>
int feof( FILE *stream );
/*********************************************************************************
* Description: 在到达给出的文件流的文件尾时,返回一个非零值
* Input: stream:文件流指针
* Return: 返回一个非零值
*
* Others:
**********************************************************************************/
2.3.5 文件随机存取——fseek()函数
#include <stdio.h>
int fseek( FILE *stream, long offset, int origin );
/*********************************************************************************
* Description: 为文件流设置位置数据
* Input: stream:文件流指针, offset:偏移量, origin从哪里开始读取
* origin:
* 名称 说明
* SEEK_SET 从文件的开始处开始搜索
* SEEK_CUR 从当前位置开始搜索
* SEEK_END 从文件的结束处开始搜索
* Return: 成功:0
* 失败:EOF
* Others: 偏移量:往前偏移就是-1、-2...,往后偏移就是1、2...
**********************************************************************************/
示例:
/******************************
> File Name : 3.c
> Author : GK
> Description : 在文件中重新设定新的位置,将剩余的内容打印出来
> Created Time : 2020年11月01日 星期日 14时04分58秒
*****************************/
//test.txt 文件中的内容是:hello world\n
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
FILE *fp = fopen("test.txt", "r");
if(fp == NULL)
{
perror("fopen");
return -1;
}
if(fseek(fp, 3, SEEK_SET) == EOF)
{
perror("feek");
return -2;
}
while(1)
{
int ch = fgetc(fp);
if( feof(fp) )
{
break;
}
printf("%c", ch);
}
if( fclose(fp) == EOF)
{
perror("fclose");
return -3;
}
return 0;
}
终端运行结果:
gaokuo@Linux:~/Public/8练习$ make 3
cc 3.c -o 3
gaokuo@Linux:~/Public/8练习$ ./3
lo world
2.3.6 按照格式去写文件——fprintf()函数
#include <stdio.h>
int fprintf( FILE *stream, const char *format, ... );
/*********************************************************************************
* Description: 根据指定的format(格式)(格式)发送信息(参数)到由stream(流)指定的文件
* Input: stream:文件流指针,format:输出格式
* Return: 成功:输出的字符数
* 失败:-1
* Others: fprintf()只能和printf()一样工作
**********************************************************************************/
2.3.7 按照字符读写文件——fgetc()函数和fputc()函数
#include <stdio.h>
int fgetc( FILE *stream );
/*********************************************************************************
* Description: 按照字符读文件
* Input: stream:文件流指针
* Return: 成功:来自stream(流)中的下一个字符
* 失败或到达文件末尾:EOF
* Others:
**********************************************************************************/
int fputc( int ch, FILE *stream );
/*********************************************************************************
* Description: 按照字符写文件
* Input: stream:文件流指针 ch:要写入的字符
* Return: 成功:字符
* 失败:EOF
* Others:
**********************************************************************************/
示例:
/******************************
> File Name : 1.c
> Author : GK
> Description : 在文件中写入 hello world
> Created Time : 2020年11月01日 星期日 13时32分07秒
*****************************/
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char buf[] = "hello world\n";
FILE *fp = fopen("test.txt", "w");
if(fp == NULL)
{
perror("fopen");
return -1;
}
puts("fopen is OK!");
for(int i=0; i<strlen(buf); i++)
{
fputc(buf[i], fp);
}
int ret = fclose(fp);
if(ret == EOF)
{
perror("fclose");
return -2;
}
puts("fclose is OK!");
return 0;
}
终端运行结果:
gaokuo@Linux:~/Public/8练习$ make 1
cc 1.c -o 1
gaokuo@Linux:~/Public/8练习$ ./1
fopen is OK!
fclose is OK!
gaokuo@Linux:~/Public/8练习$ ls
1 1.c test.txt
/******************************
> File Name : 1.c
> Author: GK
> Description : 读出文件中的内容
> Created Time : 2020年11月01日 星期日 13时32分07秒
*****************************/
#include <stdio.h>
#include <string.h>
//读出文件中的内容
int main(int argc, char *argv[])
{
char buf[] = {0};
int ch=0;
FILE *fp = fopen("test.txt", "r");
if(fp == NULL)
{
perror("fopen");
return -1;
}
puts("fopen is OK!");
while(1)
{
ch = fgetc(fp);
if( feof(fp) )
break;
printf("%c", ch);
}
int ret = fclose(fp);
if(ret == EOF)
{
perror("fclose");
return -2;
}
puts("fclose is OK!");
return 0;
}
终端运行结果:
gaokuo@Linux:~/Public/8练习$ make 2
cc 2.c -o 2
gaokuo@Linux:~/Public/8练习$ ./2
fopen is OK!
hello world
fclose is OK!
2.3.8 按照行读写文件——fgets()函数和fputs()函数
#include <stdio.h>
char *fgets( char *str, int num, FILE *stream );
/*********************************************************************************
* Description: 按照行读文件
* Input: stream:文件流指针 str:读出字符串缓冲区 num: 读出多少字符
* Return: 成功:返回str(字符串)
* 失败:NULL
* Others: 从给出的文件流中读取[num - 1]个字符,并且把它们转储到buf中
**********************************************************************************/
int fputs( const char *str, FILE *stream );
/*********************************************************************************
* Description: 按照行写文件
* Input: stream:文件流指针 str:要写入的字符串
* Return: 成功:非负值
* 失败:EOF
* Others:
**********************************************************************************/
示例:
/******************************
> File Name : 4.c
> Author : GK
> Description : 使用fgets函数,在test1.txt文件中添加内容
> Created Time : 2020年11月01日 星期日 14时32分15秒
*****************************/
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char str[32] = {0};
FILE *fp = fopen("test1.txt", "w");
if(fp == NULL)
{
perror("fopen");
return -1;
}
for(int i=0; i < 3; i++) // 写入三行
{
sprintf(str, "%d: This is an anple\n", i+1);
if( fputs(str, fp) == EOF)
{
perror("fputs");
break;
}
}
if( fclose(fp) == EOF)
{
perror("fclose");
return -2;
}
return 0;
}
终端运行结果:
gaokuo@Linux:~/Public/8练习$ make 4
cc 4.c -o 4
gaokuo@Linux:~/Public/8练习$ ./4
gaokuo@Linux:~/Public/8练习$ cat test1.txt
1: This is an anple
2: This is an anple
3: This is an anple
/******************************
> File Name : 5.c
> Author : GK
> Description : 从 test1.txt文件中,并且定位到第二行,打印第二行的内容
> Created Time : 2020年11月01日 星期日 14时48分46秒
*****************************/
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
FILE *fp = fopen("test1.txt", "r");
char buf[32] = {0};
if(fp == NULL)
{
perror("fopen");
return -1;
}
puts("fopen is OK!");
fgets(buf, sizeof(buf)-1, fp);
if(fseek(fp, strlen(buf), SEEK_SET) == EOF)
{
perror("fseek");
return -2;
}
fgets(buf, sizeof(buf)-1, fp);
printf("buf: %s",buf);
if(fclose(fp) == EOF)
{
perror("fclose");
return -3;
}
puts("fclose is OK!");
return 0;
}
终端执行结果:
gaokuo@Linux:~/Public/8练习$ make 5
cc 5.c -o 5
gaokuo@Linux:~/Public/8练习$ ./5
fopen is OK!
buf: 2: This is an anple
fclose is OK!
2.3.9 按照块读写文件——fread()函数和fwrite()函数
#include <stdio.h>
int fread( void *buffer, size_t size, size_t num, FILE *stream );
/*********************************************************************************
* Description: 按照块读文件,读的是二进制文件
* Input: buffer:缓冲区,size:一块的大小,count:多少块,stream:文件流指针
* Return: 成功:读取的内容数量
* Others: 读取[num]个对象(每个对象大小为size(大小)指定的字节数),并把它们替换到由buffer(缓冲区)指定的数组
**********************************************************************************/
int fwrite( const void *buffer, size_t size, size_t count, FILE *stream );
/*********************************************************************************
* Description: 按照块写文件,写的也是二进制文件
* Input: buffer:缓冲区,size:一块的大小,count:多少块,stream:文件流指针
* Return: 成功:已写的对象的数量
* Others: 从数组buffer(缓冲区)中, 写count个大小为size(大小)的对象到stream(流)指定的流
**********************************************************************************/
示例:
/******************************
> File Name : 6.c
> Author : GK
> Description : 使用fwrite函数写入二进制文件中
> Created Time : 2020年11月01日 星期日 15时20分03秒
*****************************/
#include <stdio.h>
#include <string.h>
typedef struct{
char name[30];
int id;
}stu;
int main(int argc, char *argv[])
{
FILE *fp = fopen("1.txt", "w");
if(fp == NULL)
{
perror("fopen");
return -1;
}
puts("请输入信息(姓名):");
stu s[3];
for(int i=0; i<3; i++)
{
scanf("%s", s[i].name);
s[i].id = i;
}
fwrite(s, sizeof(stu), 3, fp);
if(fclose(fp) == EOF)
{
perror("fclose");
return -2;
}
return 0;
}
终端执行结果:
gaokuo@Linux:~/Public/8练习$ make 6
cc 6.c -o 6
gaokuo@Linux:~/Public/8练习$ ./6
请输入信息(姓名):
张三
李四
王五
gaokuo@Linux:~/Public/8练习$ cat 1.txt
张三Genu李四��~h�Q�����王五��\��~�9�Ugaokuo@Linux:~/Public/8练习$
/*因为fwrite函数写入的是二进制文件,所以除了名字是字符外,其余均是乱码,很正常*/
/******************************
> File Name : 7.c
> Author : GK
> Description : 使用fread函数读出二进制文件的内容
> Created Time : 2020年11月01日 星期日 15时37分07秒
*****************************/
#include <stdio.h>
typedef struct{
char name[30];
int id;
}stu;
int main(int argc, char *argv[])
{
FILE *fp = fopen("1.txt", "r");
if(fp == NULL)
{
perror("fopen");
return -1;
}
stu s[3];
if( fread(s, sizeof(stu), 3, fp) == 0)
{
puts("fread");
return -2;
}
for(int i=0; i<3; i++)
{
printf("name = %s id = %d\n", s[i].name, s[i].id);
}
if( fclose(fp) == EOF )
{
perror("fclose");
return -3;
}
return 0;
}
终端执行结果:
gaokuo@Linux:~/Public/8练习$ make 7
cc 7.c -o 7
gaokuo@Linux:~/Public/8练习$ ./7
name = 张三 id = 0
name = 李四 id = 1
name = 王五 id = 2
3. 文件I/O
文件IO
1. 不带缓冲区
2. Linux系统提供给我们的函数,直接调用系统调用(man手册第二章)
3. 通过文件描述符来操作文件
文件描述符
标准IO通过fopen获取FILE的流指针,FILE就描述了这个文件。
open获取一个int类型的数值,这个数值范围0~1023
其中有三个特殊的文件描述符
0:标准输入文件描述符
1:标准输出文件描述符
2:标准错误文件描述符
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
printf("stdin 的文件描述符是:%d\n",stdin->_fileno);
printf("stdout的文件描述符是:%d\n",stdout->_fileno);
printf("stderr的文件描述符是:%d\n",stderr->_fileno);
return 0;
}
终端执行结果:
gaokuo@Linux:~/Public/8练习$ make 8
cc 8.c -o 8
gaokuo@Linux:~/Public/8练习$ ./8
stdin 的文件描述符是:0
stdout的文件描述符是:1
stderr的文件描述符是:2
标准I/O | 文件I/O |
---|---|
r | O_RDONLY |
r+ | O_RDWR |
w | O_WRONLY | O_CREAT | O_TRUNC |
w+ | O_RDWR | O_CREAT | O_TRUNC |
a | O_WRONLY | O_APPEND | O_CREAT |
a+ | O_RDWR | O_APPEND | O_CREAT |
3.1 文件I/O相关函数
3.1.1 打开文件——open()函数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcnt1.h>
void open( const char *filename );
/*********************************************************************************
* Description: 打开文件
* Input: stream:文件流指针
* Return: 成功: 0:标准输入文件描述符
* 1:标准输出文件描述符
* 2:标准错误文件描述符
* 失败: -1
* Others:
**********************************************************************************/
3.1.2 关闭文件——close()函数
#include <unistd.h.h>
int close(int fd);
/*********************************************************************************
* Description: 关闭文件
* Input: fd:文件描述符
* Return: 成功: 0
* 失败: -1
* Others:
**********************************************************************************/
3.1.3 写文件——write()函数
#include <unistd.h.h>
ssize_t write(int fd, const void *buf, size_t count);
/*********************************************************************************
* Description: 打开文件
* Input: fd:文件描述符,buf:存储缓冲区,count:写入大小
* Return: 成功:写入字节数
* 失败:-1
* Others: 将buf中的数据写入fd文件中
**********************************************************************************/
示例:
/******************************
> File Name : 9.c
> Author : GK
> Description : 向2.txt文件中写入字符串
> Created Time : 2020年11月01日 星期日 16时23分29秒
*****************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
int fd = open("2.txt", O_WRONLY | O_CREAT | O_TRUNC, 0777);
if(fd == -1)
{
perror("open");
return -1;
}
char buf[32] = "This is an anple\n";
int re = write(fd, buf, strlen(buf));
if(re == -1)
{
perror("write");
return -3;
}else if(re > 0)
{
printf("写入了%d个数据\n",re);
}
if(close(fd) == -1)
{
perror("close");
return -2;
}
return 0;
}
终端执行结果:
gaokuo@Linux:~/Public/8练习$ make 9
cc 9.c -o 9
gaokuo@Linux:~/Public/8练习$ ./9
写入了17个数据
3.1.4 读文件——read()函数
#include <unistd.h.h>
ssize_t read(int fd, void *buf, size_t count);
/*********************************************************************************
* Description: 读文件
* Input: fd:文件描述符,buf:存储缓冲区,count:读出大小
* Return: 成功:读取字节数,0表示读完了
* 失败:-1
* Others: 将fd文件中的内容读出到buf中
**********************************************************************************/
示例:
/******************************
> File Name : 10.c
> Author : GK
> Description : 将文件2.txt文件中的内容读出来
> Created Time : 2020年11月01日 星期日 16时23分29秒
*****************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
int fd = open("2.txt", O_RDONLY);
if(fd == -1)
{
perror("open");
return -1;
}
char buf[32] = {0};
int re = read(fd, buf, sizeof(buf)-1);
if(re == 0)
{
puts("读到文件的末尾!");
}else if(re == -1)
{
perror("read");
}else
{
printf("buf: %s\n", buf);
}
if(close(fd) == -1)
{
perror("close");
return -2;
}
return 0;
}
终端执行结果:
gaokuo@Linux:~/Public/8练习$ vim 10.c
gaokuo@Linux:~/Public/8练习$ make 10
cc 10.c -o 10
gaokuo@Linux:~/Public/8练习$ ./10
buf: This is an anple
3.1.5 文件内容定位——lseek()函数
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
/*********************************************************************************
* Description: 文件定位
* Input: fd:文件描述符,offset:偏移量,
* whence:从哪里开始
* SEEK_SET:开头
* SEEK_CUR:当前位置
* SEEK_END:末尾
* Return: 成功:返回设置指针偏移后的位置
* 失败:-1
* Others:
**********************************************************************************/
示例:
/******************************
> File Name : 11.c
> Author : GK
> Description :
> Created Time : 2020年11月01日 星期日 18时46分20秒
*****************************/
/** 2.txt文件中的内容
* This is an anple
This is a book
This is a table
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
char buf[32] = "";
int fd = open("2.txt", O_RDONLY);
if (fd == -1)
{
perror("open");
return -1;
}
read(fd, buf, sizeof(buf)-1);
int ret = lseek(fd, strlen(buf), SEEK_SET);
if (ret == -1)
{
perror("lseek");
return 2;
}
ret = read(fd, buf, sizeof(buf));
if (ret < 0)
{
perror("read");
return -2;
}else if(ret == 0)
{
perror("read");
return -3;
}else
{
puts(buf);
}
close(fd);
return 0;
}
终端执行结果:
gaokuo@Linux:~/Public/8练习$ make 11
cc 11.c -o 11
gaokuo@Linux:~/Public/8练习$ ./11
This is a table
This is a book
3.1.6 查看文件属性——stat()函数
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);
/*********************************************************************************
* Description: 查看文件属性
* Input: pathname:文件名,statbuf:文件属性结构体
* Return: 成功:0
* 失败:-1
* Others: 将文件的属性保存到statbuf结构体中
**********************************************************************************/
3.2 查看用户属性的相关函数
3.2.1 获取uid用户的ID——getuid()函数
#include <sys/types.h>
#include <unistd.h>
uid_t getuid(void);
/*********************************************************************************
* Description: 获取uid用户的ID
* Input: 无
* Return: 用户的ID
* Others:
**********************************************************************************/
3.2.2 获取uid用户的passwd信息——getpwuid()函数
#include <sys/types.h>
#include <pwd.h>
struct passwd *getpwuid(uid_t uid);
/*********************************************************************************
* Description: 获取uid用户的passwd信息
* Input: uid:用户的ID
* Return: passwd的结构体信息
* Others:
**********************************************************************************/
3.2.3 获取gid组的组名——getgrgid()函数
#include <sys/types.h>
#include <grp.h>
struct group *getgrgid(gid_t gid);
/*********************************************************************************
* Description: 获取gid组的组名
* Input: gid:组ID
* Return: group的结构体信息
* Others:
**********************************************************************************/
3.3 获取时间的函数
3.3.1 获取时间(秒)——time()函数
#include <time.h>
time_t time(time_t *tloc);
/*********************************************************************************
* Description: 打开文件
* Input: tloc:秒的变量值
* Return: 成功:时间值参数
* 失败:-1
* Others:
**********************************************************************************/
3.3.2 获取本地时间——localtime()函数
#include <time.h>
struct tm *localtime(const time_t *timep);
/*********************************************************************************
* Description: 函数转换参数time为本地时间格式
* Input: timep:传入获取到的秒值
* Return: 成功:返回关于tm的时间结构体
* 失败:-1
* Others:
**********************************************************************************/
3.3.3 获取本地时间——ctime()函数
#include <time.h>
char *ctime( const time_t *time );
/*********************************************************************************
* Description: 函数转换参数time为本地时间格式
* Input: timep:传入获取到的秒值
* Return: 成功:返回关于time的字符串
* Others:
**********************************************************************************/
示例:
/******************************
> File Name : 12.c
> Author : GK
> Description : 时间函数综合示例程序
> Created Time : 2020年11月01日 星期日 19时00分29秒
*****************************/
#include <stdio.h>
#include <unistd.h>
#include <time.h>
int main(int argc, char *argv[])
{
time_t secs = time(NULL);
struct tm *ret = NULL;
ret = localtime(&secs);
printf("%02d-%02d-%02d\n",ret->tm_year+1900,ret->tm_mon+1,ret->tm_mday);
printf("%02d:%02d:%02d\n",ret->tm_hour,ret->tm_min,ret->tm_sec);
printf("从1970年一月一日到现在秒数为:%ld\n",secs);
return 0;
}
终端执行结果:
gaokuo@Linux:~/Public/8练习$ make 12
cc 12.c -o 12
gaokuo@Linux:~/Public/8练习$ ./12
2020-11-01
19:05:19
从1970年一月一日到现在秒数为:1604228719
4. 进程
4.1 进程相关概念
4.1.1 进程和程序的区别
程序:
- 程序是静态文件。
- 它是程序执行的过程,包括创建、调度和消亡。
进程:
- 是程序的一次执行过程,进程是动态文件。
- 它是运行着的实体,它是程序执行的过程,包括创建、调度和消亡。
- 进程是一个独立的可调度的程序。
4.1.2 小的知识点
- PID是父进程的ID号,PPID是子进程的ID号
- 进程的状态:运行态R、等待态S、停止态T、僵尸态Z
- 子进程先于父进程结束,子进程–>僵尸进程
- 父进程先于子进程结束,子进程–>孤儿进程
- 进程之间的相互管理关系:进程——>进程组——>会话组
4.1.3 进程的相关命令
- 查看进程之间的关系:
pstree
- 查看父进程号(PPID)和子进程号(PID):
ps -ef | grep "a.out"
- 杀死进程:
kill signum pid
kill -l 查看signum的属性
kill pid 默认的效果就是结束进程
kill 其实的给进程发信号
- 动态显示进程:
top
- 改变正在运行进程的优先级:
renice -n 优先级号 PID
- 按用户指定进程的优先级去启动:
nice -n 优先级号 PID
nice (-20 到 19)数字越小,优先级越高。
- 查看终端下的正在工作的作业:
jobs
- 将挂起的进程在后台执行:
第一步:Ctrl + z 让前台正在运行的进程挂起
第二步:bg 作业号 将挂起的进程 放到后台执行
第二步:bg 作业号 将挂起的进程 放到后台执行
- 把后台运行的进程放到前台运行:
fg 作业号
- 后台进程运行:
./a.out &
4.2 进程相关函数
4.2.1 创建进程——fork()函数
#include <unistd.h>
pid_t fork(void);
/*********************************************************************************
* Description: 创建进程
* Input:
* Return: 成功: 0:子进程
* 其他正整数:为子进程的ID
* 失败: -1
* Others:
* fork函数的特性:
* 1. 执行fork函数之后,fork函数会返回两次;
* 2. 在旧进程中返回时,返回值为0;
* 3. 在新进程返回时,返回值为进程的 pid;
* 4. 也可以叫做,复制一个进程更加贴切。
* 使用fork函数总结:
* 1. 在执行fork函数之前,操作系统只有一个进程,fork函数之前的代码只会被执行一次;
* 2. 在执行fork函数之后,操作系统有两个几乎一样的进程,fork函数之后的代码会被执行两次。
**********************************************************************************/
示例:
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
while(1)
{
printf("子进程\n");
sleep(1);
}
}else if(id < 0)
{
while(1)
{
printf("父进程获取到的id是子进程的ID:%d\n", id);
sleep(1);
}
}else
perror("fork");
return 0;
}
4.2.2 退出进程——exit()函数和_exit()函数
#include <stdlib.h>
void exit(int status);
/*********************************************************************************
* Description: 在结束进程的时候,会清理I/O缓存区
* Input: status:传入的参数是程序退出时的状态码
* 正常退出:EXIT_SUCCESS(0)
* 非正常退出:EXIT_FAILURE( -1 或 1 )
* Return:
* Others:
**********************************************************************************/
#include <stdlib.h>
void _exit(int status);
/*********************************************************************************
* Description: 在结束进程的时候,不会清理I/O缓存区,直接就会退出进程
* Input: status:传入的参数是程序退出时的状态码
* 正常退出:EXIT_SUCCESS(0)
* 非正常退出:EXIT_FAILURE( -1 或 1 )
* Return:
* Others:
**********************************************************************************/
4.2.3 回收进程——wait()函数和waitpid()函数
#include <unistd.h>
pid_t wait(int *status);
/*********************************************************************************
* Description: 调用该函数会使进程阻塞,直到任一个子进程结束 或 该进 程接收到一个信号为止;
* 阻塞父进程,会一直等待僵尸进程的产生,产生后将其杀死
* Input: status:保存状态的地址;
* 一般不需要关心子进程返回的状态,因此参数为NULL。
* Return: 成功: 返回进程的ID
* 失败: 0或-1
* Others: 常用用法:wait(NULL);
**********************************************************************************/
#include <unistd.h>
pid_t waitpid(pid_t pid, int *status, int options);
/*********************************************************************************
* Description: 非阻塞父进程,直到有僵尸进程的产生,但是,只有父进程的程序执行到此函数的时候,才会执行这个函数,才会把僵尸进程给杀死
* Input: pid:进程ID
* pid=-1 :等待任意子进程
* pid=>0 :等待与pid相等的子进程
* pid==0 :等待组ID等于调用进程组ID的任一进程。
* pid<-1 :等待组ID等于pid绝对值的任一进程。
* status:保存状态的地址;
* options:0:阻塞, WNOHANG:非阻塞
* 一般不需要关心子进程返回的状态,因此参数为NULL。
* Return: 成功: 返回进程的ID
* 失败: 0或-1
* Others: 常用用法:waitpid(-1, NULL, WNOHANG);
**********************************************************************************/
4.2.4 回收僵尸进程1
产生僵尸进程
/******************************
> File Name : 15.c
> Author : GK
> Description : 产生僵尸进程
> Created Time : 2020年11月01日 星期日 19时14分01秒
*****************************/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t id = fork();
int n=3;
if(id == 0)
{
while(n--)
{
printf("子进程:%d\n", n);
sleep(1);
}
printf("退出子进程\n");
exit(0); //正常退出,子进程先于父进程退出会产生僵尸进程,这是就需要父进程去回收进程(收尸)
}else if(id > 0)
{
while(1)
{
printf("父进程获取到的id是子进程的ID:%d\n", id);
sleep(1);
}
}else
perror("fork");
return 0;
}
终端执行结果:
gaokuo@Linux:~/Public/8练习$ ps -el | grep "15"
1 Z 1000 4366 4365 0 80 0 - 0 - pts/0 00:00:00 15 <defunct>
gaokuo@Linux:~/Public/8练习$
回收进程的方式 wait(NULL)
/******************************
> File Name : 13.c
> Author : GK
> Description : 将以上函数进行综合(创建进程、退出进程、回收进程)
:回收进程的方式 wait(NULL)
> Created Time : 2020年11月01日 星期日 19时14分01秒
*****************************/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t id = fork();
int n = 3;
if(id == 0)
{
while(n--)
{
printf("子进程:%d\n", n);
sleep(1);
}
printf("退出子进程\n");
exit(0); //正常退出,子进程先于父进程退出会产生僵尸进程,这是就需要父进程去回收进程(收尸)
}else if(id > 0)
{
//使用wait()函数回收进程时,会阻塞进程
wait(NULL);
printf("僵尸进程被回收\n");
while(1)
{
//使用waitpid()函数回收进程时,只会当程序执行到此函数的时候才会回收子进程
printf("父进程获取到的id是子进程的ID:%d\n", id);
sleep(1);
}
}else
perror("fork");
return 0;
}
终端执行结果:
gaokuo@Linux:~/Public/8练习$ make 13
cc 13.c -o 13
gaokuo@Linux:~/Public/8练习$ ./13
子进程:2
子进程:1
子进程:0
退出子进程
僵尸进程被回收
父进程获取到的id是子进程的ID:4295
父进程获取到的id是子进程的ID:4295
父进程获取到的id是子进程的ID:4295
父进程获取到的id是子进程的ID:4295
回收进程的方式 waitpid(-1, NULL, WNOHANG)
/******************************
> File Name : 14.c
> Author : GK
> Description : 将以上函数进行综合(创建进程、退出进程、回收进程)
> Created Time : 2020年11月01日 星期日 19时14分01秒
*****************************/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t id = fork();
int n = 3;
if(id == 0)
{
while(n--)
{
printf("子进程:%d\n", n);
sleep(1);
}
printf("退出子进程\n");
exit(0); //正常退出,子进程先于父进程退出会产生僵尸进程,这是就需要父进程去回收进程(收尸)
}else if(id > 0)
{
//使用wait()函数回收进程时,会阻塞进程
//wait(NULL);
while(1)
{
//使用waitpid()函数回收进程时,只会当程序执行到此函数的时候才会回收子进程
waitpid(-1, NULL, WNOHANG);
printf("父进程获取到的id是子进程的ID:%d\n", id);
sleep(1);
}
}else
perror("fork");
return 0;
}
终端执行结果:
gaokuo@Linux:~/Public/8练习$ make 14
cc 14.c -o 14
gaokuo@Linux:~/Public/8练习$ ./14
父进程获取到的id是子进程的ID:4339
子进程:2
父进程获取到的id是子进程的ID:4339
子进程:1
父进程获取到的id是子进程的ID:4339
子进程:0
父进程获取到的id是子进程的ID:4339
退出子进程
父进程获取到的id是子进程的ID:4339
父进程获取到的id是子进程的ID:4339
父进程获取到的id是子进程的ID:4339
父进程获取到的id是子进程的ID:4339
父进程获取到的id是子进程的ID:4339
4.3 创建守护进程
4.3.1 守护进程的特点
- 在后台偷偷的运行,是Linux中的后台服务进程,守护进程常常在系统启动时启动,系统结束时结束。
- 在后台记录系统的日志
- 也就是通常所说的Daemon进程,是Linux中的后台服务进程,独立于终端。
4.3.2 守护进程的编写步骤
- 创建子进程,父进程退出
- 在子进程中创建新会话
- 改变当前目录为根目录
- 重设文件权限掩码
- 关闭文件描述符
- 创建完成后,就可以执行想要做的程序了
示例:
/******************************
> File Name : 16.c
> Author : GK
> Description : 守护进程
> Created Time : 2020年11月01日 星期日 19时14分01秒
*****************************/
#include <stdio.h>
#include <unistd.h>
int main()
{
// 第一步
pid_t pid = fork();
if(pid > 0){
exit(0);
}
// 第二步
setsid();
// 第三步
chdir("./"); //以根目录作为工作目录,需要sudo
// 第四步
umask(0);
// 第五步
for(int i=0; i < 1024; i++){
close(i);
}
// 第六步
//让守护进程 每2s 写一次文件
FILE *fp = fopen("log.txt","w");
while(1){
sleep(2);
fputs("hello ",fp);
fflush(fp);
}
fclose(fp);
return 0;
}
4.4 在进程中启动其他程序——exec函数族
4.4.1 功能:
可以启动二进制文件,也可以启动脚本文件
4.4.2 常用后缀
- l: 代表以列表形式传参
- v:代表以矢量数组形式传参
- p:代表使用环境变量path来寻找指定执行文件
- e:代表用户提供自定义的环境变量
4.4.3 函数族常用函数
4.4.3.1 execl()函数
#include <unistd.h>
int execl(const char *path, const char *arg, ...)
/*********************************************************************************
* Description: 需要填写程序的路径,才可以执行
* Input: path:程序的路径或指令的路径
* arg: 程序的名字或指令的名字
* ... 执行程序的参数,没有则填NULL
* Return: 成功: 不返回
* 失败: 返回-1
* Others:
**********************************************************************************/
4.4.3.2 execlp()函数
#include <unistd.h>
int execlp(const char *file, const char *arg, ...)
/*********************************************************************************
* Description: 从PATH 环境变量中查找文件并执行
* 寻找的路径是PATH路径
* Input: file:程序的名字或指令的名字
* arg: 程序的名字或指令的名字
* ... 执行程序的参数,没有则填NULL
* Return: 成功: 不返回
* 失败: 返回-1
* Others:
**********************************************************************************/
4.4.3.3 execv()函数
#include <unistd.h>
int execv(const char *path, char *const argv[])
/*********************************************************************************
* Description:
* Input: path:用户自定义的程序路径或指令路径
* arg: 程序的名字或指令的名字的指针数组,并且需要以空指针(NULL)结束
* Return: 成功: 不返回
* 失败: 返回-1
* Others:
**********************************************************************************/
4.4.3.4 execve()函数
#include <unistd.h>
int execve(const char *path, char *const argv[], char *const envp[])
/*********************************************************************************
* Description: 用户自定义的文件路径去执行
* Input: path:用户自定义的程序的路径或指令的路径
* arg: 程序的名字或指令的名字的指针数组,并且需要以空指针(NULL)结束
* envp 传递给执行文件的新环境变量数组
* Return: 成功: 不返回
* 失败: 返回-1
* Others:
**********************************************************************************/
示例
/******************************
> File Name : 17.c
> Author : GK
> Description :
> Created Time : 2020年11月01日 星期日 19时14分01秒
*****************************/
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t result;
result = fork();
if(result > 0)
{
if(execlp("ls", "ls", "-l", NULL) < 0)
{
perror("execlp");
exit(-1);
}
}
return 0;
}
4.5 进程间通信
4.5.1 早期的进程通信
4.5.1.1 无名管道
(1) 简介
在Linux内核(内存)中创建一个管道。
管道内部的实质是:队列。
(2) 特点
- 只能用于亲缘关系的进程之间的通信
- 半双工的通信方式,固定的读和写
- 管道没有数据,读管道阻塞
- 如果写端关闭,读管道读0返回
- 如果读端关闭,写管道将会产生管道破裂,默认结束进程
- 遵循先进先出
- 不支持lseek
(3) 创建无名管道——pipe()函数
#include <unistd.h>
int pipe(int filedes[2]);
/*********************************************************************************
* Description: 创建管道 (无名)
* Input: filedes[0]为管道的读取端
* filedes[1]为管道的写入端
* Return: 成功: 返回0
* 失败: 返回-1
* Others: 只有具有亲缘关系的进程之间才能使用这种无名管道
**********************************************************************************/
示例
// 通过无名管道 实现子父进程间的通信
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void)
{
int fd[2];
pipe(fd); //创建无名管道
int ret;
int rfd = fd[0]; //fd[0] :读端
int wfd = fd[1]; //fd[1] :写端
pid_t pid;
pid = fork();
if(pid==0){
close(rfd); //子进程 写管道,关闭无用的管道 rfd
char buf[32];
while(1){
gets(buf);
write(wfd,buf,sizeof(buf));
}
exit(0);
}
else if(pid>0){
close(wfd); //父进程 读管道 关闭无用的管道 wfd
char buf[32]={0};
while(1){
ret = read(rfd,buf,sizeof(buf));
printf("ret:%d,buf:%s\n",ret,buf);
}
}
else{
perror("fork");
}
return 0;
}
4.5.1.2 有名管道
(1) 简介
- 先创建一个管道文件,这个文件名在磁盘中,而数据是在内存中,每传输完成后,管道内的数据就会清零。
- 通过命令mkfifo创建,或通过函数mkfifo创建
- 有名管道相当于在读写文件。
(2) 特点
- 通过mkfifo创建有名管道
- 名字存在磁盘中,可以实现非亲缘进程通信
- 数据存在内存中
- 如果写端关闭,读管道读0返回
- 如果读端关闭,写管道将会产生管道破裂,默认结束进程
- 遵循先进先出
- 不支持lseek
(3) 创建有名管道——mkfifo()函数
#include <unistd.h>
int mkfifo(const char *pathname, mode_t mode);
/*********************************************************************************
* Description: 创建管道 (有名)
* Input: pathname:路径名,mode:权限
* Return: 成功: 返回0
* 失败: 返回-1
* Others: 非情缘关系进程也可以通信
**********************************************************************************/
示例
- 创建有名管道,并且在管道里写数据
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
//mkfifo : 创建有名管道
int mkfifo_ret = mkfifo("myfifo",0666);
if(mkfifo_ret == -1){
perror("mkfifo");
exit(-1);
}
printf("有名管道创建成功\n");
//写有名管道f1
int fd = open("f1",O_WRONLY);
if(fd==-1){
perror("open");
exit(-1);
}
char buf[256];
int ret;
while(1){
printf("请输入数据\n");
gets(buf);
ret = write(fd,buf,sizeof(buf));
if(ret <= 0){
break;
}
}
close(fd);
return 0;
}
- 在有名管道中读出数据
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
//读有名管道f1
int fd = open("f1",O_RDONLY);
if(fd==-1){
perror("open");
exit(-1);
}
char buf[256];
int ret;
while(1){
ret = read(fd,buf,sizeof(buf));
if(ret <= 0){
if(ret==0){
printf("写端已关闭\n");
}
break;
}
printf("ret:%d,buf:%s\n",ret,buf);
}
close(fd);
return 0;
}
4.5.1.3 信号
(1) 简介
信号,就相当于捕获一些信号的产生,然后做出相应的执行动作。
1. 硬件来源:比如按下了键盘或其他硬件故障;
2. 软件来源:最常用发送信号的系统函数kill、raise、alarm以及一些非法操作。
信号的处理方式:
1. 忽略信号 SIG_IGN
2. 捕获信号(自定义信号处理)
3. 默认 SIG_DFL
常见的信号:
2) SIGINT // ctrl+c
9) SIGKILL //结束 (不可捕获,忽略)
11) SIGSEGV //段错误
13) SIGPIPE //管道破裂
14)SIGALRM //闹钟信号
17) SIGCHLD //僵尸信号
18) SIGCONT //继续
19) SIGSTOP //停止 (不可捕获,忽略)
20) SIGTSTP //ctrl+z
(2) 信号的相关函数
1> 给进程发送信号——kill()函数
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
/*********************************************************************************
* Description: 发信号
* Input: pid:进程号,sig:信号编号
* pid>0 将信号传给进程识别码为pid的进程
* pid=0 将信号传给和目前进程相同的进程组
* pid=-1 将信号广播传送给系统的所有进程
* pid<0 将信号传给进程组识别码为pid绝对值的所有进程
* Return: 成功返回0, 出错返回-1
* Others: 给进程发指定的信号
**********************************************************************************/
2> 给自己发送信号——raise()函数
#include <sys/types.h>
#include <signal.h>
int raise(int sig);
/*********************************************************************************
* Description: 发信号
* Input: sig:信号编号
* Return: 成功返回0, 出错返回-1
* Others: 给自己发指定的信号
**********************************************************************************/
3> 捕获信号并处理——signal()函数
#include <sys/types.h>
#include <signal.h>
sighandler_t signal(int signum, sighandler_t handler);
/*********************************************************************************
* Description: 信号处理
* Input: signum:信号编号,handler:信号处理函数
* Return: 成功返回函数指针, 出错返回-1
* Others: sighandler_t 是函数指针类型
typedef void (*sighandler_t)(int);
**********************************************************************************/
(3) 示例
1> kiil函数
#include<stdio.h>
#include <sys/types.h>
#include <signal.h>
int main(void)
{
int pid;
int signum;
int ret;
while(1){
printf("给进程发信号,请先输入pid,再输入信号值\n");
scanf("%d",&pid);
scanf("%d",&signum);
kill(pid, signum);
}
return 0;
}
2> raise函数
#include<stdio.h>
#include <sys/types.h>
#include <signal.h>
int main(void)
{
int signum;
printf("给自己发信号,请输入信号值\n");
scanf("%d",&signum);
raise(signum);
while(1);
return 0;
}
3> signal函数
#include<stdio.h>
#include <unistd.h>
#include <signal.h>
void signal_handler(int sig)
{
printf("接收到了 闹钟信号:%d\n",sig);
}
int main(void)
{
// signal(SIGALRM,signal_handler); //捕获闹钟信号,处理方式:忽略
signal(SIGINT,signal_handler);
//注意信号的三种处理方式: 1. SIG_IGN 忽略
// 2. SIG_DFL 默认
// 3. 自定义函数
while(1){
sleep(2);
printf("while...\n");
}
return 0;
}
(4) 回收僵尸进程2
/******************************
> File Name : 13.c
> Author : GK
> Description : 使用signal函数,回收僵尸进程
:回收方式使用 waitpid() 方式。
> Created Time : 2020年11月01日 星期日 19时14分01秒
*****************************/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
void signal_handler(int sig)
{
while(waitpid(-1, NULL, WNOHANG) > 0);
}
int main()
{
pid_t id = fork();
int n = 3;
if(id == 0)
{
while(n--)
{
printf("子进程:%d\n", n);
sleep(1);
}
printf("退出子进程\n");
exit(0); //正常退出,子进程先于父进程退出会产生僵尸进程,这是就需要父进程去回收进程(收尸)
}else if(id > 0)
{
//使用wait()函数回收进程时,会阻塞进程
wait(NULL);
printf("僵尸进程被回收\n");
while(1)
{
//使用waitpid()函数回收进程时,只会当程序执行到此函数的时候才会回收子进程
printf("父进程获取到的id是子进程的ID:%d\n", id);
sleep(1);
}
}else
perror("fork");
return 0;
}
4.5.2 system V 进程通信
system V 进程通信的相关命令
ipcs //查看ipc对象
ipcrm -m shmid //删除共享内存 (根据id删除)
ipcrm -q msgid //删除消息队列 (根据id删除)
ipcrm -s semid //删除信号灯 (根据id删除)
4.5.2.1 共享内存:效率最高
(1) 打开或创建共享内存——shmget()函数
共享内存中的key值是IPC_PRIVATE :
是私有的(用于亲缘关系的通信) 反复的执行进程程序,会导致反复的创建
使用ftok()函数的返回值:
是共享的(用于非亲缘关系或亲缘关系的通信)反复的执行进程程序,不会反复的创建,shmid是惟一的了
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
/*********************************************************************************
* Description: 创建共享内存
* Input: key:key值 (IPC_PRIVATE 或 ftok的返回值)
size:大小
shmflg:标志(同open函数的权限位)
* Return: 成功返回共享内存ID;不成功返回-1
* Others:
**********************************************************************************/
(2) 建立地址映射——shmat()函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
/*********************************************************************************
* Description: 获取共享内存地址
* Input: shmid:共享内存id
shmaddr:指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。
shmflg:标志(SHM_RDONLY:共享内存只读, 0:读写)
* Return: 成功返回地址。失败时返回-1
* Others:
**********************************************************************************/
(3) 解除地址映射——shmdt()函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
/*********************************************************************************
* Description: 解除映射
* Input:
shmaddr:共享内存的地址
* Return: 成功返回返回 0 失败时返回-1
* Others:
**********************************************************************************/
(4) 共享内存的控制——shmctl()函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
/*********************************************************************************
* Description: 完成对共享内存的控制
* Input: shmid:共享内存标识符
cmd:
IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中
IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内
IPC_RMID:删除这片共享内存
buf:共享内存管理结构体。具体说明参见共享内存内核结构定义部分
* Return: 成功返回0, 出错返回-1
* Others: 给进程发指定的信号
**********************************************************************************/
4.5.2.2 消息队列:可以根据类型取消息
实质是链表
(1) 打开或创建消息队列——msgget()函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int flag);
/*********************************************************************************
* Description: 创建或打开消息队列
* Input: key:key值 (IPC_PRIVATE 或 ftok的返回值)
flag:标志(同open函数的权限位)
* Return: 成功返回队列ID,失败返回-1
* Others:
**********************************************************************************/
(2) 发消息——msgsnd()函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
//消息包类型
struct msgbuf {
long mtype; /* 消息类型,必须 > 0 */
char mtext[1]; /* 消息文本 */
};
int msgsnd(int msqid, const void *ptr, size_t size, int flag);
/*********************************************************************************
* Description: 发送消息
* Input: msqid:id
ptr:消息包地址(发送)
size:消息包正文大小,
flag:方式(0:阻塞方式 IPC_NOWAIT:表示非阻塞方式)
* Return: 成功返回0,失败返回-1
* Others:
**********************************************************************************/
(3) 收消息——msgrcv()函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
/*********************************************************************************
* Description: 读取消息
* Input: msqid:id
ptr:消息包地址(接收)
size:消息正文大小
type:类型(0:读取任意类型)
flag:方式(0:阻塞方式 IPC_NOWAIT:表示非阻塞方式)
* Return: 成功返回消息数据的长度,失败返回-1
* Others:
**********************************************************************************/
(4) 控制消息队列——msgctrl()函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
/*********************************************************************************
* Description: 控制消息队列
* Input: msqid:消息队列的队列ID
cmd:
IPC_STAT:读取消息队列的数据结构msqid_ds,并将其存储在buf制定的地址中
IPC_SET:设置消息队列的数据结构msqid_ds中的ipc_perm域
IPC_RMID:从系统内核中删除消息队列
buf:标志(同open函数的权限位)
* Return: 成功:0 ,失败:-1
* Others:
**********************************************************************************/
4.5.2.3 信号灯集:用于实现同步或互斥
(1) 创建信号灯——semget()函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
/*********************************************************************************
* Description: 创建信号灯
* Input: key:key值 (IPC_PRIVATE 或 ftok的返回值)
nsems:信号灯个数
semflg:标志(同open函数的权限位)
* Return: 成功返回0,失败返回-1
* Others:
**********************************************************************************/
(2) 信号灯的操作——semop()函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);
/*********************************************************************************
* Description: 信号灯的操作
* Input: semid:id
sops:传入的结构体
struct sembuf {
short sem_num; //灯的编号
short sem_op;
// 1: +1操作 post
//-1: -1操作 wait
// 0: 等待,直到信号灯的值变成0
short sem_flg; // 0:阻塞 IPC_NOWAIT:非阻塞
};
nsops:灯的个数
* Return: 成功返回0,失败返回-1
* Others:
**********************************************************************************/
(3) 信号灯的控制——semctl()函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
/*********************************************************************************
* Description: 控制信号灯
* Input: semid:信号灯的编号
semnum:
cmd:对信号灯的操作
IPC_STAT:获得该信号灯的semid_ds结构体中的第四个参数arg结构变量的buf域指向的semid_ds的结构中
IPC_SETVAL:将信号量值设置为arg的val值
IPC_GETVAL:返回信号量的当前值
IPC_RMID:从系统值删除信号量(或信号量灯集)
* Return: 成功:不同的cmd的值返回不同的值
* IPC_STAT、 IPC_SETVAL、IPC_RMID返回0
* IPC_GETVAL返回信号量的当前值
* 失败:-1
* Others:
**********************************************************************************/
4.5.3 BSD socket 网络通信
4.5.3.1 socket通信
5. 线程
5.1 线程相关概念
5.1.2 什么是线程?
线程是程序执行时的最小单位,即CPU调度和分派的基本单位,一个进程可以由多个线程组成,同一个进程中的多个线程之间共享此进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。
5.1.3 进程和线程的区别
5.1.3.1 相同点
- 两者均可并发执行
5.1.3.2 不同
- 地址空间:
线程共享此进程中的地址空间,而进程之间是独立的地址空间。- 资源拥有:
线程共享此进程中的资源(如I/O、内存、CPU等),而进程之间的资源是相互独立的。- 执行过程:
线程不用独立运行,必须依赖存在于进程中,由进程提供多个线程进程执行,而每个独立的进程都有一个程序运行的入口、顺序执行序列、程序入口。- 健壮性:
其中一个线程崩溃,回使整个进程后死掉,而一个进程崩溃后,不会对其他进程产生影响。所以多进程要比多线程健壮。- 进程或线程切换时:
进程之间切换,消耗资源大,但是效率高;如果涉及到频繁的切换时,使用线程要比进程好!!!
如果要求同时进行,又要共享某些变量的并发操作时,只能用线程。
5.1.3.3 优缺
- 线程执行开销小,但是不利于资源的管理和保护
- 进程执行开销小,但是能够很好的进行资源的管理和保护
5.2 线程相关函数
5.2.1 创建线程——pthread_create()函数
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
/*********************************************************************************
* Description: 创建线程
* Input: thread:线程id
attr:线程属性 默认NULL
start_routine:线程函数
arg:函数运行的参数
* Return: 成功: 返回0
* 失败: 返回错误号
* Others: 不能用perror()进行打印,采用strerror();
**********************************************************************************/
使用pthread_create()函数来创建和使用线程的时候的注意事项:
- 当多线程同时访问同一个全局变量或全局数组时,一定要加互斥量来保护数据,也就是上锁,千万不要忘记解锁
- 正确处理线程结束的方式:一个线程的终止,线程的资源不会随线程的终止而释放,一定要添加pthread_detach()函数和pthread_join()函数,来释放该线程所占用的资源。
5.2.1.1 pthread_create函数的传参
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
char buf[32]; //同一进程中子线程主线程 共享全局变量
//线程函数
void * fun(void *argc)
{
pthread_detach(pthread_self());
while(1){
printf("子线程 buf:%s, argc = %d\n",buf, *((int *)&argc));
sleep(2);
}
pthread_exit(NULL);
}
int main(void)
{
int num = 6;
pthread_t pid;
pthread_create(&pid, NULL, fun, (void *)&num);
while(1){
printf("主线程写入buf\n");
gets(buf);
}
return 0;
}
5.2.2 退出线程——pthread_exit()函数
#include <pthread.h>
void pthread_exit(void *retval);
/*********************************************************************************
* Description: 线程退出
* Input: retval:线程退出状态
* Return: 无
* Others:
**********************************************************************************/
5.2.3 回收线程——pthread_detach()函数和pthread_join()函数
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
/*********************************************************************************
* Description: 阻塞等待线程退出,回收线程的资源
* Input: thread:线程id
* retval:线程的状态
* Return: 成功:返回0
* 失败:返回错误号
* Others:
**********************************************************************************/
#include <pthread.h>
int pthread_detach(pthread_t thread);
/*********************************************************************************
* Description: 线程在运行结束后会自动释放系统分配的资源
* Input: thread:线程id
* Return: 成功:返回0
* 失败:返回错误号
* Others:
**********************************************************************************/
5.2.4 取消线程——pthread_cancel()函数
#include <pthread.h>
int pthread_cancel(pthread_t thread);
/*********************************************************************************
* Description: 取消线程
* Input: thread:线程id
* Return: 成功:返回0
* 失败:返回错误号
* Others:
**********************************************************************************/
5.2.5 过去本线程的ID——pthread_self()函数
#include <pthread.h>
pthread_t pthread_self(void);
/*********************************************************************************
* Description: 获取本线程id
* Input: 无
* Return: 成功:返回本线程的ID
* Others:
**********************************************************************************/
示例
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <time.h>
pthread_t function1_id;
pthread_t function2_id;
void *pthread_function1_task(void *argc);
void *pthread_function2_task(void *argc);
char buf[32] = {0};
int main(void)
{
pthread_create(&function1_id, NULL, pthread_function1_task, NULL);
pthread_create(&function2_id, NULL, pthread_function2_task, NULL);
while(1);
return 0;
}
/*
* 线程功能:每隔5秒打印时间
*
*/
void *pthread_function1_task(void *argc)
{
pthread_detach(pthread_self());
time_t seconds;
while(1)
{
time(&seconds);
printf("time: %s\n", ctime(&seconds));
sleep(5);
}
pthread_exit(NULL);
}
/*
* 线程功能:从终端获取字符串,然后输出到终端中
*
*/
void *pthread_function2_task(void *argc)
{
pthread_detach(pthread_self());
while(1)
{
gets(buf);
if( *buf == 'q')
{
exit(0);
}else
{
printf("buf is : %s\n", buf);
}
sleep(1);
}
pthread_exit(NULL);
}
5.3 线程间通信
5.3.1 最简单的通信方式——全局变量
使用全局类型数据
如果定义了一个全局变量,可以使用
如果定义了一个全局类型的数据,不可以单独使用,一定要加锁。。。
如下面的例子:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
int buf[128];
void * fun(void *argc)
{
while(1)
{
/* 共享数据 */
memset(buf,0,sizeof(buf));
int i;
for(i=0;i<128;i++){
buf[i] = i;
}
}
pthread_exit(NULL);
}
int main(void)
{
pthread_t pid;
pthread_create(&pid, NULL, fun, NULL);
while(1){
/* 共享数据 */
int i;
getchar();
for(i=0;i<128;i++){
printf("%d,",buf[i]);
}
printf("\n");
}
return 0;
}
5.3.2 同步通信方式——信号量
强调要有先后顺序
5.3.2.1 初始化信号量——sem_init()函数
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value)
/*********************************************************************************
* Description: 信号量初始化
* Input: sem:信号量
* pshared:指明信号量的类型
* 共享区域(0:线程间,1:进程间)
* value:信号量初值
* Return: 成功:返回0
* 失败:返回-1
* Others:
**********************************************************************************/
5.3.2.2 等待信号量(P操作)——sem_wait()函数
#include <semaphore.h>
int sem_wait(sem_t *sem);
/*********************************************************************************
* Description: P操作(信号量减1)
* Input: sem:信号量
* Return: 成功:返回0
* 失败:返回-1
* Others: 减到0值时阻塞
**********************************************************************************/
5.3.2.3 释放信号量(v操作)——sem_post()函数
#include <semaphore.h>
int sem_post(sem_t *sem);
/*********************************************************************************
* Description: V操作(信号量加1)
* Input: sem:信号量
* Return: 成功:返回0
* 失败:返回-1
* Others: 不会阻塞
**********************************************************************************/
示例
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <semaphore.h>
char buf[32]; //同一进程中子线程主线程 共享全局变量
sem_t sem1 ; //定义信号量
sem_t sem2 ; //定义信号量
//线程函数
void * fun(void *argc)
{
while(1){
sem_wait(&sem1); //sem1 阻塞
sleep(4);
printf("子线程buf:%s\n",buf);
sem_post(&sem2); //解开sem2
}
pthread_exit(NULL);
}
int main(void)
{
sem_init(&sem1, 0, 0);//信号量初始化
sem_init(&sem2, 0, 1);//信号量初始化
//创建线程 让子线程 执行fun
pthread_t pid;
pthread_create(&pid, NULL, fun, NULL);
while(1){
sem_wait(&sem2); //sem2阻塞
gets(buf);
sem_post(&sem1); //解开sem1
}
return 0;
}
5.3.3 互斥通信方式——互斥锁
强调保证共享数据的完整性,没有顺序
5.3.3.1 初始化互斥锁——pthread_mutex_init()函数
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *restrictattr);
/*********************************************************************************
* Description: 初始化锁
* Input: mutex:锁
* restrict attr:属性
* Return: 成功:返回0
* 失败:返回-1
* Others:
**********************************************************************************/
5.3.3.2 上锁——pthread_mutex_lock()函数
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex)
/*********************************************************************************
* Description: P操作(信号量减1)
* Input: mutex:锁
* Return: 成功:返回0
* 失败:返回非0
* Others:
**********************************************************************************/
5.3.3.3 解锁——pthread_mutex_unlock()函数
#include <pthread.h>
int sem_post(sem_t *sem);
/*********************************************************************************
* Description: 解锁
* Input: mutex:锁
* Return: 成功:返回0
* 失败:返回非0
* Others:
**********************************************************************************/
示例
//通过互斥锁 实现访问共享数据的完整性
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
int buf[128];
pthread_mutex_t mutex; //定义一把锁
void * fun(void *argc)
{
while(1){
pthread_mutex_lock(&mutex); //加锁
/* 共享数据 */
memset(buf,0,sizeof(buf));
int i;
for(i=0;i<128;i++){
buf[i] = i;
}
pthread_mutex_unlock(&mutex); //解锁
}
pthread_exit(NULL);
}
int main(void)
{
pthread_mutex_init(&mutex,NULL); //互斥锁的初始化
pthread_t pid;
pthread_create(&pid, NULL, fun, NULL);
while(1){
pthread_mutex_lock(&mutex); //加锁
/* 共享数据 */
int i;
for(i=0;i<128;i++){
printf("%d,",buf[i]);
}
printf("\n");
pthread_mutex_unlock(&mutex); //解锁
}
return 0;
}