IO进程学习笔记

【1】标准IO

定义

在C库中定义的一组专门用于输入输出的函数

特点

1.通过缓冲机制减少系统调用的次数,提高效率。
2.围绕流进行操作的,流用FILE *描述,FILE是结构体,保存文件的属性信息。
3.默认打开了三个流,stdin(标准输入)、stdout(标准输出)、stderr(标准错误) 

缓冲区

全缓存:和文件相关
	刷新缓存的条件:
		1.程序正常退出
		2.缓存区满刷新
		3.fflush强制刷新
行缓存:和终端相关
	刷新缓存的条件:
		1.程序正常退出
		2.\n刷新
		3.缓冲区满刷新
		4.fflush强制刷新
不缓存:没有缓存区,标准错误

计算标准输出的缓存区大小:
printf(“\n”);//调用输出
printf(“%d”,stdout->_IO_buf_end-stdout->_IO_buf_base);

函数接口

打开文件-读写文件-关闭文件
打开文件
FILE *fopen(const char *path,const char *mode);
功能:打开文件
参数:path:打开文件的路径
		 mode:打开方式
		 	r		只读,流被定位到文件开头
		 	r+		可读可写,流被定位到开头
		 	w		只写,文件不存在创建,存在清空,流被定位到文件开头
		 	w+		可读可写,文件不存在创建,存在清空,流被定位到文件开头
		 	a		追加,文件不存在创建,存在追加,流被定位到文件末尾
		 	a+		可读可写,文件不存在创建,存在追加;第一次读文件时流被定位到文件开头,写始终在末尾
读写文件
1.每次一个字符的读取
int fgetc(FILE *stream)
功能:从文件中读取一个字符
参数:stream:文件流
返回值:
	成功:读到字符的ASCII码
	失败或读到文件末尾:EOF

int fputc(int c,FILE *stream)
功能:向文件中写入一个字符
参数:c:要写的字符
	 stream:文件流
返回值:成功:写的字符的ASCII
		失败:EOF

练习:编程实现cat功能。cat 文件名
代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    FILE *f = fopen(argv[1],"r");
    int c;
    while ((c=fgetc(f))!=EOF)
    {
        printf("%c",c);
    }
    return 0;
}
补充
int feof(FILE *stream);
功能:判断文件有没有到达结尾
返回:到达结尾,返回非零值

int ferror(FILE *stream);
功能:检测文件有没有出错
返回:文件出错,返回非零值
2.每次一串字符的读写
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

练习:编程实现wc -l命令功能。计算文件行数

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
    FILE *f = fopen(argv[1],"r");
    int c,len=0,len1=0;
    char buf[32];
    // 方法一:用fgetc
    while ((c=fgetc(f))!=EOF)
    {
        if (c=='\n')
        {
            len++;
        }
    }
    printf("%d\n",len);
    rewind(f);
    // 方法二:用fgets
    while (fgets(buf,32,f))
    {
        if (buf[strlen(buf)-1]=='\n')
        {
            len1++;
        }
    }

    printf("%d\n",len1);
    return 0;
}
3.二进制数据读写
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
Fread和fwrite函数注意:
1)两个函数的返回值为:读或写的对象数
2)对于二进制数据我们更愿意一次读或写整个结构。
文件定位操作
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
fseek(fp, 0, SEEK_SET); ==>  rewind(fp);
重定向打开文件
FILE * freopen(const char *pathname,  const char *mode,  FILE* fp)
功能:将指定的文件流重定向到打开的文件中
参数:path:文件路径
mode:打开文件的方式(同fopen)
  	fp:文件流指针
返回值:
	成功:返回文件流指针
  	失败:NULL
关闭文件
int fclose(FILE* stream);
功能:关闭文件
参数:stream:文件流

作业:
题目要求:编程读写一个文件test.txt,每隔1秒向文件中写入一行数据,类似这样:
1, 2007-7-30 15:16:42
2, 2007-7-30 15:16:43
该程序应该无限循环,直到按Ctrl-C中断程序。
再次启动程序写文件时可以追加到原文件之后,并且序号能够接续上次的序号,比如:
1, 2007-7-30 15:16:42
2, 2007-7-30 15:16:43
3, 2007-7-30 15:19:02
4, 2007-7-30 15:19:03
5, 2007-7-30 15:19:04

注:
time(); //获取系统当前时间,单位秒,从当前时间到1970.1.1
time_t time(time_t *tloc);  //*tloc = 时间,  return  时间;
time_t  t; 
time(&t);  //t = time(NULL);

localtime();//将时间秒数转换成年月日时分秒格式
struct tm *localtime(const time_t *timep);
struct tm *tm;
tm = localtime();

fprintf();
int fprintf(FILE *stream, const char *format, ...);
功能:将字符串输出到文件中

sprintf();
int sprintf(char *str, const char *format, ...);
功能:将字符串存储在数组中
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <time.h>
#include <fcntl.h>

int main(int argc, char const *argv[])
{
    int len=0;
    char buf[32];
    time_t t;
    FILE *f = fopen("hw1.txt","a+");
    if(!f){
        perror("fopen error");
        return -1;
    }
    while (fgets(buf,32,f))
    {
        if(buf[strlen(buf)-1]=='\n')    len++;
    }
    
    while (1)
    {
        len++;
        time(&t);
        struct tm *tim = localtime(&t);
        sprintf(buf,"%d %d-%d-%d %d:%d:%d\n",len,tim->tm_year+1900,tim->tm_mon+1,tim->tm_mday,tim->tm_hour,tim->tm_min,tim->tm_sec);
        printf("%s",buf);
        fputs(buf,f);
        fflush(NULL);
        sleep(1);
    }
    
    return 0;
}

