IO进程基本操作

IO进程学习概述

  • 概述
  • 缓存机制
  • 标准IO
    • 打开文件
    • 读写文件
      • cat
    • 每次一行地读写文件
      • wc -l
    • 文件定位操作
  • 文件IO
    • 打开文件
    • 读写文件
      • cp命令的实现
    • 文件定位操作
  • 文件属性的获取
  • 目录操作
    • 分类
  • 进程
    • 进程状态切换图
    • 创建进程
    • 获取进程号
    • 结束进程
    • 回收进程资源
    • 创建守护进程
  • 线程
    • 创建线程
    • 线程退出
    • 线程回收&线程取消
    • 获取线程tid号
  • 线程同步
    • 信号量初始化&pv操作
  • 线程互斥
  • 死锁
    • 线程条件变量的控制
  • 进程间通信
    • 无名管道
      • 创建无名管道
    • 有名管道
      • 创建有名管道
  • 信号
    • 信号的响应方式
    • 信号处理接口
      • 司机与售票员信号处理
  • 共享内存

概述

IO进程主打的就是文件之间的通信,希望本文有你需要的内容。

缓存机制

  1. 全缓存:与文件相关
    刷新缓存的条件:程序正常退出时刷新,缓存区满刷新,强制刷新
  2. 行缓存:与终端相关
    刷新缓存的条件:\n刷新,程序正常退出时刷新,缓存区满刷新,强制刷新:fflush()
  3. 不缓存:没有缓存区(stderr)

标准IO

IO:input/output,针对于文件输入输出。
linux下文件类型:
b(块设备) c(字符设备) d(目录) -(普通文件) l(链接文件) s(套接字) p(管道)

  1. 概念:在C库中定义的一组专门用于输入输出的函数
  2. 特点:
    a. 标准IO通过缓冲机制减少系统调用的次数,提高效率
    b. 标准IO围绕流进行操作,流用FILE *描述;(FILE 就是一个结构体,用于存放操作文件的相关信息,它在stdio.h文件中定义;vi -t FILE,ctags,ctrl+]:代码追踪,ctrl+t:回退)
    c. 标准IO默认打开了三个流,stdin(标准输入)、stdout(标准输出)、stderr(标准出错)(FILE *stdin)

打开文件

FILE *fopen(const char *path, const char *mode)
功能:打开文件
参数:
    path:打开的文件
    mode:打开的方式
        r:只读,当文件不存在时报错,文件流定位到文件开头
        r+:可读可写,当文件不存在时报错,文件流定位到文件开头
        w:只写,文件不存在创建,存在清空
        w+:可读可写,文件不存在创建,存在清空
        a:追加(在末尾写),文件不存在创建,存在追加,文件流定位到文件末尾
        a+:读和追加,文件不存在创建,存在追加,文件流定位到文件末尾
                注:当a的方式打开文件时,写只能在末尾进行追加,定位操作
                是无法改变写的位置,但是可以改变读的位置
返回值: 成功:文件流
        失败:NULL,并且会设置错误码
FILE * freopen(const char *pathname,  const char *mode,  FILE* fp)
功能:将指定的文件流重定向到打开的文件中
参数:path:文件路径
	  mode:打开文件的方式(同fopen)
	  fp:文件流指针
返回值:成功:返回文件流指针
	   失败:NULL	

读写文件

int  fgetc(FILE * stream)
功能:从文件中读取一个字符
参数:stream:文件流
返回值:成功:读到的字符
        失败或读到文件末尾:EOF(-1)
int fputc(int c, FILE * stream)
功能:向文件中写入一个字符
参数:c:要写的字符
        stream:文件流
返回值:成功:写的字符的ASCII
       失败:EOF

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

由这两个函数,可以实现cat命令

cat

#include 
int main(int argc, const char *argv[])
{
	FILE *fp;
	int ch = 0;
	fp = fopen(argv[1], "r");
	if(fp == NULL)
	{
		perror("fopen err");
		return -1;
	}
	while((ch = fgetc(fp)) != -1)
	{
		printf("%c", ch);
	}
	fclose(fp);
	return 0;
}

每次一行地读写文件

char * fgets(char *s,  int size,  FILE * stream);
功能:从文件中每次读取一行字符串
参数:s:存放字符串的地址
         size:一次读取的字符个数
         stream:文件流
 返回值:成功:s的地址
        失败或读到文件末尾:NULL
