IO进程

scanf\printf:终端

IO:input/output,文件

标准IO

文件IO

文件属性获取:ls -l 文件类型 文件权限 链接数 用户名 组名 大小 时间 文件名

目录操作:ls

进程

进程:创建进程

线程:创建线程、同步和互斥

进程间通信:7->6种

一、标准IO

文件:7种文件类型

b(块设备) c(字符设备) d(目录) -(普通文件) l(链接文件) s(套接字) p(有名管道)

1、概念

1.1 定义

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

1.2 特点

1)有缓冲机制,通过缓冲机制减少系统调用的次数,提高效率

系统调用:内核向上提供的一组接口

2)围绕流进行操作,流用FILE *描述,FILE是一个结构体,描述的是文件的相关信息

typedef struct _IO_FILE FILE;

3)默认打开了三个流,stdin(标准输入)、stdout(标准输出)、stderr(标准错误)

struct _IO_FILE *stdin; –> FILE *stdin;

补充:ctags的使用(可以追代码)

vi -t FILE(typedef定义数据类型、宏定义、结构体等)

选择合适的编号

将光标定位在目标位置,ctrl+] :向下追代码

ctrl+t:回退

q:退出

1.3 缓存区

1)全缓存:和文件相关

刷新缓存区的条件:

1-程序正常结束

2-缓存区满刷新

3-fflush强制刷新

2)行缓存:和终端相关

刷新缓存区的条件:

1-程序正常结束

2-\n刷新缓存

3-缓存区满刷新

4-fflush强制刷新

3)不缓存:没有缓存区,标准错误

练习:计算行缓存中标准输出的缓存区大小

#include int main(int argc, char const *argv[]){// for(int i = 0; i _IO_buf_end - stdout->_IO_buf_base);return 0;}

2.函数接口

2.1 打开文件

FILE *fopen(const char *path, const char *mode);参数:path:打开文件mode:打开方式r:只读,流被定位到文件开头r+:可读可写,流被定位到文件开头w:只写,文件不存在创建,文件存在清空,流被定位到文件开头w+:可读可写,文件不存在创建,文件存在清空,流被定位到文件开头a:追加,文件不存在创建,存在追加,流被定位到文件末尾a+:可读可写,文件不存在创建,存在追加,开始进行读从头读,进行写流被定位到文件末尾返回值:成功:文件流 失败:NULL,并且设置errno(错误码)

2.2 读写文件

2.2.1 每次一个字符的读写

int fgetc(FILE *stream);功能:从文件中读一个字符参数:stream:文件流返回值:成功:读到字符的ASCII 失败或读到文件末尾:EOFint fputc(int c, FILE * stream)功能:向文件中写入一个字符参数:c:要写的字符 stream:文件流返回值:成功:写的字符的ASCII失败:EOF
#include int main(int argc, const char *argv[]){FILE *fp;fp=fopen("./i2.c","w");if(fp==NULL){perror("fopen err");return -1;}fputc('a',fp);//存放字符到指定文件中fputc(32,fp);fputc('a',stdout);//向终端(标准输出)一个函数return 0; }

练习:编程实现cat功能

思路:打开文件,循环读文件(fgetc),当读到文件末尾(fgetc函数返回值为EOF)循环结束,打印读到的内容

#include int main(int argc, const char *argv[]){FILE *p;//定义结构体指针int ch;if (argc!=2) //传入的参数不等于2个的时候进行提示{printf("usage:%s \n",argv[0]);return -1;}p=fopen(argv[1],"r"); //打开文件if (p==NULL){perror("open err");//printf("open error\n");return -1;}while((ch=fgetc(p))!=EOF) //循环打印出文件的全部内容{printf("%c",ch);}return 0;}

补充:

intfeof(FILE * stream);功能:判断文件有没有到结尾返回:到达文件末尾,返回非零值int ferror(FILE * stream);功能:检测文件有没有出错返回:文件出错,返回非零值void perror(const char *s);功能:根据errno打印错误信息参数:s:要打印的字符串
#include int main(int argc, char const *argv[]){FILE *fp;fp = fopen("./a.c", "w");if(fp == NULL){// printf("fopen err\n");perror("fopen err"); return -1;}printf("fopen success\n");int ch = fgetc(fp);printf("%c\n", ch);//fgetc返回值为EOF时,是因为读到末尾还是因为调用失败,可以用这两个函数区分if(feof(fp)) //判断是否读到文件末尾printf("eof\n");if(ferror(fp))//判断函数是否调用失败printf("error\n");return 0;}

补充vscode使用:

  1. 将work目录下的.vscode文件夹复制到自己目录(file)下
  2. 切换到file的上一级目录
  3. code file,用vscode打开目录,编写代码即可
  4. 快捷方式:

1)ctrl+shift+i:代码自动对齐

2)ctrl+/:注释代码

3)代码追踪:

ctrl+鼠标左键:向下追

alt+键盘左健:回退

2.2.2 每次一行的读写

char *fgets(char *s, int size, FILE *stream);功能:从文件中读取一串字符参数:s:存放读取的字符串的首地址 size:读取的大小 stream:文件流返回值:成功:读取的字符串的首地址失败或读到文件末尾:NULL特性:1.实际读取size-1个字符,在末尾添加\0 2.读到\n结束读取intfputs(const char *s,FILE *stream);功能:向文件中写字符串参数:s:要写的内容stream:文件流返回值:成功:非负整数 失败:EOF

练习:编程实现计算一个文件行数的功能(wc -l 文件名)。

要求:使用fgets实现

思路:打开文件,循环读文件,当读到文件末尾(fgets返回值为NULL)循环结束,在循环中判断字符串中是否有\n,如果是\n,则变量n++即可

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

2.3 关闭文件

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

sleep(1);

fprintf/sprintf();

time(); //计算时间,秒

localtime(); //秒转换成年月日时分秒

思路:打开文件,计算行数,循环向文件中写字符串,每隔一秒写入一行

注意:全缓存

#include #include #include #include int main(int argc, char const *argv[]){FILE *fp;char buf[32] = "";int n = 0;fp = fopen("test.txt", "a+");if(fp == NULL){perror("fopen err");return -1;}//判断行数while(fgets(buf, 32, fp) != NULL){if(buf[strlen(buf)-1] == '\n')n++;}time_t tm;struct tm *t;while(1){//计算时间// time(&tm);tm = time(NULL);t = localtime(&tm);fprintf(fp, "%d,%d-%d-%d %d-%d-%d\n", ++n,t->tm_year+1900,t->tm_mon+1,\t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec);fflush(NULL);sleep(1);}return 0;}

练习二:实现head功能

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