练习:编程实现“head -n 文件名”的功能
思路:打开文件,循环读文件,当读到文件末尾时循环结束,将文件内容输出到终端,计算文件行数,和n进行比较,如果相等循环结束。
argv[1] : “-11”,
argv[1]+1:“11”, 将其转换成整数“11” – > 11, atoi(“11”) --> 11

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    FILE *f = fopen(argv[2],"r");
    if(!f){
        perror("fopen error!");
        return -1;
    }
    int n = atoi(argv[1]+1),len=0;
    char buf[32];
    while (fgets(buf,32,f))
    {
        if(len==n)  break;
        printf("%s",buf);
        if(buf[strlen(buf)-1]=='\n')  len++;  
    }
    
    return 0;
}

【2】文件IO

概念

定义
在posix(可移植操作系统接口)中定义的一组用于输入输出的函数
特点
1)没有缓冲机制,每次操作都引起系统调用
2)围绕文件描述符进行操作,文件描述符是非负整数(>=0),顺序分配
3)默认打开三个文件描述符:0(标准输入)、1(标准输出)、2(标准错误)
4)可以操作任意类型的文件,除目录

函数接口

打开文件-读写文件-关闭文件-定位操作
打开文件
int open(const char *pathname, int flags);
功能:打开文件
参数:
	pathname:文件路径名
  	flags:打开文件的方式
     	 O_RDONLY:只读
       	 O_WRONLY:只写
       	 O_RDWR:可读可写
       	 O_CREAT:创建
       	 O_TRUNC:清空
       	 O_APPEND:追加   
返回值:
	成功:文件描述符
    失败:-1
当第二个参数中有O_CREAT选项时,需要给open函数传递第三个参数,指定创建文件的权限 
int open(const char *pathname, int flags, mode_t mode);
创建出来的文件权限为指定权限值&(~umask)  //umask为文件权限掩码
标准IO文件IO
rO_RDONLY
r+O_RDWR
wO_WRONLY\O_CREAT\O_TRUNC,0666
w+O_RDWR\O_CREAT\O_TRUNC,0666
aO_WRONLY\O_CREAT\O_APPEND,0666
a+O_RDWR\O_CREAT\O_APPEND,0666
读写文件
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
关闭文件
int close(int fd);
功能:关闭文件
参数:fd:文件描述符
文件定位
off_t lseek(int fd, off_t offset, int whence);
功能:设定文件的偏移位置
参数:fd:文件描述符
offset偏移量  
    正数:向文件结尾位置移动
    负数:向文件开始位置
whence  相对位置
    SEEK_SET   开始位置
    SEEK_CUR   当前位置
    SEEK_END   结尾位置
返回值:
	成功:文件的当前位置
    失败:-1
标准IO和文件IO的对比
标准IO文件IO
定义C库中定义的一组输入输出函数posix中定义的一组输入输出的函数
特点有缓冲机制
围绕流进行操作
默认打开三个流:stdin、stdout、stderr
只能操作普通文件
无缓冲机制
围绕文件描述符操作
默认打开三个文件描述符:0/1/2
除目录外可以操作任意类型文件
函数

练习:编程实现cp命令功能。(使用文件IO)
如:cp 源文件 新文件

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char const *argv[])
{
    int fd1 = open(argv[1], O_RDONLY);
    int fd2 = open(argv[2], O_WRONLY|O_CREAT|O_TRUNC,0666);
    if (fd1==-1 || fd2==-1)
    {
        perror("fopen error!");
        return -1;
    }
    char buf[32];
    int s;
    while ((s=read(fd1,buf,32))!=0)
    {
        write(fd2,buf,s);
    }

    return 0;
}

文件属性获取
int stat(const char *path, struct stat *buf);
功能:获取文件属性
参数:
	path:文件路径名
    buf:保存文件属性信息的结构体
返回值:
	成功:0
  	失败:-1
struct stat {
    ino_t     st_ino;     /* inode号 */
    mode_t    st_mode;    /* 文件类型和权限 */
    nlink_t   st_nlink;   /* 硬链接数 */
    uid_t     st_uid;     /* 用户ID */
    gid_t     st_gid;     /* 组ID */
    off_t     st_size;    /* 大小 */
    time_t    st_atime;   /* 最后访问时间 */
    time_t    st_mtime;   /* 最后修改时间 */
    time_t    st_ctime;  /* 最后状态改变时间 */
};
判断文件类型

在这里插入图片描述


判断文件权限

在这里插入图片描述


检测一下今天学的吧!
编程实现“ls -l 文件名”命令的功能

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>

void switchType(mode_t mode)
{
    switch (mode & S_IFMT)
    {
    case S_IFSOCK:
        printf("s");
        break;
    case S_IFLNK:
        printf("l");
        break;
    case S_IFREG:
        printf("-");
        break;
    case S_IFBLK:
        printf("b");
        break;
    case S_IFDIR:
        printf("d");
        break;
    case S_IFCHR:
        printf("c");
        break;
    case S_IFIFO:
        printf("p");
        break;
    default:
        break;
    }
}

int main(int argc, char const *argv[])
{
    char buf[32];
    struct stat st;
    if (lstat(argv[1], &st) != 0)
    {
        perror("open error");
        return -1;
    }
    switchType(st.st_mode);
    char s[4] = "rwx-";
    for (int i = 0; i < 9; i++)
    {
        printf("%c", (st.st_mode & (0400 >> i)) ? s[i % 3] : s[3]);
    }
    printf(" %d ", st.st_nlink);
    struct passwd *pw = getpwuid(st.st_uid);
    printf("%s ", pw->pw_name);
    struct group *gp = getgrgid(st.st_gid);
    printf("%s ",gp->gr_name);
    printf("%ld",st.st_size);
    struct tm *tim = localtime(&(st.st_mtime));
    printf(" %d月  %d %d:%d %s\n",tim->tm_mon+1,tim->tm_mday,tim->tm_hour,tim->tm_min,argv[1]);
    return 0;
}