特性:每次实际读取的字符个数为size-1个,会在末尾自动添加\0
int  fputs(const char *s,  FILE * stream);
功能:向文件中写字符串
参数:s:要写的内容
        stream:文件流
返回值:成功:非负整数
       失败:EOF

wc -l

fgets每次读取的数据特点是,读完这一行所有的内容,在字符串末尾添加一个\0,再读下一行的内容。但有时文件一行的字符个数大于读取的size个数,fgets也会读取size-1个,然后在末尾添加\0,这一行剩下的内容交给下一次读。这就导致计算文件行数,不能简单地每执行一次fgets行数就加1,还需要确定读的这一行倒数第二个字符是不是\n。

#include 
#include 
int main(int argc, const char *argv[])
{
	FILE *fp;
	char buf[32] = "";
	int n = 0;
	fp = fopen(argv[1], "r+");
	if(NULL == fp)
	{
		perror("fopen err");
		return -1;
	}
	while(fgets(buf, 32, fp) != NULL)
	{
		if(buf[strlen(buf)-1] == '\n')
			n++;
	}
	printf("%d %s\n", n, argv[1]);
	fclose(fp);
	return 0;
}

文件定位操作

void rewind(FILE *stream);
功能:将文件位置指针定位到起始位置
int fseek(FILE *stream, long offset, int whence);
功能:文件的定位操作
参数:stream:文件流
           offset:偏移量:正数表示向后文件尾部偏移
           					负数表示向文件开头偏移
           whence:相对位置:
                   SEEK_SET:相对于文件开头
                   SEEK_CUR:相对于文件当前位置
                   SEEK_END:相对于文件末尾
  返回值:成功:0
         失败:-1                 
long ftell(FILE *stream);
功能:获取当前的文件位置
参数:要检测的文件流
返回值:成功:当前的文件位置,出错:-1

文件IO

  1. 概念:由系统提供的一组用于输入输出的函数接口
  2. 特点:
    a. 没有缓冲机制,每次访问都要经过系统调用(系统向上提供的一组接口)
    b. 围绕文件描述符进行操作,文件描述符是非负整数(>=0),依次加1进行分配
    c. 文件IO默认打开三个描述符:0(标准输入)、1(标准输出)、2(标准出错)
    d. 可以操作-、b、c、p、s、l类型的文件

打开文件

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)默认为0002,可以用八进制的数修改
umask 0000

读写文件

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

cp命令的实现

#include 
#include 
#include 
#include 
#include 
int main(int argc, const char *argv[])
{
	int fd_src, fd_dest;
	char buf[32] = "";
	ssize_t s;
	if(argc != 3)
	{
		printf("Usage:%s  \n", argv[0]);
		return -1;
	}
	//1.打开文件(源文件、目标文件)
	fd_src = open(argv[1], O_RDONLY);
	if(fd_src < 0)
	{
		perror("open src file err");
		return -1;
	}
	fd_dest = open(argv[2], O_WRONLY|O_CREAT|O_TRUNC, 0666);
	if(fd_dest < 0)
	{
		perror("open dest file err");
		return -1;
	}
	//2.循环读源文件,写目标文件
	while((s = read(fd_src, buf, 32)) > 0)
	{
		write(fd_dest, buf, s);
	}
	//3.关闭文件
	close(fd_src);
	close(fd_dest);
	return 0;
}

文件定位操作

off_t lseek(int fd, off_t offset, int whence);
功能:设定文件的偏移位置	
参数:fd:文件描述符
	offset偏移量  				
		正数:向文件结尾位置移动
		负数:向文件开始位置
	whence  相对位置
		SEEK_SET   开始位置
		SEEK_CUR   当前位置
		SEEK_END   结尾位置
返回值:成功:文件的当前位置
	      失败:-1

文件属性的获取

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;  /* 最后状态改变时间 */
    };

目录操作

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:目录流

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

分类

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

静态库
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指定库的路径
		执行./a.out
动态库
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/22061/day3/dynamiclib

进程