2.4 二进制读写

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);功能:从文件流读取多个元素参数:ptr :用来存放读取元素size :元素大小sizeof(数据类型)nmemb :读取对象的个数stream :要读取的文件返回值:成功:读取对象的个数读到文件尾或失败:0size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);功能:按对象写参数:同上返回值:成功:写的元素个数失败 :-1Fread和fwrite函数注意:1)两个函数的返回值为:读或写的对象数2)对于二进制数据我们更愿意一次读或写整个结构。
#include int main(int argc, char const *argv[]){FILE *fp;int arr[3] = {10, 20, 30}, num[3] = {0};fp = fopen("a.c", "w+");if(fp == NULL){perror("fopen err");return -1;}fwrite(arr, sizeof(int), 3, fp);//将文件位置移动到文件开头rewind(fp);fread(num, sizeof(int), 3, fp);for(int i = 0; i < 3; i++)printf("%d\n", num[i]);return 0;}

2.5 文件定位操作

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
#include int main(int argc, char const *argv[]){FILE *fp;char buf[32] = "";int n = 0;fp = fopen("test.txt", "r+");if(fp == NULL){perror("fopen err");return -1;}//将文件位置进行定位操作fseek(fp, 10, SEEK_SET); //相对文件开头向后偏移fputc('a', fp);fseek(fp, -5, SEEK_CUR); //相对文件当前向前偏移fputc('b', fp);long l = ftell(fp); //获取当前文件位置printf("%ld\n", l);//计算文件长度// fseek(fp, 0, SEEK_END);// l = ftell(fp);//rewind和fseek等价// rewind(fp); //fseek(fp, 0, SEEK_SET);return 0;}

2.6 重定向打开文件

FILE * freopen(const char *pathname,const char *mode,FILE* fp)功能:将指定的文件流重定向到打开的文件中参数:path:文件路径mode:打开文件的方式(同fopen)fp:文件流指针返回值:成功:返回文件流指针失败:NULL
#include int main(int argc, char const *argv[]){printf("hello\n");//将标准输出重定向到打开的文件freopen("test.txt", "r+", stdout);printf("world\n");//将标准输出重定向到终端freopen("/dev/tty", "r+", stdout);printf("nihao\n");return 0;}

练习:通过标准IO实现cp功能

#include #include #include #include #include int main(int argc, const char *argv[]){FILE *fd;FILE *fd1;char ch;fd=fopen(argv[1],"r");fd1=fopen(argv[2],"w+");if(fd==NULL){perror("open eror\n");return -1;}if(fd1==NULL){perror("open eror\n");return -1;}while((ch=fgetc(fd))!=EOF)fputc(ch,fd1);fclose(fd);fclose(fd1);return 0;} 

百度+man手册

time(time_t*tm)

函数调用:

参数:个数、类型和函数原型意义对应;当函数原型中参数是一级指针时,需要定义变量传地址

返回值:并不是所有函数都需要接收返回值;如果需要接收返回值,函数原型返回值类型是什么,在代码定义什么类型变量或指针去接收

二、文件IO

1、概念

1.1定义

在posix(可移植操作系统接口)中定义的一组输入输出的函数

系统调用:内核向上提供的一组接口

1.2特点

1)没有缓冲机制,每次IO操作都会引起系统调用

2)围绕文件描述符操作,非负整数(int),依次分配

3)默认打开三个文件描述符:0(标准输入)、1(标准输出)、2(标准错误)

4)可以操作除d以外其他任意类型文件

2、函数接口

2.1打开文件

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为文件权限掩码
#include #include #include #include #include int main(int argc, char const *argv[]){int fd;char buf[32] = "";// fd = open("./a.txt", O_RDWR|O_CREAT|O_TRUNC, 0666);fd = open("test.txt", O_RDWR);if (fd < 0){perror("open err");return -1;}printf("fd:%d\n", fd);//read返回值s表示实际读到的字符个数ssize_t s = read(fd, buf, 32);printf("%s\n", buf);printf("%d\n", s);write(fd, "nihao", 5);close(fd);return 0;}

比较:打开文件方式标准IO和文件IO对应关系

标准IO

文件IO

r

O_RDONLY

r+

O_RDWR

w

O_WRONLY|O_CREAT|O_TRUNC,0666

w+

O_RDWR|O_CREAT|O_TRUNC,0666

a

O_WRONLY|O_CREAT|O_APPEND,0666

a+

O_RDWR|O_CREAT|O_APPEND,0666

2.2读写文件

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功能。

cpsrcfilenewfile->./a.outsrcfilenewfile

思路:打开两个文件,循环读源文件写新文件,当读到源文件末尾时循环结束

diff文件名1文件名2:比较两个文件是否相等

#include #include #include #include #include int main(int argc, char const *argv[]){int fd_src, fd_new;char buf[32] = "";ssize_t s;fd_src = open(argv[1], O_RDONLY);fd_new = open(argv[2], O_WRONLY|O_CREAT|O_TRUNC, 0666);if(fd_src < 0 || fd_new < 0){perror("open err");return -1;}while(1){s = read(fd_src, buf, 32);if(s == 0)break;write(fd_new, buf, s);}close(fd_src);close(fd_new);return 0;}

2.3关闭文件

int close(int fd);参数:fd:文件描述符

2.4文件定位

off_t lseek(int fd, off_t offset, int whence);功能:设定文件的偏移位置参数:fd:文件描述符offset偏移量正数:向文件结尾位置移动负数:向文件开始位置whence相对位置SEEK_SET 开始位置SEEK_CUR 当前位置SEEK_END 结尾位置返回值:成功:文件的当前位置
#include #include #include #include #include int main(int argc, char const *argv[]){int fd;char buf[32] = "";fd = open("test.txt", O_RDWR);if (fd < 0){perror("open err");return -1;}printf("fd:%d\n", fd);lseek(fd, 10, SEEK_SET);write(fd, "a", 1);off_t off = lseek(fd, 0, SEEK_CUR);printf("%ld\n", off);close(fd);return 0;}

练习二:实现如下功能

1–打开一个文件,不存在创建,存在清零

2–向文件中第10位置处写一个字符,

3–在文件此时的位置,后20个位置处,写一行字符串hello进去

4–求文件的长度。

#include #include #include #include #include int main(int argc, const char *argv[]){int fd;char buf[32]="";fd=open(argv[1],O_RDWR|O_CREAT|O_TRUNC,0666);if(fd<0){perror("open eror\n");return -1;}lseek(fd,9,SEEK_SET);write(fd,"i",1);lseek(fd,19,SEEK_CUR);write(fd,"hello",5);off_t off=lseek(fd,0,SEEK_END);printf("%ld\n",off);close(fd);return 0;} 

2.5 标准IO与文件IO的比较

标准IO

文件IO

定义

在C库中定义输入输出的函数

在posix中定义的输入输出的函数

特点

有缓冲机制

围绕流操作,FILE*

默认打开三个流:stdin/stdout/stderr

只能操作普通文件

没有缓冲机制

围绕文件描述符操作,int非负整数

默认打开三个文件描述符:0/1/2

除d外其他任意类型文件

函数接口

打开文件:fopen/freopen

读写文件:fgetc/fputc、fgets/fputs、fread/fwrite

关闭文件:fclose

文件定位:rewind、fseek、ftell

打开文件:open

读写文件:read、write

关闭文件:close

文件定位:lseek

2.6文件属性获取