目录操作

DIR *opendir(const char *name);
功能:获得目录流
参数:要打开的目录
返回值:
	成功:目录流
    失败:NULL
    
struct dirent *readdir(DIR *dirp);
功能:读目录
参数:要读的目录流
返回值:
	成功:读到的信息    
    失败或读到目录结尾:NULL
返回值为结构体,该结构体成员为描述该目录下的文件信息
struct dirent {
    ino_t   d_ino;              /* 索引节点号*/
    off_t   d_off;              /*在目录文件中的偏移*/
    unsigned short d_reclen;    /* 文件名长度*/
    unsigned char  d_type;      /* 文件类型 */
    char    d_name[256];        /* 文件名 */
};

int closedir(DIR *dirp);
功能:关闭目录
参数:dirp:目录流

练习:实现ls命令功能。不显示隐藏文件

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>

int main(int argc, char const *argv[])
{
    DIR *d = opendir("./");
    struct dirent *dd = readdir(d);
    while (dd)
    {
        if((dd->d_name)[0]!='.')
            printf("%s  ",dd->d_name);
        dd = readdir(d);
    }
    printf("\n");
    return 0;
}

库
源文件:xx.c   包含main函数的.c、包含子函数的.c
头文件:xx.h   函数声明、头文件、结构体/共用体/枚举定义、typedef、define
库文件:如下

1.库的定义

当使用别人的函数时除了包含头文件以外还要有库
 库:就是把一些常用函数的目标文件打包在一起,提供相应函数的接口,便于程序员使用;本质上来说库是一种可执行代码的二进制形式
由于windows和linux的本质不同,因此二者库的二进制是不兼容的

2.库的分类

静态库和动态库,本质区别是代码被载入时刻不同。
1) 静态库在程序编译时会被连接到目标代码中。
	优点:程序运行时将不再需要该静态库;运行时无需加载库,运行速度更快
	缺点:静态库中的代码复制到了程序中,因此体积较大;
		  静态库升级后,程序需要重新编译链接
2) 动态库是在程序运行时才被载入代码中。
优点:程序在执行时加载动态库,代码体积小;
 	 程序升级更简单;
     不同应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。
缺点:运行时还需要动态库的存在,移植性较差

3.静态库制作

1-将源文件编译生成目标文件
gcc -c  add.c -o add.o
2-创建静态库用ar命令,它将很多.o转换成.a
ar  crs  libmyadd.a  add.o 
静态库文件名的命名规范是以lib为前缀,紧接着跟静态库名,扩展名为.a
3-测试使用静态库:
gcc  main.c  -L.  -lmyadd    // -L指定库的路径  -l指定库名
执行./a.out

4.动态库制作

1-我们用gcc来创建共享库
gcc -fPIC  -c hello.c  -o hello.o
-fPIC 创建与地址无关的编译程序
gcc  -shared  -o  libmyhello.so  hello.o