进程是一个独立的可调度的任务
进程是动态的,是程序的一次执行过程,包含创建、调度、执行、消亡
特点:
系统会为每一个进程分配0-4g的虚拟空间,0-3g(用户空间)是每个进程所独有的,3g-4g(内核空间)是所有进程共有的。
CPU调度进程时会给进程分配时间片(几毫秒~十几毫秒),当时间片用完后,cpu再进行其他进程的调度,实现进程的轮转,从而实现多任务的操作
进程段:
Linux中的进程包含三个段:
“数据段”存放的是全局变量、常数以及动态数据分配的数据空间(如malloc函数取得的空间)等。
“正文段”存放的是程序中的代码
“堆栈段”存放的是函数的返回地址、函数的参数以及程序中的局部变量
进程分类:
交互进程:该类进程是由shell控制和运行的。交互进程既可以在前台运行,也可以在后台运行。该类进程经常与用户进行交互,需要等待用户的输入,当接收到用户的输入后,该类进程会立刻响应,典型的交互式进程有:shell命令进程、文本编辑器等
批处理进程:该类进程不属于某个终端,它被提交到一个队列中以便顺序执行。
守护进程:该类进程在后台运行。它一般在Linux启动时开始执行,系统关闭时才结束。
进程状态:
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
当进程已经终止运行,但还占用系统资源,要避免僵尸态的产生

进程状态切换图

进程创建后,进程进入就绪态,当CPU调度到此进程时进入运行态,当时间片用完时,此进程会进入就绪态,如果此进程正在执行一些IO操作(阻塞操作)会进入阻塞态,完成IO操作后又可进入就绪态,当进程运行结束即进入结束态。IO进程基本操作_第1张图片

创建进程

pid_t fork(void);
功能:创建子进程
返回值:
    成功:在父进程中:返回子进程的进程号 >0
                在子进程中:返回值为0
    失败:-1并设置errno
特性:
1)子进程几乎拷贝了父进程的全部内容。包括代码、数据
	系统数据段中的pc值、栈中的数据、父进程中打开的文件等;但它们的PID
	PPID是不同的。
2)父子进程有独立的地址空间,互不影响;当在相应的进程中改变全局变量
	静态变量,都互不影响。
3)若父进程先结束,子进程成为孤儿进程,被init进程收养 子进程变成后台进程
4)若子进程先结束,父进程如果没有及时回收,子进程变成僵尸进程
	要避免僵尸进程产生
5)fork函数的“写时拷贝”特性,当子进程要修改全局变量值时
	会复制父进程的地址空间,如果只是读取变量的值
	子进程和父进程访问的是同一个地址空间;
	vfork函数先执行子进行再执行父进程,父子进程共享数据段。

获取进程号

pid_t getpid(void);
功能:获取当前进程的进程号
pid_t getppid(void);
功能:获取当前进程的父进程号

结束进程

void exit(int status);
功能:结束进程,刷新缓存
void _exit(int status);
功能:结束进程,不刷新缓存
参数:status是一个整型的参数,可以利用这个参数传递进程结束时的状态。
	通常0表示正常结束;
	其他的数值表示出现了错误,进程非正常结束
	在实际编程时,子进程中调用exit函数,会将参数值(状态)传递给父进程
	父进程可以进行相应的处理。

回收进程资源

pid_t wait(int *status);
功能:回收子进程资源(阻塞)
参数:status:子进程退出状态
返回值:成功:回收的子进程的进程号
              失败:-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
当子进程退出时会给父进程发送SIGCHLD信号。

创建守护进程

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

线程

是一个轻量级的进程,为了提高系统的性能引入线程,Linux里同样用task_struct来描述一个线程。
线程和进程都参与统一的调度。
在同一个进程中创建的线程共享该进程的地址空间。
进程和线程区别
共性:都为操作系统提供了并发执行能力
不同点:
调度和资源:线程是系统调度的最小单位,进程是资源分配的最小单位
地址空间方面:同一个进程创建的多个线程共享进程的资源;进程的地址空间相互独立
通信方面:线程通信相对简单,只需要通过全局变量可以实现,但是需要考虑临界资源访问的问题;进程通信比较复杂,需要借助进程间的通信机制(借助3g-4g内核空间)
安全性方面:线程安全性差一些,当进程结束时会导致所有线程退出;进程相对安全
线程资源:
共享的资源:可执行的指令、静态数据、进程中打开的文件描述符、信号处理函数、当前工作目录、用户ID、用户组ID
私有的资源:线程ID (TID)、PC(程序计数器)和相关寄存器、堆栈、错误号 (errno)、信号掩码和优先级、执行状态和属性