int stat(const char *path, struct stat *buf);功能:获取文件属性参数:path:文件路径名 buf:保存文件属性信息的结构体返回值:成功:0失败:-1struct stat {ino_t st_ino; /* inode号 */mode_tst_mode;/* 文件类型和权限 */nlink_t st_nlink; /* 硬链接数 */uid_t st_uid; /* 用户ID */gid_t st_gid; /* 组ID */off_t st_size;/* 大小 */time_tst_atime; /* 最后访问时间 */time_tst_mtime; /* 最后修改时间 */time_tst_ctime;/* 最后状态改变时间 */};
#include #include #include #include int main(int argc, char const *argv[]){struct stat st;if (stat("./test.c", &st) < 0){perror("stat err");return -1;}printf("%lu\n", st.st_ino);printf("%d\n", st.st_nlink);//判断文件类型printf("%#o\n", st.st_mode);if((st.st_mode & S_IFMT) == S_IFREG)putchar('-');else if((st.st_mode & S_IFMT) == S_IFDIR)putchar('d');//判断文件权限if(st.st_mode & S_IRUSR)putchar('r');else putchar('-'); if(st.st_mode & S_IWUSR)putchar('w');else putchar('-');//获取用户名和组名//getpwuid();//将用户ID转换成用户名//getgrgid();//将组ID转换成组名//时间//st_mtime:最后一次修改时间,s//localtime();return 0;}

2.7目录操作

围绕目录流进行操作,DIR*

DIR *opendir(const char *name);功能:获得目录流参数:要打开的目录返回值:成功:目录流 失败:NULLstruct dirent *readdir(DIR *dirp);功能:读目录参数:要读的目录流返回值:成功:读到的信息失败或读到目录结尾:NULL返回值为结构体,该结构体成员为描述该目录下的文件信息struct dirent {ino_t d_ino; /* 索引节点号*/off_t d_off; /*在目录文件中的偏移*/unsigned short d_reclen;/* 文件名长度*/unsigned chard_type;/* 文件类型 */chard_name[256];/* 文件名 */};int closedir(DIR *dirp);功能:关闭目录参数:dirp:目录流
#include #include #include int main(int argc, char const *argv[]){DIR *dir;struct dirent *d;dir = opendir(".");if (dir == NULL){perror("opendir err");return -1;}while((d = readdir(dir)) != NULL){if(d->d_name[0] != '.') printf("%s\n", d->d_name);}// d = readdir(dir);// printf("%s\n", d->d_name);// d = readdir(dir);// printf("%s\n", d->d_name);closedir(dir);return 0;}

练习:编程实现ls功能

三、库

1、库的定义

当使用别人的函数时除了包含头文件以外还要有库

库:就是把一些常用函数的目标文件打包在一起,提供相应函数的接口,便于程序员使用;本质上来说库是一种可执行代码的二进制形式

由于windows和linux的本质不同,因此二者库的二进制是不兼容的

2、库的分类

静态库和动态库,本质区别是代码被载入时刻不同。

1)静态库在程序编译时会被连接到目标代码中。

优点:程序运行时将不再需要该静态库;运行时无需加载库,运行速度更快

缺点:静态库中的代码复制到了程序中,因此体积较大;

静态库升级后,程序需要重新编译链接

2)动态库是在程序运行时才被载入代码中。

优点:程序在执行时加载动态库,代码体积小;

程序升级更简单;

不同应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。

缺点:运行时还需要动态库的存在,移植性较差

3、库的制作

3.1静态库的制作

1-将源文件编译生成目标文件

gcc-cadd.c-oadd.o

2-创建静态库用ar命令,它将很多.o转换成.a

arcrslibmyadd.aadd.o

静态库文件名的命名规范是以lib为前缀,紧接着跟静态库名,扩展名为.a

3-测试使用静态库:

gccmain.c-L.-lmyadd//-L指定库的路径-l指定库名

执行./a.out

3.2动态库的制作

1-我们用gcc来创建共享库

gcc-fPIC-chello.c-ohello.o

-fPIC创建与地址无关的编译程序

gcc-shared-olibmyhello.sohello.o

2-测试动态库使用

gccmain.c-L.-lmyhello

可以正常编译通过,但是运行时报错./a.out:errorwhileloadingsharedlibraries:libmyadd.so:cannotopensharedobjectfile:Nosuchfileordirectory

原因:当加载动态库时,系统会默认从/lib或/usr/lib路径下查找库文件

解决方法(有三种):

(1)把库拷贝到/usr/lib和/lib目录下。(此方法编译时不需要指定库的路径)

(2)在LD_LIBRARY_PATH环境变量中加上库所在路径。

exportLD_LIBRARY_PATH=$LD_LIBRARY_PATH:.

(终端关闭,环境变量就没在了)