2-测试动态库使用
gcc main.c  -L. -lmyhello
可以正常编译通过,但是运行时报错./a.out: error while loading shared libraries: libmyadd.so: cannot open shared object file: No such file or directory
原因:当加载动态库时,系统会默认从/lib或/usr/lib路径下查找库文件
解决方法(有三种):
(1)把库拷贝到/usr/lib和/lib目录下。(此方法编译时不需要指定库的路径) 
(2)在LD_LIBRARY_PATH环境变量中加上库所在路径。 
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. 
(终端关闭,环境变量就没在了)
(3) 添加/etc/ld.so.conf.d/*.conf文件。把库所在的路径加到文件末尾,并执行ldconfig刷新
sudo vi xx.conf
添加动态库存在的路径,如:
/home/hq/teach/22092/day3/dynamic

补充:
库默认的查找路径:/lib 或 /usr/lib
头文件默认的查找路径:/usr/include
	#include <stdio.h>  :从系统路径下查找
	#include “head.h”  :从当前路径下查找
gcc编译选项:
	-L路径:指定库的路径
	-I路径:(i大写)指定头文件的路径
	-l库名:(L小写)指定库文件
当头文件不在当前路径下时,两种解决方法:
1.gcc  main.c  -I头文件路径
2.将头文件存放在系统路径下/usr/include

进程

【1】概念

1. 程序和进程区别
程序:编译好的可执行文件
	存放在磁盘上的指令和数据的有序集合(文件)
	程序是静态的,没有任何执行的概念
进程:一个独立的可调度的任务
	执行一个程序所分配的资源的总称
	进程是程序的一次执行过程
	进程是动态的,包括创建、调度、执行和消亡
2. 特点
1)系统会为每个进程分配0-4g的虚拟空间,0-3g是用户空间,每个进程独有,3g-4g是内核空间,所有进程共享
2)系统为每个进程分配时间片(几毫秒~几十毫秒),当一个进程时间片用完时,CPU调度另一个进程,从而实现进程调度的切换
3.进程段
Linux中的进程包含三个段:
“数据段”存放的是全局变量、常数以及动态数据分配的数据空间(如malloc函数取得的空间)等。
“正文段”存放的是程序中的代码
“堆栈段”存放的是函数的返回地址、函数的参数以及程序中的局部变量 
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)僵尸态:Z 当进程已经终止运行,但还占用系统资源,要避免僵尸态的产生
6. 进程状态切换图
进程创建后,进程进入就绪态,当CPU调度到此进程时进入运行态,当时间片用完时,此进程会进入就绪态,如果此进程正在执行一些IO操作(阻塞操作)会进入阻塞态,完成IO操作(阻塞结束)后又可进入就绪态,等待CPU的调度,当进程运行结束即进入结束态。

在这里插入图片描述

【2】函数接口

1.创建子进程
pid_t fork(void);
功能:创建子进程
返回值:
    成功:在父进程中:返回子进程的进程号 >0
         在子进程中:返回值为0
    失败:-1并设置errno


特点:
1)子进程几乎拷贝了父进程的全部内容。包括代码、数据、系统数据段中的pc值、栈中的数据、父进程中打开的文件等;但它们的PID、PPID是不同的。
2)父子进程有独立的地址空间,互不影响;当在相应的进程中改变全局变量、静态变量,都互不影响。
3)若父进程先结束,子进程成为孤儿进程,被init进程收养,子进程变成后台进程。
4)若子进程先结束,父进程如果没有及时回收,子进程变成僵尸进程(要避免僵尸进程产生)
killall  a.out
2. 回收进程资源
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
3. 结束进程
void exit(int status);
功能:结束进程,刷新缓存
void _exit(int status);
功能:结束进程,不刷新缓存
参数:status是一个整型的参数,可以利用这个参数传递进程结束时的状态。
    通常0表示正常结束;
其他的数值表示出现了错误,进程非正常结束

exit和return区别:
exit:不管在子函数还是主函数,都可以结束进程
return:当子函数中有return时返回到函数调用位置,并不结束进程

作业:通过父子进程完成对文件的拷贝(cp),父进程从文件开始到文件的一半开始拷贝,子进程从文件的一半到文件末尾。要求:文件IO

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>

int main(int argc, char const *argv[])
{
    int fd1 = open(argv[1], O_RDONLY);
    int fd2 = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if (fd1 < 0 || fd2 < 0)
    {
        perror("open error");
        return -1;
    }
    int len = lseek(fd1, 0, SEEK_END) / 2, s;
    char buf[32];
    pid_t pid = fork();
    if (pid < 0)
    {
        printf("create error!");
        return -1;
    }
    else if (pid == 0)
    {
        lseek(fd1, len, SEEK_SET);
        lseek(fd2, len, SEEK_SET);
        while ((s = read(fd1, buf, 32)) != 0)
        {
            write(fd2, buf, s);
        }
    }
    else
    {
        wait(NULL);
        lseek(fd1,0,SEEK_SET);
        lseek(fd2,0,SEEK_SET);
        while (len>0)
        {
            if(len>32){
                s=read(fd1,buf,32);
            }else{
                s=read(fd1,buf,len);
            }
            len -= s;
            write(fd2,buf,s);
        }
    }
    return 0;
}
4.获取进程号
pid_t getpid(void);
功能:获取当前进程的进程号
pid_t getppid(void);
功能:获取当前进程的父进程号
exec函数族--了解

【3】守护进程

1. 特点:守护进程是后台进程;生命周期比较长,从系统启动时开启,系统关闭时结束;它是脱离控制终端且周期执行的进程。
2. 步骤:
	1) 创建子进程,父进程退出
让子进程变成孤儿进程,成为后台进程;fork()
	2) 在子进程中创建新会话
让子进程成为会话组组长,为了让子进程完全脱离终端;setsid()
	3) 改变进程运行路径为根目录
原因进程运行的路径不能被删除或卸载;chdir("/")
	4) 重设文件权限掩码
目的:增大进程创建文件时权限,提高灵活性;umask(0)
	5) 关闭文件描述符
将不需要的文件关闭;close()

练习:创建一个守护进程,循环间隔1s向当前路径下任意文件中写入一串字符“hello”

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char const *argv[])
{
    pid_t pid = fork();
    if (pid < 0)
    {
        perror("create process error");
        return -1;
    }
    else if (pid > 0)
    {
    }
    else
    {
        setsid();
        chdir("/");
        umask(0);
        for (int i = 0; i < 3; i++)
        {
            close(i);
        }
        int fd = open("/home/hq/study/IO/exer/protect.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
        if(fd<0){
            perror("open err");
            return -1;
        }
        while (1)
        {
            write(fd,"hello\n",6);
            sleep(1);
        }
    }
    return 0;
}

线程

【1】概念

1.定义:
是一个轻量级的进程,为了提高系统的性能引入线程
Linux里同样用task_struct来描述一个线程。
线程和进程都参与统一的调度。
在同一个进程中创建的线程共享该进程的地址空间。
2. 进程和线程区别(重点):
共性:都为操作系统提供了并发执行能力
不同点:
	调度和资源:线程是系统调度的最小单位,进程是资源分配的最小单位
	地址空间方面:同一个进程创建的多个线程共享进程的资源;进程的地址空间相互独立
	通信方面:线程通信相对简单,只需要通过全局变量可以实现,但是需要考虑临界资源访问的问题;进程通信比较复杂,需要借助进程间的通信机制(借助3g-4g内核空间)
	安全性方面:线程安全性差一些,当进程结束时会导致所有线程退出;进程相对安全

【2】函数接口

创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, 
                    void *(*start_routine) (void *), void *arg);
功能:创建线程
参数:thread:线程标识
     attr:线程属性, NULL:代表设置默认属性
     start_routine:函数名:代表线程函数
     arg:用来给前面函数传参
返回值:成功:0
       失败:错误码
退出线程
int  pthread_exit(void *value_ptr) 
功能:用于退出线程的执行
参数:value_ptr:线程退出时返回的值
返回值:成功 : 0
        失败:errno
线程回收
int  pthread_join(pthread_t thread,  void **value_ptr) 
功能:用于等待一个指定的线程结束,阻塞函数
参数:thread:创建的线程对象
        value_ptr:指针*value_ptr指向线程返回的参数
返回值:成功 : 0
       失败:errno

练习:主线程循环从终端输入数据,子线程循环将数据打印到终端,当输入quit时程序结束。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>

char buf[10];
int flag = 0;

void * print(void *buffer){
    while (1)
    {
        if(flag){
            if(!strcmp(buf,"quit")) break;
            printf("%s\n",buf);
            flag=0;
        }
            
    }
    pthread_exit(NULL);
}

int main(int argc, char const *argv[])
{
    pthread_t pt;
    if(pthread_create(&pt,NULL,print,NULL)){
        perror("create thread err");
        return -1;
    }
    while (strcmp(buf,"quit"))
    {
        scanf("%s",buf);
        flag=1;
    }
    pthread_join(pt,NULL);
    return 0;
}

线程同步

1.概念:

同步(synchronization)指的是多个任务(线程)按照约定的顺序相互配合完成一件事情

2.同步机制:

 通过信号量实现线程间同步。
信号量:由信号量来决定线程是继续运行还是阻塞等待,信号量代表某一类资源,其值表示系统中该资源的数量
信号量是一个受保护的变量,只能通过三种操作来访问:初始化、P操作(申请资源)、V操作(释放资源)
信号量的值为非负整数

3.特性:

	P操作:
	当信号量的值大于0时,可以申请到资源,申请资源后信号量的值减1
	当信号量的值等于0时,申请不到资源,函数阻塞
	V操作:
		不阻塞,执行到释放操作,信号量的值加1

4.函数接口

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. 概念

临界资源:一次仅允许一个进程所使用的资源
临界区:指的是一个访问共享资源的程序片段
互斥:多个线程在访问临界资源时,同一时间只能一个线程访问
互斥锁:通过互斥锁可以实现互斥机制,主要用来保护临界资源,每个临界资源都由一个互斥锁来保护,线程必须先获得互斥锁才能访问临界资源,访问完资源后释放该锁。如果无法获得锁,线程会阻塞直到获得锁为止。

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:互斥锁

3. 死锁:

是指两个或两个以上的进程/线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去
死锁产生的四个必要条件
1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
注意:当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。

条件变量

和互斥锁搭配使用,实现同步机制
pthread_cond_init:初始化
pthread_cond_wait:阻塞等待条件产生,没有条件产生时阻塞,同时解锁,当条件产生时结束阻塞,再次上锁
	pthread_mutex_lock(); //上锁
	pthread_cond_wait(cond, lock); //如果没有条件产生时,解锁,当等待到条件产生时,上锁
pthread_cond_signal:产生条件,不阻塞
pthread_cond_wait先执行,pthread_cond_signal再产生条件

函数接口

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

进程间通信

【1】进程间通信方式

1)早期的进程间通信:
无名管道、有名管道、信号
2)systerm V IPC:
共享内存、消息队列、信号灯集
3)BSD:
套接字

【2】无名管道

特点
a. 只能用于具有亲缘关系的进程之间的通信
b. 半双工的通信模式,具有固定的读端和写端
c. 管道可以看成是一种特殊的文件,对于它的读写可以使用文件IO如read、write函数.
d. 管道是基于文件描述符的通信方式。当一个管道建立时,它会创建两个文件描述符
fd[0]和fd[1]。其中fd[0]固定用于读管道,而fd[1]固定用于写管道。
函数接口
int pipe(int fd[2])
功能:创建无名管道
参数:文件描述符 fd[0]:读端  fd[1]:写端
返回值:成功 0
       失败 -1
注意事项
a. 当管道中无数据时,读操作会阻塞;
   管道中无数据,将写端关闭,读操作会立即返回
b. 管道中装满(管道大小64K)数据写阻塞,一旦有4k空间,写继续
c. 只有在管道的读端存在时,向管道中写入数据才有意义。否则,会导致管道破裂,向管道中写入数据的进程将收到内核传来的SIGPIPE信号  (通常Broken pipe错误)。

作业:通过无名管道实现父子进程间通信。
父进程:循环从终端输入数据,子进程:循环将数据输出;当输入quit事程序结束

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <pthread.h>
#include <fcntl.h>

int main(int argc, char const *argv[])
{
    int fd[2];
    char buf[32];
    if (pipe(fd))
    {
        perror("create pipe error!");
        exit(-1);
    }
    pid_t pid = fork();
    if (pid < 0)
    {
        perror("create progress err1");
        exit(-1);
    }
    else if (pid == 0)
    {
        while (1)
        {
            if (read(fd[0], buf, 5))
            {
                if (!strcmp(buf, "quit"))
                    break;
                printf("%s\n", buf);
            }
        }
        exit(0);
    }
    else
    {
        while (1)
        {
            scanf("%s", buf);
            write(fd[1], buf, strlen(buf)+1);
            if (!strcmp(buf, "quit"))
                break;
        }
        wait(NULL);
    }

    return 0;
}

【有名管道】

特点
a. 有名管道可以使互不相关的两个进程互相通信。
b. 有名管道可以通过路径名来指出,并且在文件系统中可见,但内容存放在内存中。
c. 进程通过文件IO来操作有名管道
d. 有名管道遵循先进先出规则
e. 不支持如lseek() 操作 
函数接口
int mkfifo(const char *filename,mode_t mode);
功能:创健有名管道
参数:filename:有名管道文件名
       mode:权限
返回值:成功:0
       失败:-1,并设置errno号
注意对错误的处理方式:
如果错误是file exist时,注意加判断,如:if(errno == EEXIST)

注:函数只是在路径下创建管道文件,往管道中写的数据依然写在内核空间。

注意事项
a. 只写方式,写阻塞,一直到另一个进程把读打开
b. 只读方式,读阻塞,一直到另一个进程把写打开
c. 可读可写,如果管道中没有数据,读阻塞;
步骤:
	创建有名管道
	打开管道文件
	读写文件

练习:通过有名管道实现文件复制。
要求:两个进程,一个进程读源文件,另一个进程写新文件
read.c -> ./r 源文件
write.c -> ./w 新文件

read.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <pthread.h>
#include <fcntl.h>
#include <errno.h>

int main(int argc, char const *argv[])
{
    int fd1 = open(argv[1], O_RDONLY);
    char buf[32];
    ssize_t s;
    if (mkfifo("./fifo", 0666))
    {
        if (errno == EEXIST)
        {
            printf("file exist\n");
        }
        else
        {
            perror("mkfifo err!");
            exit(-1);
        }
    }

    int fd = open("./fifo", O_WRONLY);

    if (fd < 0 || fd1 < 0)
    {
        perror("fopen err!");
        exit(-1);
    }

    while ((s = read(fd1, buf, 32)) != 0)
    {
        write(fd, buf, s);
        fflush(NULL);
    }
    close(fd);
    close(fd1);
    return 0;
}

write.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <pthread.h>
#include <fcntl.h>
#include <errno.h>

int main(int argc, char const *argv[])
{
    int fd1 = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC, 0666);
    char buf[32];
    ssize_t s;
    if (mkfifo("./fifo", 0666))
    {
        if (errno == EEXIST)
        {
            printf("file exist\n");
        }
        else
        {
            perror("mkfifo err!");
            exit(-1);
        }
    }
    int fd = open("./fifo", O_RDONLY);

    if (fd < 0 || fd1 < 0)
    {
        perror("fopen err!");
        exit(-1);
    }
    
    while ((s = read(fd, buf, 32))!=0)
    {
        write(fd1, buf, s);
        fflush(NULL);
    }
    close(fd);
    close(fd1);
    return 0;
}
有名管道和无名管道的区别:
无名管道有名管道
应用场景具有亲缘关系的进程间互不相关的两个进程间
特点半双工通信方式,有固定读端和写端,通过文件描述符操作在路径中存在管道文件,遵循先进先出,不支持lseek
读写特性管道中没有数据时,读阻塞
管道中写满数据时,写阻塞
只写方式打开文件,写阻塞
只读方式打开文件,读阻塞
函数及操作方式pipe(int fd[2] )
文件IO的read/write
mkfifo(“fifo”, 0666);
文件IO的open/read/write/close

【信号】

1. 概念
1)信号是在软件层次上对中断机制的一种模拟,是一种 异步通信方式
2)信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。
3)如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。
2. 信号的响应方式
1)忽略信号:对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及SIGSTOP。
2)捕捉信号:定义信号处理函数,当信号发生时,执行相应的处理函数。
3)执行缺省操作:Linux对每种信号都规定了默认操作 
3. 信号种类
SIGKILL:结束进程,不能被忽略不能被捕捉
SIGSTOP:结束进程,不能被忽略不能被捕捉
SIGCHLD:子进程状态改变时给父进程发的信号,不会结束进程
SIGINT:结束进程,对应快捷方式ctrl+c
SIGTSTP:暂停信号,对应快捷方式ctrl+z
SIGQUIT:退出信号,对应快捷方式ctrl+\
SIGALRM:闹钟信号,alarm函数设置定时,当到设定的时间时,内核会向进程发送此信号结束进程。
SIGTERM:结束终端进程,kill 使用时不加数字默认是此信号
4. 函数接口
int kill(pid_t pid, int sig);
功能:信号发送
参数:pid:指定进程
   sig:要发送的信号
返回值:成功 0     
       失败 -1
int raise(int sig);
功能:进程向自己发送信号
参数:sig:信号
返回值:成功 0   
       失败 -1
int pause(void);
功能:用于将调用进程挂起,直到收到信号为止。
#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

练习:父子进程,父进程收到SIGINT信号,打印hello同时给子进程发送SIGUSR1信号,当子进程收到SIGUSR1时,打印world,最后退出。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <pthread.h>
#include <fcntl.h>
#include <signal.h>
pid_t pid;
void printhello(int sig)
{
    printf("hello\n");
    kill(pid, SIGUSR1);
    wait(NULL);
    exit(0);
}
void printworld(int sig)
{
    printf("world\n");
    exit(0);
}
int main(int argc, char const *argv[])
{
    pid = fork();
    if (pid < 0)
    {
        perror("create pid err!");
        exit(-1);
    }
    else if (pid == 0)
    {
        signal(SIGINT, SIG_IGN);
        signal(SIGUSR1, printworld);
        // pause();
    }
    else
    {
        signal(SIGINT, printhello);
        // pause();
        //kill(pid, SIGUSR1);
        // wait(NULL);
    }
    while (1)
    {
        pause();
    }
    return 0;
}

【消息队列】

1. 特点:
消息队列是IPC对象的一种
消息队列由消息队列ID来唯一标识
消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等。
消息队列可以按照类型来发送/接收消息
2. 步骤:
1)产生key值ftok
2)创建或打开消息队列
3)添加消息:按照类型把消息添加到已打开的消息队列末尾
4)读取消息:可以按照类型把消息从消息队列中取走
5)删除消息队列
3. 操作命令
ipcs  -q :查看消息队列
ipcrm  -q  msgid :删除消息队列
4. 函数接口
key_t ftok(const char *pathname, int proj_id);
功能:产生一个独一无二的key值
参数:
    Pathname:已经存在的可访问文件的名字
    Proj_id:一个字符(因为只用低8位)
返回值:成功:key值
      失败:-1
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)

作业:

  1. 用信号的知识实现司机和售票员问题。
    1)售票员捕捉SIGINT(代表开车)信号,向司机发送SIGUSR1信号,司机打印(let’s gogogo)
    2)售票员捕捉SIGQUIT(代表停车)信号,向司机发送SIGUSR2信号,司机打印(stop the bus)
    3)司机捕捉SIGTSTP(代表到达终点站)信号,向售票员发送SIGUSR1信号,售票员打印(please get off the bus)
    4)司机等待售票员下车,之后司机再下车。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <pthread.h>
#include <signal.h>
#include <fcntl.h>

pid_t pid;

void conduct_go(int sig)
{
    kill(getppid(), SIGUSR1);
}
void conduct_stop(int sig)
{
    kill(getppid(), SIGUSR2);
}

void dirver_go(int sig)
{
    printf("let's go\n");
}
void dirver_stop(int sig)
{
    printf("stop the bus\n");
}
void arriveStaDriver(int sig){
    kill(pid,SIGUSR1);
    wait(NULL);
    // printf("driver get off the bus\n");
    exit(0);
}
void arriveStaConducter(int sig){
    printf("please get off the bus\n");
    // printf("conductor get off the bus\n");
    exit(0);
}
int main(int argc, char const *argv[])
{
    pid = fork();
    if (pid < 0)
    {
        perror("pid err!");
        exit(-1);
    }
    //售票员
    else if (pid == 0)
    {
        signal(SIGINT, conduct_go);
        signal(SIGQUIT, conduct_stop);
        signal(SIGUSR1,arriveStaConducter);

        signal(SIGUSR2,SIG_IGN);
        signal(SIGTSTP, SIG_IGN);
    }
    //司机
    else
    {
        signal(SIGINT, SIG_IGN);
        signal(SIGQUIT, SIG_IGN);

        signal(SIGUSR1,dirver_go);
        signal(SIGUSR2,dirver_stop);
        signal(SIGTSTP, arriveStaDriver);
    }
    while(1)
        pause();
    return 0;
}

练习:通过消息队列实现进程间通信。
一个进程从终端输入设备名称及操作。
另一个进程根据指令打印对应的提示信息。
在这里插入图片描述
msg_send.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>

struct msgComm
{
    long mtype;
    char nam[32];
    int op;
};

int main(int argc, char const *argv[])
{
    struct msgComm msg1;
    struct msgComm msg2;
    key_t key;
    pid_t pid;
    int msgid;
    if ((key = ftok("app", 't')) < 0)
    {
        perror("create key err!");
        exit(-1);
    }
    msgid = msgget(key, IPC_CREAT | IPC_EXCL | 0666);
    if (msgid < 0)
    {
        if (errno == EEXIST)
        {
            msgid = msgget(key, 0666);
        }
        else
        {
            perror("msgget err");
            return -1;
        }
    }

    msg1.mtype = 1;
    while (1)
    {
        printf("请输入设备名称(LED/BEEP):");
        scanf("%s", msg1.nam);
        printf("请输入操作方式(0/1):");
        scanf("%d", &(msg1.op));
        msgsnd(msgid, &msg1, sizeof(msg1) - sizeof(long), 0);
    }

    return 0;
}

msg_recv.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>

struct msgComm
{
    long mtype;
    char nam[32];
    int op;
};

int main(int argc, char const *argv[])
{
    struct msgComm msg1;
    struct msgComm msg2;
    key_t key;
    pid_t pid;
    int msgid;
    if ((key = ftok("app", 't')) < 0)
    {
        perror("create key err!");
        exit(-1);
    }
    msgid = msgget(key, IPC_CREAT | IPC_EXCL | 0666);
    if (msgid < 0)
    {
        if (errno == EEXIST)
        {
            msgid = msgget(key, 0666);
        }
        else
        {
            perror("msgget err");
            return -1;
        }
    }

    while (1)
    {
        msgrcv(msgid, &msg2, sizeof(msg2) - sizeof(long), 0, 0);
        if (!strcmp(msg2.nam, "LED"))
        {
            if (msg2.op == 1)
                printf("open light\n");
            if (msg2.op == 0)
                printf("close light\n");
        }
        else if (!strcmp(msg2.nam, "BEEP"))
        {
            if (msg2.op == 1)
                printf("open beep\n");
            if (msg2.op == 0)
                printf("close beep\n");
        }
    }

    msgctl(msgid, IPC_RMID, NULL);
    return 0;
}

共享内存

1. 特点
1)共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,
  而不需要任何数据的拷贝
2)为了在多个进程间交换信息,内核专门留出了一块内存区,可以由
  需要访问的进程将其映射到自己的私有地址空间
3)进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。
4)由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等
2. 步骤
a. 创建key值
b. 创建或打开共享内存
c. 映射共享内存到用户空间
d. 撤销映射
e. 删除共享内存
3. 函数接口
int shmget(key_t key, size_t size, int shmflg);
功能:创建或打开共享内存
参数:
    key  键值
    size   共享内存的大小
    shmflg   IPC_CREAT|IPC_EXCL|0777
返回值:成功   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:删除共享内存

练习:通过共享内存实现进程通信。一个进程循环输入,另一个进程循环输出。

shm-s.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <errno.h>
#include <sys/msg.h>
#include <errno.h>
#include <sys/shm.h>

int main(int argc, char const *argv[])
{
    int shmid;
    key_t key;
    struct shmData
    {
        int flag;
        char data[32];
    };
    struct shmData *p = NULL;
    if ((key = ftok("./1-shm.c", 't')) < 0)
    {
        perror("create key err");
        exit(-1);
    }
    printf("key:%d\n", key);
    //创建共享内存
    shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid < 0)
    {
        if (errno == EEXIST)
            shmid = shmget(key, 128, 0666);
        else
        {
            perror("create shm err");
            exit(-1);
        }
    }
    //映射
    p = shmat(shmid, NULL, 0);
    printf("shmid:%d\n", shmid);
    if (p == (char *)-1)
    {
        perror("shmat err");
        exit(-1);
    }
    // p->flag=0;
    while (1)
    {
        if (p->flag == 1)
        {
            if (!strcmp(p->data, "quit"))
                break;
            printf("%s\n", p->data);
            p->flag=0;
        }
    }
    //取消映射
    shmdt(p);
    //删除共享内存
    shmctl(shmid, IPC_RMID, NULL);
    return 0;
}

shm-r.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <errno.h>
#include <sys/msg.h>
#include <errno.h>
#include <sys/shm.h>

int main(int argc, char const *argv[])
{
    int shmid;
    key_t key;
    struct shmData{
        int flag;
        char data[32];
    };
    struct shmData *p = NULL;
    if ((key = ftok("./1-shm.c", 't')) < 0)
    {
        perror("create key err");
        exit(-1);
    }
    printf("key:%d\n", key);
    //创建共享内存
    shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid < 0)
    {
        if (errno == EEXIST)
            shmid = shmget(key, 128, 0666);
        else
        {
            perror("create shm err");
            exit(-1);
        }
    }
    //映射
    p = shmat(shmid, NULL, 0);
    printf("shmid:%d\n", shmid);
    if (p == (char *)-1)
    {
        perror("shmat err");
        exit(-1);
    }
    //int pid = *p;
    p->flag=0;
    while (1)
    {
        scanf("%s", p->data);
        p->flag=1;
        if(!strcmp(p->data, "quit")) break;
    }

    return 0;
}

信号灯集

1. 特点:
信号灯(semaphore),也叫信号量。它是不同进程间或一个给定进程内部不同线程间同步的机制;System V的信号灯是一个或者多个信号灯的一个集合。其中的每一个都是单独的计数信号灯。而Posix信号灯指的是单个计数信号灯。

通过信号灯集实现共享内存的同步操作

2. 步骤:
1) 创建或打开信号灯集:semget
2) 初始化信号灯:semctl
3) PV操作:semop
4) 删除信号灯集:semctl
3. 命令:
ipcs -s:查看信号灯集
ipcrm -s semid:删除信号灯集
4. 函数接口
int semget(key_t key, int nsems, int semflg);
功能:创建/打开信号灯
参数:key:ftok产生的key值
    nsems:信号灯集中包含的信号灯数目
    semflg:信号灯集的访问权限,通常为IPC_CREAT |0666
返回值:成功:信号灯集ID
       失败:-1
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);
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);

作业:将信号灯集加入共享内存代码,实现同步操作。
如下功能:
两个进程实现通信,一个进程循环终端输入,另一个进程循环打印,当输入quit时结束

comm_snd.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/shm.h>
#include <sys/sem.h>

union semun {
    int val;
};

void sem_init(int semid, int num, int val);
void sem_op(int semid, int num, int op);

void sem_init(int semid, int num, int val)
{
    union semun sem;
    sem.val = val;
    semctl(semid, num, SETVAL, sem);
}
void sem_op(int semid, int num, int op)
{
    struct sembuf buf;
    buf.sem_num = num;
    buf.sem_op = op;
    buf.sem_flg = 0;
    semop(semid, &buf, 1);
}

int main(int argc, char const *argv[])
{
    key_t key;
    int shmid, semid;
    char *p;

    //创建共享内存
    if ((key = ftok("aaa", 't')) < 0)
    {
        perror("create key err!");
        exit(-1);
    }
    shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid < 0)
    {
        if (errno == EEXIST)
        {
            shmid = shmget(key, 128, 0666);
        }
        else
        {
            perror("shmid create err!");
            exit(-1);
        }
    }
    p = shmat(shmid, NULL, 0);
    if (p == (char *)-1)
    {
        perror("shmat err!");
        exit(-1);
    }

    //创建信号灯集
    semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666);
    if (semid < 0)
    {
        if (errno == EEXIST)
        {
            semid = semget(key, 1, 0666);
        }
        else
        {
            perror("create sem err!");
            exit(-1);
        }
    }
    else
    {
        sem_init(semid, 0, 0);
        sem_init(semid, 1, 1);
    }
    while (1)
    {
        // sem_op(semid,1,-1);
        scanf("%s", p);

        sem_op(semid, 0, 1);
        if (!strcmp(p, "quit"))
        {
            
            break;
        }
    }
    semctl(semid, 0, IPC_RMID);
    semctl(semid, 1, IPC_RMID);
    shmdt(p);
    shmctl(shmid, IPC_RMID, NULL);
    return 0;
}

comm_rcv.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/shm.h>
#include <sys/sem.h>

union semun {
    int val;
};

void sem_init(int semid, int num, int val);
void sem_op(int semid, int num, int op);

void sem_init(int semid, int num, int val)
{
    union semun sem;
    sem.val = val;
    semctl(semid, num, SETVAL, sem);
}
void sem_op(int semid, int num, int op)
{
    struct sembuf buf;
    buf.sem_num = num;
    buf.sem_op = op;
    buf.sem_flg = 0;
    semop(semid, &buf, 1);
}

int main(int argc, char const *argv[])
{
    key_t key;
    int shmid, semid;
    char *p;

    //创建共享内存
    if ((key = ftok("aaa", 't')) < 0)
    {
        perror("create key err!");
        exit(-1);
    }
    shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid < 0)
    {
        if (errno == EEXIST)
        {
            shmid = shmget(key, 128, 0666);
        }
        else
        {
            perror("shmid create err!");
            exit(-1);
        }
    }
    p = shmat(shmid, NULL, 0);
    if (p == (char *)-1)
    {
        perror("shmat err!");
        exit(-1);
    }

    //创建信号灯集
    semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666);
    if (semid < 0)
    {
        if (errno == EEXIST)
        {
            semid = semget(key, 1, 0666);
        }else{
            perror("create sem err!");
            exit(-1);
        }
    }else{
        sem_init(semid,0,0);
        sem_init(semid,1,1);
    }
    while (1)
    {
        sem_op(semid,0,-1);
        if(!strcmp(p,"quit")) {
            *p='a';
            break;
        }
        printf("%s\n",p);
        //sem_op(semid,1,1);
    }

    return 0;
}
  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

追meng赤子心

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值