创建线程

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
int pthread_detach(pthread_t thread);
功能:让线程分离,让线程结束时自动回收线程资源
参数:thread:线程ID
int pthread_cancel(pthread_t thread);
功能:取消线程

获取线程tid号

pthread_t pthread_self(void);
功能:获取当前线程的tid
返回值:tid值

线程同步

同步指的是多个任务(线程)按照约定的顺序相互配合完成一件事情
信号量:通过信号量实现同步操作;由信号量来决定线程是继续运行还是阻塞等待
信号量代表某一类资源,其值表示系统中该资源的数量,它是一个受保护的变量,只能通过三种操作来访问:初始化、P操作(申请资源)、V操作(释放资源)其值为非负整数。

信号量初始化&pv操作

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
注:此函数执行过程,先对信号量进行减1,当信号量的值大于等于0时
	表示有资源可以用,则继续执行;当信号量的值小于0时
	表示没有资源可以使用,函数阻塞
int  sem_post(sem_t *sem)   
功能:释放资源  V操作      
参数:sem:信号量对象
返回值:成功 0
	    失败 -1	
注:释放一次信号量的值加1,函数不阻塞

线程互斥

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

int  pthread_mutex_init(pthread_mutex_t  *mutex, pthread_mutexattr_t *attr)  
功能:初始化互斥锁  
参数:mutex:互斥锁
	    attr:  互斥锁属性  //  NULL表示缺省属性
返回值:成功 0
	      失败 -1
int  pthread_mutex_lock(pthread_mutex_t *mutex)   
功能:申请互斥锁     
参数:mutex:互斥锁
返回值:成功 0
	      失败 -1
注:和pthread_mutex_trylock区别:
	pthread_mutex_lock是阻塞的;
	pthread_mutex_trylock不阻塞,如果申请不到锁会立刻返回
int  pthread_mutex_unlock(pthread_mutex_t *mutex)   
功能:释放互斥锁     
参数:mutex:互斥锁
返回值:成功 0
	      失败 -1
int  pthread_mutex_destroy(pthread_mutex_t  *mutex)  
功能:销毁互斥锁     
参数:mutex:互斥锁

死锁

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

线程条件变量的控制

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函数先执行,再产生条件才可以
	此函数和pthread_cond_broadcast区别是:
	pthread_cond_broadcast函数相当于是广播,会将所有等待此条件的线程唤醒
pthread_cond_signal只能唤醒单个等待此条件的线程
int pthread_cond_destroy(pthread_cond_t *cond);
功能:将条件变量销毁
参数:cond:条件变量值
返回值:成功:0, 失败:非0

进程间通信

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

无名管道

特点
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错误)。

有名管道

1) 特点
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. 可读可写,如果管道中没有数据,读阻塞

信号

1.信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式
2.信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。
3.如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。

信号的响应方式

1)忽略信号:对信号不做任何处理,但是有两个信号不能忽略:
	即SIGKILL及SIGSTOP。
2)捕捉信号:定义信号处理函数,当信号发生时,执行相应的处理函数。
3)执行缺省操作:Linux对每种信号都规定了默认操作 

信号种类

SIGKILL:结束进程,不能被忽略不能被捕捉
SIGSTOP:结束进程,不能被忽略不能被捕捉
SIGCHLD:子进程退出时给父进程发的信号
SIGINT:结束进程,对应快捷方式ctrl+c
SIGTSTP:暂停信号,对应快捷方式ctrl+z
SIGQUIT:退出信号,对应快捷方式ctrl+\
SIGALRM:闹钟信号,alarm函数设置定时,当到设定的时间时
内核会向进程发送此信号结束进程。
SIGTERM:结束终端进程,kill 使用时不加数字默认是此信号

信号处理接口

int kill(pid_t pid, int sig);
功能:信号发送
参数:pid:指定进程
	 sig:要发送的信号
返回值:成功 0     失败 -1
int raise(int sig);
功能:进程向自己发送信号
参数:sig:信号
返回值:成功 0   失败 -1
unsigned int alarm(unsigned int seconds)
功能:在进程中设置一个定时器
参数:seconds:定时时间,单位为秒
返回值:如果调用此alarm()前,进程中已经设置了闹钟时间,则
返回上一个闹钟时间的剩余时间,否则返回0。
注意:一个进程只能有一个闹钟时间。如果在调用alarm时
	已设置过闹钟时间,则之前的闹钟时间被新值所代替