(3)添加/etc/ld.so.conf.d/*.conf文件。把库所在的路径加到文件末尾,并执行ldconfig刷新

sudovixx.conf

添加动态库存在的路径,如:

/home/hq/teach/22092/day3/dynamic

补充:

头文件:

放在当前目录:#include”xx.h”,从当前路径下查找文件,如果没有再从系统目录下查找

放在系统目录:#include,默认从系统路径下查找,系统路径:/usr/include

放在其他目录:#include”xx.h”,在用gcc编译代码时加选项-I(i的大写)指定头文件的路径

gccmain.c-I头文件路径

库文件:动态库放在系统目录

系统路径:/usr/lib和/lib

gcc编译时需要添加选项

-L路径:指定库的路径

-l库名:(小写的L)指定库名

-I路径:(大写的i)指定头文件的路径

四、进程

1、概念:

1.1程序和进程区别:

程序:编译好的可执行文件

存放在磁盘上的指令和数据的有序集合(文件)

程序是静态的,没有任何执行的概念

进程:一个独立的可调度的任务

执行一个程序所分配的资源的总称

进程是程序的一次执行过程

进程是动态的,包括创建、调度、执行和消亡

1.2特点

  1. 系统会为每个进程分配0-4g的虚拟空间,其中0-3g是用户空间,每个进程独有;3g-4g是内核空间,所有进程共享
  2. 轮转调度:时间片,系统为每个进程分配时间片(几毫秒~几十毫秒),当一个进程时间片用完时,CPU调度另一个进程,从而实现进程调度的切换

1.3.进程段:

Linux中的进程包含三个段:

“数据段”存放的是全局变量、常数以及动态数据分配的数据空间(如malloc函数取得的空间)等。

“正文段”存放的是程序中的代码

“堆栈段”存放的是函数的返回地址、函数的参数以及程序中的局部变量

1.4.进程分类:

交互进程:该类进程是由shell控制和运行的。交互进程既可以在前台运行,也可以在后台运行。该类进程经常与用户进行交互,需要等待用户的输入,当接收到用户的输入后,该类进程会立刻响应,典型的交互式进程有:shell命令进程、文本编辑器等

批处理进程:该类进程不属于某个终端,它被提交到一个队列中以便顺序执行。

守护进程:该类进程在后台运行。它一般在Linux启动时开始执行,系统关闭时才结束

1.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当进程已经终止运行,但还占用系统资源,要避免僵尸态的产生

<高优先级

N低优先级

s会话组组长

l多线程

+前台进程

1.6.进程状态切换图

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

2、函数:

2.1创建进程

pid_t fork(void);功能:创建子进程返回值:成功:在父进程中:返回子进程的进程号 >0 在子进程中:返回值为0失败:-1并设置errno
#include #include int main(int argc, char const *argv[]){int num = 10;pid_t id;id = fork(); //创建子进程if(id < 0){perror("fork err");return -1;}else if(id == 0){//in the childprintf("in the child\n");}else{// int s;// wait(&s); //回收子进程资源,阻塞函数// printf("%d\n", s);wait(NULL);//in the parentprintf("in the parent\n");while(1);}// while(1);return 2;}

特点

1)子进程几乎拷贝了父进程的全部内容。包括代码、数据、系统数据段中的pc值、栈中的数据、父进程中打开的文件等;但它们的PID、PPID是不同的。

2)父子进程有独立的地址空间,互不影响;当在相应的进程中改变全局变量、静态变量,都互不影响。

3)若父进程先结束,子进程成为孤儿进程,被init进程收养,子进程变成后台进程。

4)若子进程先结束,父进程如果没有及时回收,子进程变成僵尸进程(要避免僵尸进程产生)

2.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
#include #include #include #include int main(int argc, char const *argv[]){pid_t id;id = fork(); //创建子进程if (id < 0){perror("fork err");return -1;}else if (id == 0){sleep(1);//in the childprintf("in the child\n");}else{//IO模型:阻塞IO\非阻塞IO// waitpid(-1, NULL, 0); //wait(NULL);//轮询(循环)while(1){if(waitpid(id, NULL, WNOHANG)!=0) //WNOHANG:表示非阻塞break;}//in the parentprintf("in the parent\n");while (1);}return 0;}

2.3 结束进程

void exit(int status);功能:结束进程,刷新缓存void _exit(int status);功能:结束进程,不刷新缓存参数:status是一个整型的参数,可以利用这个参数传递进程结束时的状态。通常0表示正常结束;其他的数值表示出现了错误,进程非正常结束

exit和return区别:

exit:不管在子函数还是主函数,都可以结束进程

return:当子函数中有return时返回到函数调用位置,并不结束进程

#include #include #include int fun(){printf("in fun\n");exit(0);// return 0;}int main(int argc, char const *argv[]){printf("hello\n");// fun();// printf("world\n");// return 0;// exit(0); //结束进程,刷新缓存// _exit(0); //结束进程,不刷新缓存区// execl("/bin/ls", "ls", "-l", NULL);// char *arg[] = {"ls", "-l", NULL};// execv("/bin/ls", arg);while(1);return 0;}

2.4 获取进程号

pid_t getpid(void);功能:获取当前进程的进程号pid_t getppid(void);功能:获取当前进程的父进程号
#include #include #include int main(int argc, char const *argv[]){pid_t id;if ((id = fork()) < 0){perror("fork err");return -1;}else if (id == 0){printf("in child, pid:%d ppid:%d\n", getpid(), getppid());}else{printf("in parent, pid:%d ppid:%d\n", id, getpid());}return 0;}

exec函数-了解

system(“ls-l”);

#include #include #include int fun(){printf("in fun\n");exit(0);// return 0;}int main(int argc, char const *argv[]){printf("hello\n");// fun();// printf("world\n");// return 0;// exit(0); //结束进程,刷新缓存// _exit(0); //结束进程,不刷新缓存区// execl("/bin/ls", "ls", "-l", NULL);// char *arg[] = {"ls", "-l", NULL};// execv("/bin/ls", arg);while(1);return 0;}

2.5 守护进程

1、 特点:

守护进程是后台进程,不依赖于控制终端;

生命周期比较长,从运行时开启,系统关闭时结束;

它是脱离控制终端且周期执行的进程。

2.步骤:

1)创建子进程,父进程退出

让子进程变成孤儿进程,成为后台进程;fork()

2)在子进程中创建新会话

让子进程成为会话组组长,为了让子进程完全脱离终端;setsid()

3)改变进程运行路径为根目录

原因进程运行的路径不能被卸载;chdir(“/”)

4)重设文件权限掩码

目的:增大进程创建文件时权限,提高灵活性;umask(0)

5)关闭文件描述符

将不需要的文件关闭;close()

#include #include #include #include #include #include int main(int argc, char const *argv[]){pid_t id;if ((id = fork()) < 0){perror("fork err");return -1;}else if (id == 0){//在子进程中创建新会话setsid();//修改进程运行路径为根目录chdir("/");//修改文件权限掩码umask(0);//关闭文件描述符for (int i = 0; i < 2; i++)close(i);int fd;fd = open("/tmp/info.log", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd < 0){perror("open err");return -1;}while (1){write(fd, "hello", 5);sleep(1);}}else{exit(0);}return 0;}

五、线程

1.概念

是一个轻量级的进程,为了提高系统的性能引入线程

Linux里同样用task_struct来描述一个线程。

线程和进程都参与统一的调度。

在同一个进程中创建的线程共享该进程的地址空间。

2.线程和进程区别

共性:都为操作系统提供了并发执行能力

不同点:

调度和资源:线程是系统调度的最小单位,进程是资源分配的最小单位

地址空间方面:同一个进程创建的多个线程共享进程的资源;进程的地址空间相互独立

通信方面:线程通信相对简单,只需要通过全局变量可以实现,但是需要考虑临界资源保护的问题;进程通信比较复杂,需要借助进程间的通信机制(借助3g-4g内核空间)

安全性方面:线程安全性差一些,当进程结束时会导致所有线程退出;进程相对安全

3.线程函数

3.1创建线程

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);功能:创建线程参数:thread:线程标识 attr:线程属性, NULL:代表设置默认属性 start_routine:函数名:代表线程函数 arg:用来给前面函数传参返回值:成功:0 失败:错误码
#include #include #include #include char buf[32] = "";//线程函数void *handler(void *arg){sleep(1);printf("in the thread\n");printf("num:%d\n", *((int *)arg));printf("buf:%s\n", buf);pthread_exit(NULL); //结束线程}int main(int argc, char const *argv[]){pthread_t tid;int num = 100;if(pthread_create(&tid, NULL, handler, &num) != 0){perror("create thread err");return -1;}printf("in the main\n");printf("main tid:%lu\n", pthread_self());strcpy(buf, "hello");//线程回收,阻塞函数,等待子线程结束,回收线程资源pthread_join(tid, NULL);return 0;}

3.2结束线程

intpthread_exit(void *value_ptr) 功能:用于退出线程的执行参数:value_ptr:线程退出时返回的值返回值:成功 : 0失败:errno

3.3回收线程

intpthread_join(pthread_t thread,void **value_ptr) 功能:用于等待一个指定的线程结束,阻塞函数参数:thread:创建的线程对象value_ptr:指针*value_ptr指向线程返回的参数返回值:成功 : 0失败:errno

3.4获取线程号

pthread_t pthread_self(void);功能:获取线程号返回值:线程ID

3.5线程分离

int pthread_detach(pthread_t thread);功能:让线程分离,线程退出让系统自动回收线程资源

练习:通过线程实现通信。

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

要求:先输入后输出

提示:标志位intflag=0;

#include #include #include char buf[32] = "";int flag = 0;void *print(void *arg){while (1){if (flag == 1){if (strcmp(buf, "quit") == 0)break;printf("buf:%s\n", buf);flag = 0;}}}int main(int argc, char const *argv[]){pthread_t tid;if (pthread_create(&tid, NULL, print, NULL) != 0){perror("create thread err");return -1;}while (1){scanf("%s", buf);flag = 1;if (strcmp(buf, "quit") == 0)break;}pthread_join(tid, NULL);return 0;}

4、线程同步

4.1概念

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

4.2同步机制

通过信号量实现线程间同步。

信号量:由信号量来决定线程是继续运行还是阻塞等待,信号量代表某一类资源,其值表示系统中该资源的数量

信号量是一个受保护的变量,只能通过三种操作来访问:初始化、P操作(申请资源)、V操作(释放资源)

信号量的值为非负整数

4.3特性

P操作:

当信号量的值大于0时,可以申请到资源,申请资源后信号量的值减1

当信号量的值等于0时,申请不到资源,函数阻塞

V操作:

不阻塞,执行到释放操作,信号量的值加1

4.4函数

intsem_init(sem_t *sem,int pshared,unsigned int value)功能:初始化信号量 参数:sem:初始化的信号量对象pshared:信号量共享的范围(0: 线程间使用 非0:1进程间使用)value:信号量初值返回值:成功 0失败 -1intsem_wait(sem_t *sem)功能:申请资源P操作 参数:sem:信号量对象返回值:成功 0失败 -1注:此函数执行过程,当信号量的值大于0时,表示有资源可以用,则继续执行,同时对信号量减1;当信号量的值等于0时,表示没有资源可以使用,函数阻塞intsem_post(sem_t *sem) 功能:释放资源V操作参数:sem:信号量对象返回值:成功 0失败 -1注:释放一次信号量的值加1,函数不阻塞

#include #include #include #include char buf[32] = "";sem_t sem, sem1;void *print(void *arg){while (1){//P操作:申请资源,-1sem_wait(&sem);if (strcmp(buf, "quit") == 0)break;printf("buf:%s\n", buf);sem_post(&sem1);}}int main(int argc, char const *argv[]){pthread_t tid;if (pthread_create(&tid, NULL, print, NULL) != 0){perror("create thread err");return -1;}if(sem_init(&sem, 0, 0) < 0){perror("sem init err");return -1;}if(sem_init(&sem1, 0, 1) < 0){perror("sem init err");return -1;}while (1){//申请sem_wait(&sem1);printf("请输入:");scanf("%s", buf);//V操作:释放资源, +1sem_post(&sem);if (strcmp(buf, "quit") == 0)break;}pthread_join(tid, NULL);return 0;}

5.线程互斥

5.1概念

临界资源:一次仅允许一个进程所使用的资源

临界区:指的是一个访问共享资源的程序片段

互斥:多个线程在访问临界资源时,同一时间只能一个线程访问

互斥锁:通过互斥锁可以实现互斥机制,主要用来保护临界资源,每个临界资源都由一个互斥锁来保护,线程必须先获得互斥锁才能访问临界资源,访问完资源后释放该锁。如果无法获得锁,线程会阻塞直到获得锁为止。

5.2函数接口

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

案例:全局数组inta[10]={};

t1:循环倒置数组中元素

t2:循环打印数组中元素

#include #include #include int a[10] = {0,1,2,3,4,5,6,7,8,9};pthread_mutex_t lock;void *print_handler(void *arg){while(1){pthread_mutex_lock(&lock);for(int i = 0; i < 10; i++)printf("%d", a[i]);printf("\n");pthread_mutex_unlock(&lock);sleep(1);}}void *swap_handler(void *arg){int t;while(1){pthread_mutex_lock(&lock);for(int i = 0; i < 5; i++){t = a[i];a[i] = a[9-i];a[9-i] = t;}pthread_mutex_unlock(&lock);}}int main(int argc, char const *argv[]){pthread_t t1, t2;pthread_create(&t1, NULL, print_handler, NULL);pthread_create(&t2, NULL, swap_handler, NULL);if(pthread_mutex_init(&lock, NULL) != 0){perror("mutex init err");return -1;}pthread_join(t1, NULL);pthread_join(t2, NULL);return 0;}

5.3死锁

是指两个或两个以上的进程/线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去

死锁产生的四个必要条件

1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用

2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。

3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。

4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。

注意:当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。

6.条件变量

和互斥锁搭配使用实现同步机制

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 失败:非0int 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

pthread_mtex_init(&lock,NULL);

案例:存钱和取钱的例子

主线程循环存钱,子线程循环取钱,每次取100直到余额为0,再进行存钱;

#include #include int money = 0;pthread_mutex_t lock;pthread_cond_t cond, cond1;//取钱void *getMoney(void *arg){while(1){pthread_mutex_lock(&lock);if(money < 100)pthread_cond_wait(&cond, &lock);money -= 100;printf("money:%d\n", money);if(money = 100)pthread_cond_wait(&cond1, &lock);scanf("%d", &money); //50if(money >= 100)pthread_cond_signal(&cond); //产生条件pthread_mutex_unlock(&lock);}pthread_join(t, NULL);return 0;}

六、进程间通信

7种

6种

传统的进程间通信方式:

无名管道、有名管道、信号

systemVIPC对象:

共享内存、消息队列、信号灯集

BSD:

套接字(socket)

1.无名管道

1.1特点

a.只能用于具有亲缘关系的进程之间的通信

b.半双工的通信模式,具有固定的读端和写端

c.管道可以看成是一种特殊的文件,对于它的读写可以使用文件IO如read、write函数.

d.管道是基于文件描述符的通信方式。当一个管道建立时,它会创建两个文件描述符

fd[0]和fd[1]。其中fd[0]固定用于读管道,而fd[1]固定用于写管道。

1.2函数接口

int pipe(int fd[2])功能:创建无名管道参数:文件描述符 fd[0]:读端fd[1]:写端返回值:成功 0失败 -1
#include #include int main(int argc, char const *argv[]){int fd[2] = {0};char buf[65536] = "";//创建无名管道if(pipe(fd) < 0){perror("pipe err");return -1;}printf("%d %d\n", fd[0], fd[1]);//对管道进行读写// write(fd[1], "hello", 5);// read(fd[0], buf, 32);// printf("%s\n", buf);//1.当管道中没有数据时,读阻塞// close(fd[1]); //关闭写端// ssize_t s = read(fd[0], buf, 32);// printf("%s %d\n", buf, s);//2.当写满管道时,写阻塞,当至少读出4k空间时,才可以继续写//管道大小:64k// write(fd[1], buf, 65536);// read(fd[0], buf, 4096);// printf("befor\n");// write(fd[1], "a", 1);// printf("after\n");//3.当读端关闭,写管道时,会导致管道破裂close(fd[0]);write(fd[1], "hello", 5); //SIGPIPEprintf("after\n");return 0;}

1.3注意事项

a. 当管道中无数据时,读操作会阻塞;

管道中无数据时,将写端关闭,读操作会立即返回

b.管道中装满(管道大小64K)数据写阻塞,一旦有4k空间,写继续

c.只有在管道的读端存在时,向管道中写入数据才有意义。否则,会导致管道破裂,向管道中写入数据的进程将收到内核传来的SIGPIPE信号(通常Brokenpipe错误)。

练习:父子进程通信。

父进程循环从终端输入字符串,子进程将字符串循环输出,当输入quit时,程序退出。

#include #include #include #include #include #include int main(int argc, char const *argv[]){char buf[32] = "";pid_t id;int fd[2] = {0};if(pipe(fd) < 0){perror("pipe err");return -1;}if ((id = fork()) < 0){perror("fork err");return -1;}else if (id == 0){while(1){char buf[32] = "";read(fd[0], buf, 32);if(strcmp(buf, "quit") == 0)break;printf("buf:%s\n", buf);}exit(0);}else{while(1){char buf[32] = "";// scanf("%s", buf);fgets(buf, 32, stdin);write(fd[1], buf, strlen(buf)+1);if(strcmp(buf, "quit") == 0)break;}wait(NULL);}return 0;}

2.有名管道

2.1特点

a.有名管道可以使互不相关的两个进程互相通信。

b.有名管道可以通过路径名来指出,并且在文件系统中可见,但内容存放在内存中。

c.进程通过文件IO来操作有名管道

d.有名管道遵循先进先出规则

e.不支持如lseek()操作

2.2函数接口

int mkfifo(const char *filename,mode_t mode);功能:创健有名管道参数:filename:有名管道文件名 mode:权限返回值:成功:0 失败:-1,并设置errno号注意对错误的处理方式:如果错误是file exist时,注意加判断,如:if(errno == EEXIST)

#include #include #include #include #include #include #include int main(int argc, char const *argv[]){int fd;//创建管道if (mkfifo("./fifo", 0666) < 0){if (errno == EEXIST)printf("file exists\n");else{perror("mkfifo err");return -1;}}printf("mkfifo ok\n");//打开管道fd = open("./fifo", O_RDWR);if(fd < 0){perror("open err");return -1;}char buf[32] = "hello";char data[32] = "";write(fd, buf, strlen(buf));read(fd, data, 32);printf("%s\n", data);return 0;}

2.3注意事项

a.只写方式,写阻塞,一直到另一个进程把读打开

b.只读方式,读阻塞,一直到另一个进程把写打开

c.可读可写,如果管道中没有数据,读阻塞

练习:实现两个不相关进程间通信。

read.c:从终端读取数据

write.c:向终端输出数据

当输入quit时结束。

#include #include #include #include #include #include #include int main(int argc, char const *argv[]){int fd;char buf[32] = "";//创建管道if (mkfifo("./fifo", 0666) < 0){if (errno == EEXIST)printf("file exists\n");else{perror("mkfifo err");return -1;}}printf("mkfifo ok\n");//打开管道fd = open("./fifo", O_WRONLY);if(fd < 0){perror("open err");return -1;}while(1){scanf("%s", buf);write(fd, buf, strlen(buf)+1);if(!strcmp(buf, "quit"))break;}return 0;}
#include #include #include #include #include #include #include int main(int argc, char const *argv[]){int fd;char buf[32] = "";//创建管道if (mkfifo("./fifo", 0666) < 0){if (errno == EEXIST)printf("file exists\n");else{perror("mkfifo err");return -1;}}printf("mkfifo ok\n");//打开管道fd = open("./fifo", O_RDONLY);if(fd < 0){perror("open err");return -1;}while(1){read(fd, buf, 32);if(!strcmp(buf, "quit"))break;printf("buf:%s\n", buf);}return 0;}

2.4有名管道和无名管道区别

无名管道

有名管道

特点

只能在亲缘关系进程间使用

半双工通信方式

有固定的读端和写端,fd[0]:读,fd[1]:写端

通过文件IO进行操作

步骤:创建管道、读写操作

不相关的任意进程间使用

在路径中有管道文件,实际数据存在内核空间

通过文件IO进行操作

步骤:创建管道、打开管道、读写操作

函数

pipe

mkfifo

读写特性

当管道中没有数据,读阻塞

当写满管道时,写阻塞

3.信号

3.1概念

1)信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式

2)信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。

3)如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。

3.2.信号的响应方式

1)忽略信号:对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及SIGSTOP

2)捕捉信号:定义信号处理函数,当信号发生时,执行相应的处理函数。

3)执行缺省操作:Linux对每种信号都规定了默认操作

3.3.信号种类

SIGKILL:结束进程,不能被忽略不能被捕捉

SIGSTOP:结束进程,不能被忽略不能被捕捉

SIGCHLD:子进程状态改变时给父进程发的信号,不会结束进程

SIGINT:结束进程,对应快捷方式ctrl+c

SIGTSTP:暂停信号,对应快捷方式ctrl+z

SIGQUIT:退出信号,对应快捷方式ctrl+\

SIGALRM:闹钟信号,alarm函数设置定时,当到设定的时间时,内核会向进程发送此信号结束进程。

SIGTERM:结束终端进程,kill使用时不加数字默认是此信号

3.4函数接口

int kill(pid_t pid, int sig);功能:信号发送参数:pid:指定进程 sig:要发送的信号返回值:成功 0失败 -1int raise(int sig);功能:进程向自己发送信号参数:sig:信号返回值:成功 0失败 -1int 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
#include #include #include #include int main(int argc, char const *argv[]){// kill(getpid(), SIGKILL);raise(SIGKILL); //给自己发信号// while (1)// ;while (1)pause(); //将当前进程挂起(阻塞),直到收到信号结束return 0;}

typedefvoid(*sighandler_t)(int);//typedefunsignedintINT;

typedefvoid(*)(int)sighandler_t;

sighandler_tsignal(intsignum,void(*handler)(int));

voidhandler(intsig)

{

if(sig==SIGINT)

printf(“xxx\n”);

elseif(sig==SIGQUIT)

}

signal(SIGINT,handler);

signal(SIGQUIT,handler)

#include #include #include void handler(int sig){printf("ctrl+c\n");}int main(int argc, char const *argv[]){ // signal(SIGINT, SIG_IGN);//忽略信号// signal(SIGINT, SIG_DFL); //执行默认操作signal(SIGINT, handler); //捕捉信号while(1)pause();return 0;}

作业:

  1. 两个进程实现cp功能

./rsrcfile

./wnewfile

  1. 用信号的知识实现司机和售票员问题。

1)售票员捕捉SIGINT(代表开车)信号,向司机发送SIGUSR1信号,司机打印(let’sgogogo)

2)售票员捕捉SIGQUIT(代表停车)信号,向司机发送SIGUSR2信号,司机打印(stopthebus)

3)司机捕捉SIGTSTP(代表到达终点站)信号,向售票员发送SIGUSR1信号,售票员打印(pleasegetoffthebus)

4)司机等待售票员下车,之后司机再下车。

司机:父进程

捕捉信号:

忽略信号:

售票员:子进程

捕捉信号:

忽略信号:

./a.out

#include #include #include #include #include #include pid_t pid;void driver(int sig){if (sig == SIGUSR1)printf("let's gogogo\n");else if (sig == SIGUSR2)printf("stop the bus\n");else if (sig == SIGTSTP){kill(pid, SIGUSR1);wait(NULL);exit(0);}}void saler(int sig){if (sig == SIGINT)kill(getppid(), SIGUSR1);else if (sig == SIGQUIT)kill(getppid(), SIGUSR2);else if (sig == SIGUSR1){printf("please get off the bus\n");exit(0);}}int main(int argc, char const *argv[]){if ((pid = fork()) < 0){perror("fork err");return -1;}else if (pid == 0){signal(SIGINT, saler);signal(SIGQUIT, saler);signal(SIGUSR1, saler);signal(SIGTSTP, SIG_IGN);}else{signal(SIGUSR1, driver);signal(SIGUSR2, driver);signal(SIGTSTP, driver);signal(SIGINT, SIG_IGN);signal(SIGQUIT, SIG_IGN);}while (1)pause();return 0;}

4.共享内存

4.1特点

1)共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝

2)为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间

3)进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。

4)由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等

4.2步骤

0)创建key值

1)创建或打开共享内存

2)映射

3)取消映射

4)删除共享内存

4.3函数接口

key_t ftok(const char *pathname, int proj_id);功能:创建key值参数:pathname:文件名 proj_id:取整型数的低8位数值返回值:成功:key值 失败:-1int shmget(key_t key, size_t size, int shmflg);功能:创建或打开共享内存参数:key键值size 共享内存的大小shmflg IPC_CREAT|IPC_EXCL|0777返回值:成功 shmid出错-1void*shmat(intshmid,constvoid*shmaddr,intshmflg);功能:映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问参数: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失败的-1intshmctl(intshmid,intcmd,structshmid_ds *buf);功能:(删除共享内存),对共享内存进行各种操作参数:shmid 共享内存的id号cmd IPC_STAT 获得shmid属性信息,存放在第三参数IPC_SET 设置shmid属性信息,要设置的属性放在第三参数IPC_RMID:删除共享内存,此时第三个参数为NULL即可返回:成功0失败-1用法:shmctl(shmid,IPC_RMID,NULL);

查看共享内存的命令:

ipcs-m

删除共享内存的命令:

ipcrm-mshmid

代码案例:

int main(int argc, char const *argv[]){key_t key;int shmid;//创建key值key = ftok("./app", 'b');if (key < 0){perror("ftok err");return -1;}printf("%#x\n", key);//创建或打印共享内存shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);if (shmid < 0){if (errno == EEXIST)shmid = shmget(key, 128, 0666);else{perror("shmget err");return -1;}}printf("shmid:%d\n", shmid);//映射char *p = NULL;p = shmat(shmid, NULL, 0); //NULL:系统自动进行映射 0:可读可写if(p == (char *)-1){perror("shmat err");return -1;}strcpy(p, "hello");printf("%s\n", p);//取消映射shmdt(p);//删除共享内存shmctl(shmid, IPC_RMID, NULL);return 0;}

练习:一个进程从终端输入,另一个进程将数据输出,借助共享内存通信。

要求:当输入quit时程序退出

同步:标志位

#include #include #include #include #include #include struct msg{int flg;char buf[32];};int main(int argc, char const *argv[]){key_t key;int shmid;//创建key值key = ftok("./app", 'b');if (key < 0){perror("ftok err");return -1;}printf("%#x\n", key);//创建或打印共享内存shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);if (shmid flg = 0;while(1){scanf("%s", p->buf);p->flg = 1;if(strcmp(p->buf, "quit") == 0)break;}return 0;}
#include #include #include #include #include #include int main(int argc, char const *argv[]){key_t key;int shmid;//创建key值key = ftok("./app", 'b');if (key < 0){perror("ftok err");return -1;}printf("%#x\n", key);//创建或打印共享内存shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);if (shmid < 0){if (errno == EEXIST)shmid = shmget(key, 128, 0666);else{perror("shmget err");return -1;}}printf("shmid:%d\n", shmid);//映射char *p = NULL;p = shmat(shmid, NULL, 0); //NULL:系统自动进行映射 0:可读可写if(p == (char *)-1){perror("shmat err");return -1;}strcpy(p, "hello");printf("%s\n", p);//取消映射shmdt(p);//删除共享内存shmctl(shmid, IPC_RMID, NULL);return 0;}

5.信号灯集

5.1.特点:

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

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

5.2步骤:

0)创建key值

1)创建或打开信号灯集semget

2)初始化信号灯集semctl

3)pv操作semop

4)删除信号灯集semctl

5.3函数接口

int semget(key_t key, int nsems, int semflg);功能:创建/打开信号灯参数:key:ftok产生的key值nsems:信号灯集中包含的信号灯数目semflg:信号灯集的访问权限,通常为IPC_CREAT |0666返回值:成功:信号灯集ID 失败:-1int semop ( int semid, struct sembuf*opsptr,size_tnops);功能:对信号灯集合中的信号量进行PV操作参数:semid:信号灯集ID opsptr:操作方式 nops:要操作的信号灯的个数 1个返回值:成功 :0失败:-1struct sembuf { shortsem_num; // 要操作的信号灯的编号 shortsem_op;//0 :等待,直到信号灯的值变成0 // 1:释放资源,V操作 // -1 :分配资源,P操作shortsem_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:信号灯集IDsemnum: 要操作的集合中的信号灯编号 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);

5.4命令

ipcs-s:查看信号灯集

ipcrm-ssemid:删除信号灯集

例子:

union semun {int val; //信号灯的初值};int main(int argc, char const *argv[]){key_t key;int semid;key = ftok("./app", 'b');if (key < 0){perror("ftok err");return -1;}printf("%#x\n", key);//创建或打开信号灯集semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666);if (semid < 0){if (errno == EEXIST)semid = semget(key, 2, 0666);else{perror("semget err");return -1;}}else{//初始化union semun sem;sem.val = 10;semctl(semid, 0, SETVAL, sem); //对编号为0的信号灯初值设置为10sem.val = 0;semctl(semid, 1, SETVAL, sem); //对编号为1的信号灯初值设置为0}printf("semid:%d\n", semid);printf("%d\n",semctl(semid, 0, GETVAL));//获取编号为0的信号灯的值printf("%d\n",semctl(semid, 1, GETVAL));//获取编号为1的信号灯的值//pv操作//p操作:申请资源struct sembuf buf;buf.sem_num = 0; //信号灯的编号buf.sem_op = -1; //p操作buf.sem_flg = 0; //阻塞semop(semid, &buf, 1);//v操作:释放资源buf.sem_num = 1;buf.sem_op = 1; //v操作buf.sem_flg = 0;semop(semid, &buf, 1);printf("%d\n",semctl(semid, 0, GETVAL));//获取编号为0的信号灯的值printf("%d\n",semctl(semid, 1, GETVAL));//获取编号为1的信号灯的值//删除信号灯集semctl(semid, 0, IPC_RMID); //指定任意一个编号即可删除信号灯集return 0;

//input.c代码#include #include #include #include #include #include #include union semun {int val; //信号灯的初值};//初始化void seminit(int semid, int snum, int val){union semun sem;sem.val = val;semctl(semid, snum, SETVAL, sem);}//pv操作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;//创建key值key = ftok("./app", 'b');if (key < 0){perror("ftok err");return -1;}printf("%#x\n", key);//创建或打印共享内存shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);if (shmid < 0){if (errno == EEXIST)shmid = shmget(key, 128, 0666);else{perror("shmget err");return -1;}}printf("shmid:%d\n", shmid);//创建或打开信号灯集semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666);if (semid < 0){if (errno == EEXIST)semid = semget(key, 1, 0666);else{perror("semget err");return -1;}}else{//初始化seminit(semid, 0, 0);}//映射char *p = NULL;p = shmat(shmid, NULL, 0); //NULL:系统自动进行映射 0:可读可写if(p == (char *)-1){perror("shmat err");return -1;}while(1){scanf("%s", p);//释放资源sem_op(semid, 0, 1);if(strcmp(p, "quit") == 0)break;}return 0;}//output.c代码#include #include #include #include #include #include #include union semun {int val; //信号灯的初值};//初始化void seminit(int semid, int snum, int val){union semun sem;sem.val = val;semctl(semid, snum, SETVAL, sem);}//pv操作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;//创建key值key = ftok("./app", 'b');if (key < 0){perror("ftok err");return -1;}printf("%#x\n", key);//创建或打印共享内存shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);if (shmid < 0){if (errno == EEXIST)shmid = shmget(key, 128, 0666);else{perror("shmget err");return -1;}}printf("shmid:%d\n", shmid);//创建或打开信号灯集semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666);if (semid < 0){if (errno == EEXIST)semid = semget(key, 1, 0666);else{perror("semget err");return -1;}}else{//初始化seminit(semid, 0, 0);}//映射char *p = NULL;p = shmat(shmid, NULL, 0); //NULL:系统自动进行映射 0:可读可写if(p == (char *)-1){perror("shmat err");return -1;}while(1){//申请资源sem_op(semid, 0, -1);if(strcmp(p, "quit") == 0)break;printf("data:%s\n", p);}//取消映射shmdt(p);//删除共享内存shmctl(shmid, IPC_RMID, NULL);//删除信号灯集semctl(semid, 0, IPC_RMID);return 0;}

6.消息队列

6.1特点:

消息队列是IPC对象的一种

消息队列由消息队列ID来唯一标识

消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等。

消息队列可以按照类型来发送/接收消息

6.2步骤:

1)创建key值ftok

2)创建或打开消息队列msgget

3)添加消息/读取消息msgsnd/msgrcv

4)删除消息队列msgctl

6.3操作命令

ipcs-q:查看消息队列

ipcrm-qmsgid:删除消息队列

6.4.函数接口

int msgget(key_t key, int flag);功能:创建或打开一个消息队列参数:key值 flag:创建消息队列的权限IPC_CREAT|IPC_EXCL|0666返回值:成功:msgid 失败:-1int msgsnd(int msqid, const void *msgp, size_t size, int flag); 功能:添加消息参数:msqid:消息队列的IDmsgp:指向消息的指针。常用消息结构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_tsize,long msgtype,intflag);功能:读取消息参数:msgid:消息队列的ID msgp:存放读取消息的空间 size:接受的消息正文的字节数msgtype:0:接收消息队列中第一个消息。大于0:接收消息队列中第一个类型为msgtyp的消息.小于0:接收消息队列中类型值不小于msgtyp的绝对值且类型值又最小的消息。 flag:0:若无消息函数会一直阻塞IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG返回值:成功:接收到的消息的长度失败:-1int 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)

例子:

#include #include #include #include #include #include #include struct msgbuf{long type;int num;char buf[32];};int main(int argc, char const *argv[]){key_t key;int msgid;//创建key值key = ftok("./app", 'b');if (key < 0){perror("ftok err");return -1;}printf("%#x\n", key);//创建消息队列msgid = msgget(key, IPC_CREAT|IPC_EXCL|0666);if(msgid < 0){if(errno == EEXIST)msgid = msgget(key, 0666);else{perror("msgget err");return -1;} }//添加消息int size = sizeof(struct msgbuf)-sizeof(long);struct msgbuf msg = {1, 100, "hello"};struct msgbuf msg1 = {2, 200, "world"};msgsnd(msgid, &msg, size, 0);msgsnd(msgid, &msg1, size, 0);//读取消息struct msgbuf m;msgrcv(msgid, &m, size, 2, 0);printf("%d %s\n", m.num, m.buf);//删除消息队列msgctl(msgid, IPC_RMID, NULL);return 0;}

练习:两个进程通过消息队列进行通信,一个进程从终端输入下发的指令,另一个进程接收指令,并打印对应操作语句。

如果输入LEDON,另一个进程输出“开灯”

如果输入LEDOFF,另一个进程输出“关灯”

#include  //send.c#include #include #include #include #include #include struct msgbuf{long type;char buf[32];};int main(int argc, char const *argv[]){key_t key;int msgid;//创建key值key = ftok("./app", 'b');if (key < 0){perror("ftok err");return -1;}printf("%#x\n", key);//创建消息队列msgid = msgget(key, IPC_CREAT|IPC_EXCL|0666);if(msgid < 0){if(errno == EEXIST)msgid = msgget(key, 0666);else{perror("msgget err");return -1;} }struct msgbuf msg;msg.type = 1;int s = sizeof(struct msgbuf)-sizeof(long);while(1){scanf("%s", msg.buf);msgsnd(msgid, &msg, s, 0);}return 0;}#include  //recieve.c#include #include #include #include #include #include struct msgbuf{long type;char buf[32];};int main(int argc, char const *argv[]){key_t key;int msgid;//创建key值key = ftok("./app", 'b');if (key < 0){perror("ftok err");return -1;}printf("%#x\n", key);//创建消息队列msgid = msgget(key, IPC_CREAT|IPC_EXCL|0666);if(msgid < 0){if(errno == EEXIST)msgid = msgget(key, 0666);else{perror("msgget err");return -1;} }struct msgbuf msg;int s = sizeof(struct msgbuf)-sizeof(long);while(1){msgrcv(msgid, &msg, s, 1, 0);if(strcmp(msg.buf, "LEDON") == 0)printf("开灯\n");else if(strcmp(msg.buf, "LEDOFF") == 0)printf("关灯\n");}return 0;}