int pause(void);
功能:用于将调用进程挂起,直到收到信号为止。

#include 
 typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:信号处理函数
参数:signum:要处理的信号
          handler:信号处理方式
                    SIG_IGN:忽略信号
                    SIG_DFL:执行默认操作
                    handler:捕捉信号  void handler(int sig){} //函数名可以自定义
返回值:成功:设置之前的信号处理方式
              失败:-1

附上一个挺有意思的练习

司机与售票员信号处理

用信号的知识实现司机和售票员问题。
1)售票员捕捉SIGINT(代表开车)信号,向司机发送SIGUSR1信号,司机打印(let’s gogogo)
2)售票员捕捉SIGQUIT(代表停车)信号,向司机发送SIGUSR2信号,司机打印(stop the bus)
3)司机捕捉SIGTSTP(代表到达终点站)信号,向售票员发送SIGUSR1信号,售票员打印(please get off the bus)
4)司机等待售票员下车,之后司机再下车。
售票员:捕捉:SIGINT SIGQUIT SIGUSR1
忽略:SIGTSTP
司机:捕捉:SIGUSR1 SIGUSR2 SIGTSTP
忽略:SIGINT SIGQUIT

下面是我的实现过程,输出带点个人风格,不影响理解

#include
#include
#include
#include
#include
#include
pid_t pid;
void handler_conductor(int sig)
{
	if(SIGINT == sig)
		kill(getppid(),SIGUSR1);
	if(SIGQUIT == sig)
		kill(getppid(),SIGUSR2);
	if(SIGUSR1 == sig)
	{
		printf("\nConductor: Terminal is here,PLZ GET OFF ^.^\n");
		sleep(1);
		puts("Conductor: All passengers get off,now I'm gone");
		exit(0);
	}	
}
void handler_busdriver(int sig)
{
	if(SIGUSR1 == sig)
		printf("\nBusdriver: Hustle Hustle let's go!\n");
	if(SIGUSR2 == sig)
		printf("\nBusdriver: Bus Stopped,Get Off Quickly!\n");
	if(SIGTSTP == sig)
		{
			kill(pid,SIGUSR1);
			wait(NULL);
			printf("Busdriver: Conductor already get off,I'm gone too\n");
			exit(0);
		}
}
int main()
{
	if((pid = fork()) < 0)
	{
		perror("fork err: ");
		return -1;
	}
	if(pid == 0)
	{	
		//conductor ignore
		signal(SIGTSTP,SIG_IGN);
	//	while(1)
	//	{	
			signal(SIGINT,handler_conductor);
			signal(SIGQUIT,handler_conductor);
			signal(SIGUSR1,handler_conductor);

	//	}
	}
	else
	{		
		sleep(1);
		//busdriver ignore
		signal(SIGINT,SIG_IGN);
		signal(SIGQUIT,SIG_IGN);
	//	while(1)
	//	{
			signal(SIGTSTP,handler_busdriver);
			signal(SIGUSR1,handler_busdriver);
			signal(SIGUSR2,handler_busdriver);
	//	}
	}
	while(1)
		pause();
	return 0;
}

注释处换成pause后,功能一样,但是pause函数可以让程序对CPU的资源占用率极大的降低,这与pause的底层实现逻辑有关,哥们也不懂 qaq。

共享内存

1)共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,
	而不需要任何数据的拷贝
2)为了在多个进程间交换信息,内核专门留出了一块内存区,可以由
 	需要访问的进程将其映射到自己的私有地址空间
3)进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大
	 提高的效率。
4)由于多个进程共享一段内存,因此也需要依靠某种同步机制
	如互斥锁和信号量等

创建共享内存步骤
a. 创建key值
b. 创建或打开共享内存
c. 映射共享内存到用户空间
d. 撤销映射
e. 删除共享内存

key_t ftok(const char *pathname, int proj_id);
功能:产生一个独一无二的key值
参数:
	Pathname:已经存在的可访问文件的名字
	Proj_id:一个字符(因为只用低8位)
返回值:成功:key值
	      失败:-1
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);

系统命令:
ipcs -m : 查看共享内存
ipcrm -m shmid: 删除共享内存

你可能感兴趣的:(c语言,学习)