第二阶段课程,属于linux下高级编程,也叫linux系统编程,感知linux内核的存在和内核的强大功能; 内容包括:文件管理、进程管理、设备管理、内存管理、网络管理。
第一章、文件管理
包括文件IO,标准IO,目录IO linux分七大类文件: 普通文件-,open创建 目录文件d,mkdir创建 链接文件l,ln -s[源][目]创建 管道文件p,mkfifo创建 套接字文件s 块设备文件b,一般为硬盘等 字符设备文件c,串口设备,键盘鼠标等 管道文件、套接字文件、块设备文件、字符设备文件不占磁盘空间,大小为0,只有文件节点
一、文件IO
文件IO主要包含4个函数:open、close、read、write,直接调用内核提供的系统调用函数,头文件是unistd.h 文件描述符fd,打开一个进程时,内核自动打开3个文件描述符:标准输入(键盘)的文件描述符是 0,标准输出(屏幕)是 1,标准错误(standard error)是 2,所以新打开的文件描述符一般是3,往后递增
1、open函数 打开或创建一个文件,open(char*,flag,mode),在fcntl.h文件中声明 第一个参数,char*,包含有文件名和路径 第二个参数,flag,打开文件的方式 第三个参数,mode,创建文件的权限,打开文件时不需要这个参数,创建文件时需要这个参数
第二个参数flag说明 flag 功能 O_RDONLY 只读 O_WRONLY 只写 O_RDWR 读写 O_CREAT 创建一个文件 O_EXCL 如果使用O_CREAT时文件存在,则返回错误消息,这一参数可测试文件是否存在,O_CREAT|O_EXCL文件不存在就创建,文件存在则返回-1,不加O_EXCL不管文件存不存在都创建 O_TRUNC 打开文件,会把已经存在的内容删除 O_APPEND 追加方式打开文件
open函数返回值 成功:返回文件描述符,它是正整数,(文件的ID,ls -lai命令可查) 失败:返回-1 示例:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc,char *argv[])
{
int fd;
fd=open(argv[1],O_CREAT | O_RDWR,0777);
if(fd<0)
{
printf("create %s failure\n",argv[1]);
return -1;
}
printf("create %s success,fd=%d\n",argv[1],fd);
close(fd);
return 0;
}
gcc -o touch touch.c,生成touch可执行文件 ./touch b.c,当前目录生成b.c文件,打印:create b.c success,fd=3
ps,生成的文件权限还和系统umask有关,命令umask可查当前设置,open第三个参数和umask值取掩码得到创建的文件权限 创建的文件权限 = mode & (~umask)//公式:mode和取反后的umask相与 例如umask是0022,mode是0777,则创建的文件权限是755 777: 111 111 111 022: 000 010 010 权限: 111 101 101 umask可改,umask 0000
2、close函数 close(fd),参数fd是要关闭的文件描述符
3、write函数 write(int fd,void *buf,size_t count); 第一个参数fd,向哪个文件写 第二个参数buf,写什么内容 第三个参数count,向这个文件写多少个字节 返回值,实际写的字节数
4、read函数 read(int fd,void *buf,size_t count) 第一个参数,从哪个文件读 第二个参数,读到哪里去 第三个参数,读多少个 返回值,实际读的字节数
5、lseek函数 lseek(int fd,off_t offset,int whence) 功能:调整读写的位置指针,头文件(sys/types.h unistd.h) 第一个参数,要调整的文件描述符 第二个参数,相对whence偏移量,单位是字节数,正整数向前移,负整数向后移 第三个参数,当前位置的基点,SEEK_SET(当前位置为文件的开头),SEEK_CUR(当前位置为文件指针的位置),SEEK_END(当前位置为文件的结尾) 返回值,成功返回文件当前的位置,失败返回-1 示例:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc,char *argv[])
{
int fd;
char buf[]="hello linux";
char read_buf[128]={0};
int wr_ret,rd_ret;
fd=open("./a.c",O_RDWR | O_TRUNC);
if(fd<0)
{
printf("open file a.c failure\n");
return -1;
}
wr_ret=write(fd,buf,sizeof(buf));
printf("open file a.c success,fd=%d\n",fd);
printf("wr_ret=%d\n",wr_ret);
lseek(fd,2,SEEK_SET);
rd_ret=read(fd,read_buf,128);
printf("recv data: read_buf=%s\n",read_buf);
close(fd);
return 0;
}
gcc -o write write.c编译,./write调用,在当前目录a.c文件中写入hello linux 打印输出: open file a.c success,fd=3 wr_ret=12 recv data: read_buf=llo linux
二、标准IO
C库函数,间接调用系统调用函数,头文件是stdio.h 1、三个缓存的概念: a>程序的缓存,就是程序想从内核读写的缓存(数组)----用户空间缓存 b>每打开一个文件,内核在内核空间中也会开辟一块缓存----内核空间缓存 文件IO中的写即是将用户空间中的缓存写到内核空间的缓存中 文件IO中的读即是将内核空间中的缓存写到用户空间的缓存中 c>标准IO的函数库中也有一个缓存----库缓存(分三类,全缓存、行缓存、无缓存)
示例: printf函数,是将内容先写入库缓存,当库缓存满了或遇到\n时,再写入内核缓存,这个库缓存大小通常为1024字节; 可使用setbuf(stdout,NULL)关闭行缓冲,或者setbuf(stdout,uBuff)设置新的缓冲区大小。
2、fopen函数 FILE *fopen(const char *path,const char *mode); 返回值,FILE * 文件流指针,类似与文件IO中的描述符,定义/usr/include/libio.h,struct_IO_FILE,包含读写缓存的首地址、大小、位置指针等 当打开一个进程时,会自动创建3个文件流: 标准的输入流:stdin(对应文件IO中文件描述符0) 标准的输出流:stdout(对应文件IO中文件描述符1),行缓存 标准的出错流:stderr(对应文件IO中文件描述符2),无缓存 第一个参数path,包含文件名的文件全路径 第二个参数mode,文件打开方式,类文件IO中的flag 1)b:二进制文件 2)r:只读方式打开文件,文件必须存在 3)w或a:只写方式打开文件,文件不存在则创建,w等级与O_TRUNC,a等价于O_APPEND 4)+:读写方式打开文件(w+创建文件并以读写方式打开,r+读写方式打开已存在文件) 5)w+:文件不存在时创建文件并以读写方式打开,文件存在时先删除文件,再创建文件并以读写方式打开 6)a+:文件不存在时创建文件并以读写方式打开,文件存在时,文件以读写方式打开并在原来的内容后面追加内容
fopen创建一个文件的权限默认时0666,文件的实际权限=0666 & (~umask) 示例:
#include <stdio.h>
int main(int argc,char *argv[])
{
FILE *fp;
fp=fopen(argv[1],"w+");
if(fp==NULL)
{
printf("create %s failure\n",argv[1]);
return -1;
}
printf("create %s success\n",argv[1]);
fclose(fp);
return 0;
}
3、fclose函数 int fclose(FILE *stream) 关闭流 stream。刷新所有的缓冲区 参数stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了要被关闭的流 返回值,如果流成功关闭,则该方法返回零。如果失败,则返回 EOF
4、fflush函数 fflush(FILE *fp);刷新缓存,把库函数中缓存的内容强制写到内核中 #include <stdio.h>//示例 int main() { printf(“hello linux”);//printf使用标准输出stdout fflush(stdout);//不加这句话,则强制退出前不会打印hello linux while(1); return 0; }
5、调整位置指针函数,fseek、rewind、ftell int fseek(FILE *stream, long int offset, int whence);//修改文件位置指针 stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流。 offset – 这是相对 whence 的偏移量,以字节为单位。 whence – 这是表示开始添加偏移 offset 的位置(SEEK_SET文件的开头,SEEK_CUR文件指针的当前位置,SEEK_END文件的末尾) 返回值,如果成功,则该函数返回零,否则返回非零值
void rewind(FILE *stream);//设置文件位置为给定流 stream 的文件的开头 stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流。 等价于:(void)fseek(fp,0,SEEK_SET)
long int ftell(FILE *stream);//返回给定流 stream 的当前文件位置 stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流 返回值,该函数返回位置标识符的当前值。如果发生错误,则返回 -1L,全局变量 errno 被设置为一个正值
6、三类读写函数 第一类行缓存:遇到新行符(\n)或写满缓存时,调用系统调用函数 1)读:fgets,gets,printf,fprintf,sprintf 2)写:fputs,puts,scanf 第二类无缓存:只要用户调用这个函数,就会将其内容写入到缓存中 stderr 第三类全缓存:只有写满缓存再调用系统调用函数 1)读:fread 2)写:fwrite
一个字符的读写 1)读:fgetc,getc,getchar 2)写:fputc,putc,putchar
stderr示例
#include <stdio.h>
int main()
{
fputs("hello linux",stderr);
while(1);
return 0;
}
6.1、行缓存的读写函数fgets和fputs char *fgets(char *s,int size,FILE *stream); 第一个参数s,缓存,即读到哪里区 第二个参数size,读多少个字节 第三个参数stream,从什么地方读 返回值,成功则为s缓存的地址,若已处于文件末尾或出错则为NULL
int fputs(const char *s,FILE *stream); 第一个参数s,缓存,即写什么内容 第二个参数stream,写到哪里区 返回值,成功则为非负值,若出错则为EOF(-1)
示例:
#include <stdio.h>
int main(int argc,char *argv[])
{
FILE *fp;
char buf[]="hello linux";
char readbuf[128]={0};
fp=fopen("./a.c","w+");
if(fp==NULL)
{
printf("open file a.c failure\n");
return -1;
}
printf("open file a.c success\n");
fputs(buf,fp);
fseek(fp,0,SEEK_SET);
fgets(readbuf,128,fp);
printf("%s\n",readbuf);
fclose(fp);
return 0;
}
6.2、行缓存的读写函数gets和puts char *gets(char *s);//参数s,缓存,即读到哪里区;返回值成功返回s缓存的地址,失败返回NULL int puts(const char *s);//参数s,缓存,即写什么内容;返回值,成功则为非负值,若出错则为EOF(-1)
gets和fgets的区别 1)gets只能从标准输入中读 2)gets不能指定缓存的长度,可能会造成缓存越界 3)gets并不将新行符\n存入缓存中,fgets会将新行符\n存入缓存
puts和fputs区别 1)puts只能向标准输出中写 2)puts输出时会添加一个新行符\n,fputs输出时不会添加
示例
#include "stdio.h"
#include "string.h"
int main()
{
char buf[128]={0};
int len;
fgets(buf,128,stdin);
len=strlen(buf);
printf("len=%d\n",len);
fputs(buf,stdout);
return 0;
}
//输入abcd,输出 len=5 abcd
6.3、printf,fprintf,sprintf int fprintf(FILE *stream, const char *format, …);//可以输出到文件中,也可以输出到显示器 stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流 format – 格式化字符串 返回值,如果成功,则返回写入的字符总数,否则返回一个负数
int sprintf(char *str, const char *format, …);//输出内容到一个字符串中 str – 这是指向一个字符数组的指针,该数组存储了 C 字符串 format – 格式化字符串 返回值,如果成功,则返回写入的字符总数,不包括字符串追加在字符串末尾的空字符。如果失败,则返回一个负数
int printf(const char *format, …);//只能输出到显示器
示例:
#include "stdio.h"
int main()
{
FILE *fp;
int i=10;
char buf[128]={0};
fp=fopen("./a.c","w+");
if(fp==NULL)
{
printf("open a.c failure\n");
return -1;
}
fprintf(fp,"open a.c success, i=%d\n",i);
fclose(fp);
sprintf(buf,"i=%d",i);
printf("buf:%s\n",buf);
return 0;
}
6.4、fgetc,fputc int fgetc(FILE *fp);//从文件中读取一个字符 参数fp,文件流 返回值,成功返回读取的字符,到文件末尾或出错返回EOF
int fputc(int c,FILE *fp);//写一个字符到文件中,fputc有缓存,但不是行缓存 参数c,要写的字符 参数fp,文件流 返回值,成功返回输入的字符,出错返回EOF
6.5、feof,ferror,clearerr int feof(FILE *stream);//判断是否已经到文件结束 参数stream,文件流 返回值,没到文件结束返回0,到文件结束返回非0
int ferror(FILE *stream)//判断是否读写错误 参数stream,文件流 返回值,如果是读写错误,返回非0;如果没有读写错误,返回0
void clearerr(FILE *stream)//清除流错误 参数stream,文件流
示例:
#include "stdio.h"
int main()
{
FILE *fp;
int ret;
fp=fopen("./a.c","w+");
if(fp==NULL)
{
printf("open file a.c failure\n");
return -1;
}
printf("open file a.c success\n");
fputc('a',fp);
rewind(fp);
ret=fgetc(fp);
printf("ret=%c\n",ret);
ret=fgetc(fp);
printf("ret=%d\n",ret);
printf("feof=%d,ferror=%d\n",feof(fp),ferror(fp));
clearerr(fp);
printf("feof=%d,ferror=%d\n",feof(fp),ferror(fp));
fclose(fp);
return 0;
}
打印: open file a.c success ret=a ret=-1 feof=1,ferror=0 feof=0,ferror=0
6.5、fread,fwrite size_t fread(void *ptr, size_t size, size_t nmemb, FILE stream);//从给定流 stream 读取数据到 ptr 所指向的数组中 ptr – 这是指向带有最小尺寸 sizenmemb 字节的内存块的指针。 size – 这是要读取的每个元素的大小,以字节为单位。 nmemb – 这是元素的个数,每个元素的大小为 size 字节。 stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。 返回值,成功读取的元素总数会以 size_t 对象返回,size_t 对象是一个整型数据类型。如果总数与 nmemb 参数不同,则可能发生了一个错误或者到达了文件末尾
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);//把 ptr 所指向的数组中的数据写入到给定流 stream 中 ptr – 这是指向要被写入的元素数组的指针。 size – 这是要被写入的每个元素的大小,以字节为单位。 nmemb – 这是元素的个数,每个元素的大小为 size 字节。 stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流. 返回值,如果成功,该函数返回一个 size_t 对象,表示元素的总数,该对象是一个整型数据类型。如果该数字与 nmemb 参数不同,则会显示一个错误
示例:
#include "stdio.h"
int main()
{
FILE *fp;
char buf[]="hello linux\n";
char readbuf[128]={0};
fp=fopen("./a.c","w+");
if(fp==NULL)
{
printf("open file a.c failure\n");
return -1;
}
printf("open file a.c success\n");
fwrite(buf,sizeof(char),sizeof(buf),fp);
rewind(fp);
fread(readbuf,sizeof(char),sizeof(readbuf),fp);
printf("readbuf=%s",readbuf);
fclose(fp);
return 0;
}
打印 open file a.c success readbuf=hello linux
linux函数库目录:/lib /usr/lib 7、静态函数库的制作与使用 libxxx.a,在编译时就将库编译进可执行程序中 优点:程序的运行环境中不需要外部的函数库 缺点:可执行程序体积大 7.1静态库的制作: 1)、生成目标文件:gcc -c -o file.o file.c 2)、静态库生成:ar -cr -o libfile.a file.o -c:create的意思 -r:repalce,表示当插入的模块file.o已经存在libfile.a中,则覆盖 3)、查询一个静态库包含哪些模块:ar -t Tool_Fun.a
7.2静态库的使用:gcc -o main main.c -L. -lfile//编译main.c就会把静态函数库整合进main -L指定静态函数库的查找位置,注意L后面的.表示静态函数库在本目录下查找 -l指定了静态函数库名,由于静态函数库的命名方式是lib***.a,其中lib和.a忽略
示例:
#include "stdio.h"
int main()
{
int ret,x,y;
x=10;
y=5;
ret=sub(x,y);
printf("ret=%d\n",ret);
return 0;
}
int sub(int x,int y)
{
return(x-y);
}
操作步骤: 1)gcc -c -o sub.o sub.c 生成sub.o 2)ar -cr -o libsub.a sub.o 生成libsub.a 3)gcc main.c -L. -lsub 把静态库编译进可执行程序,生成a.out
8、动态库的制作和使用 又称共享库,libxxx.so,在运行时将库加载到可执行程序中 优点:可执行程序小 缺点:程序的运行环境中必须提供相应的库
8.1、动态函数库的制作 1)gcc -c file.c 生成目标文件 2)gcc -shared -fpic -o libfile.so file.o 生成动态库 -fpic:产生位置无关代码 -shared:生成共享库 8.2、动态库的使用 1)gcc -o out main.c -L. -lfile 生成可执行文件,此时不能./out,因为动态函数库在使用时,会查找/lib和/usr/lib目录,而我们生成的so不在目录 2)运行时加载动态库 第一种方法:把生成的so动态库放到/usr/lib或/lib文件中 第二种方法:把当前目录加到环境变量 export LD_LIBRARY_PATH=/home/chw/learnlinux echo $LD_LIBRARY_PATH查询生成的环境变量 第三种方法:在/etc/ld.so.conf文件加入生成的库的目录,然后sudo ldconfig /etc/ld.so.conf运行配置文件
示例://main.c和sub.c和上面示例一样 操作步骤: 1)gcc -c -o sub.o sub.c 生成sub.o 2)gcc -shared -fpic -o libsub.so sub.o 生成libsub.so 3)gcc -o main main.c -L. -lsub 生成可执行文件main 4)把生成的动态库路径加到搜索目录即可运行main
三、目录IO
opendir(只能打开目录),mkdir(创建目录),readdir(读目录),rewinddir、telldir、seekdir(调整位置指针),closedir(关闭目录)
1、opendir DIR *opendir(const char *pathname);//打开目录 参数pathname,打开的目录以及路径 返回值,成功返回目录流指针,出错返回NULL
2、mkdir int mkdir(const char *path,mode_t mode);//创建目录 参数path,创建的目录文件路径 参数mode,该目录的访问权限 返回值,创建成功返回0,失败返回-1
3、readdir struct dirent *readdir(DIR *dr);//读目录 参数dr,目录流指针 返回值,成功返回struct dirent结构体,若在目录尾或出错返回NULL struct dirent定义在头文件dirent.h
struct dirent
{
ino_t d_ino;
off_t d_off;
unsigned short d_reclen;
unsigned char d_type;
char d_name[256];
};
4、closedir int closedir(DIR *dir); 参数dir,目录流指针 返回值,关闭成功则返回0,失败返回-1
5、rewinddir void rewinddir(DIR *dr);//重置读取目录的位置为开头 参数dr,目录流指针
6、telldir long telldir(DIR *dr); 参数dr,目录流指针 返回值,目录流当前位置
7、seekdir void seekdir(DIR *dr,long loc); 类似文件定位函数fseek(),在目录流上设置下一个readdir()操作的位置 参数dr,目录流指针 参数loc,偏移量
示例:
#include "stdio.h"
#include "sys/types.h"
#include "dirent.h"
int main()
{
int ret;
DIR *dp;
struct dirent *dir;
ret=mkdir("./mydir",0777);
if(ret<0)
{
printf("create mydir failure\n");
return -1;
}
printf("create mydir success\n");
dp=opendir("./mydir");
if(dp==NULL)
{
printf("open dir failure\n");
return -2;
}
printf("open mydir success\n");
dir=readdir(dp);
if(dir!=NULL)
{
printf("inode=%ld,name=%s\n",dir->d_ino,dir->d_name);
}
closedir(dp);
return 0;
}
打印 create mydir success open mydir success inode=449350,name=…
第二章、进程间通信
进程间通信:在用户空间无法实现进程间通信,需要通过linux内核通信; 线程间通信:可以通过用户空间实现(全局变量)。
进程间通信方式 管道通信:无名管道,有名管道(文件系统中有名); 信号通信:信号的发送、信号的接收和信号的处理; IPC通信:共享内存、消息队列和信号灯; socket通信:存在于一个网络中两个进程之间的通信。
进程通信学习思路,每一种通信方式都是基于文件IO的思想 open,创建或打开进程通信对象,函数形式不一样,有的是有多个函数完成; write,向进程通信对象写入内容,函数形式可能不一样; read,从进程通信对象中读取内容,函数形式可能不一样; close,关闭或删除进程通信对象,函数形式可能不一样。
IPC通信和文件I/O函数比较 文件I/O IPC open Msg_get,shmget,Sem_get read/write msgsnd/msgrecv,shmat/shmdt,semop close msgctrl,shmctrl,semctrl
一、无名管道
管道文件是一个特殊的文件,是由队列来实现的; 在文件IO中创建一个文件或打开一个文件是由open函数实现的,但open不能创建管道文件;
int pipe(int fd[2]);//使用pipe函数创建管道 参数,就是得到的文件描述符,有两个文件描述符fd[0]和fd[1],管道有一个读端fd[0]用来读,一个写端fd[1]用来写 返回值,成功返回0,失败返回-1.
无名管道的特点 1、管道是创建再内存中的,进程结束,空间释放,管道就不存在了; 2、管道中的数据,读完就会删除; 3、如果管道中没有东西可读,则会读阻塞; 4、如果管道写满(65536个字节),则会写阻塞; 5、只能实现父子进程或有亲缘关系进程之间的通信(fork)。
//示例1
#include "unistd.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
int main()
{
int fd[2];
int ret;
char readbuf[128]={0};
char writebuf[]="hello linux";
ret = pipe(fd);
if(ret < 0)
{
printf("create pipe failure\n");
return -1;
}
printf("creatr pipe success fd[0]=%d,fd[1]=%d\n",fd[0],fd[1]);
write(fd[1],writebuf,sizeof(writebuf));
read(fd[0],readbuf,128);
printf("readbuf=%s\n",readbuf);
memset(readbuf,0,128);
read(fd[0],readbuf,128);
printf("second read after\n");
close(fd[0]);
close(fd[1]);
return 0;
}
打印 creatr pipe success fd[0]=3,fd[1]=4 readbuf=hello linux ^C //示例2
#include "unistd.h"
#include "stdio.h"
#include "sys/types.h"
#include "stdlib.h"
int main()
{
pid_t pid;
int fd[2];
int ret;
int process_inter=0;
ret=pipe(fd);
if(ret<0)
{
printf("create pipe failure\n");
return -1;
}
printf("create pipe sucess\n");
pid=fork();
if(pid==0)
{
int i=0;
read(fd[0],&process_inter,1);
while(process_inter == 0);
for(i=0;i<5;i++)
{
printf("this is child process i=%d\n",i);
usleep(100);
}
}
if(pid>0)
{
int i=0;
for(i=0;i<5;i++)
{
printf("this is parent process i=%d\n",i);
usleep(100);
}
process_inter=1;
write(fd[1],&process_inter,1);
}
while(1);
return 0;
}
打印 create pipe sucess this is parent process i=0 this is parent process i=1 this is parent process i=2 this is parent process i=3 this is parent process i=4 this is child process i=0 this is child process i=1 this is child process i=2 this is child process i=3 this is child process i=4 ^C
二、有名管道
所谓有名,即文件系统中存在这样一个文件节点,每个文件节点都有一个inode号,这是一种特殊的文件类型:p管道文件 管道文件不站磁盘空间,只有inode号; int mkfifo(const char *filename,mode_t mode);//创建管道文件 参数,管道文件名,权限,创建文件的权限和umask有关 返回值,成功返回0,失败返回-1
mkfifo,创建管道文件的节点,没有在内核中创建管道; 只有通过open函数打开这个文件时,才会在内核空间创建管道。 //示例1,创建管道文件
#include "unistd.h"
#include "stdio.h"
#include "stdlib.h"
int main()
{
int ret;
ret=mkfifo("./myfifo",0777);
if(ret<0)
{
printf("creat myfifo failure\n");
return -1;
}
printf("create myfifo success\n");
return 0;
}
#include "unistd.h"
#include "stdio.h"
#include "sys/types.h"
#include "stdlib.h"
#include "fcntl.h"
int main()
{
int fd;
int process_inter=0;
fd=open("./myfifo",O_WRONLY);
if(fd<0)
{
printf("open myfifo failure\n");
return -1;
}
printf("open myfifo success\n");
for(int i=0;i<5;i++)
{
printf("this is first process i=%d\n",i);
usleep(100);
}
process_inter=1;
sleep(5);
write(fd,&process_inter,1);
while(1);
return 0;
}
#include "unistd.h"
#include "stdio.h"
#include "sys/types.h"
#include "stdlib.h"
#include "fcntl.h"
int main()
{
int fd;
int process_inter=0;
fd=open("./myfifo",O_RDONLY);
if(fd<0)
{
printf("open myfifo failure\n");
return -1;
}
printf("open myfifo success\n");
read(fd,&process_inter,1);
while(process_inter == 0);
for(int i=0;i<5;i++)
{
printf("this is second process i=%d\n",i);
usleep(100);
}
while(1);
return 0;
}
三、信号通信
信号通信,是内核向用户空间进程发送信号,只有通过内核才能发信号,用户空间进程之间不能发送信号 信号在linux系统已存在,kill -l查询内核可以发送的所有信号(64种不同的信号) 示例:kill 9 pid//给指定pid的进程发送信号9,用kill -l查询可知9代表SIGKILL,作用是杀死进程
信号通信的框架 信号的发送(发送信号进程):kill、raise、alarm 信号的接收(接收信号进程):pause()、sleep、while(1) 信号的处理(接收信号进程):signal 1、信号的发送 kill函数 int kill(pid_t pid,int sig); 参数pid,正数(要接收信号进程的进程号),0(信号被发送到所有和pid进程在同一个进程组的进程,-1(信号发给所有进程表中的进程,除了进程号最大的进程外) 参数sig,信号 返回值,0成功,-1失败 //示例1,mykill.c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
int main(int argc,char *argv[])
{
int sig;
int pid;
if(argc < 3)
{
printf("please input param\n");
return -1;
}
sig=atoi(argv[1]);
pid=atoi(argv[2]);
printf("sig=%d,pid=%d\n",sig,pid);
kill(pid,sig);
return 0;
}
./mykill 9 10804//杀死10804进程
raise函数 int raise(int sig);//发送信号给自己,不能给其他进程发送信号,raise(int sig)==kill(getpid(),sig) 参数,sig,发送的信号 返回值,0成功,-1失败 //示例2,raise.c
#include "stdio.h"
#include "stdlib.h"
#include "signal.h"
int main()
{
printf("raise before");
raise(9);
printf("raise after");
return 0;
}
alarm函数 unsigned int alarm(unsigned int seconds);//只能发送给当前进程,只会发送SIGALRM信号终止进程,会让内核延迟seconds秒后发送信号 参数,seconds,指定秒数 返回值,成功,如果调用该alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟的剩余时间,否则返回0;失败返回-1 //示例3,alarm.c
#include "unistd.h"
#include "stdio.h"
#include "stdlib.h"
#include "signal.h"
int main()
{
int i;
i=0;
printf("alarm before\n");
alarm(9);
printf("alarm after\n");
while(i<20)
{
i++;
sleep(1);
printf("process things,i=%d\n",i);
}
return 0;
}
2、信号的接收 pause函数 int pause();//使进程处于睡眠状态,可以使用键盘Ctrl+C终止进程,Ctrl+C是键盘驱动通过内核向用户空间进程发送SIGINT信号终止进程 返回值,0成功,-1失败
3、信号的处理 signal函数 void (*signal(int sig, void (*func)(int)))(int); 参数,sig,指定信号 func,函数指针,是自己写的处理函数 返回值,该函数返回信号处理程序之前的值,当发生错误时返回 SIG_ERR //示例4,signal.c
#include "stdio.h"
#include "unistd.h"
#include "stdlib.h"
#include "signal.h"
#include "sys/types.h"
void myfun(int signum)
{
int i=0;
while(i<10)
{
printf("process signal,sig=%d\n",signum);
sleep(1);
i++;
}
return;
}
int main()
{
int i;
i=0;
signal(14,myfun);
signal(14,SIG_IGN);
signal(14,SIG_DFL);
printf("alarm before\n");
alarm(9);
printf("alarm after\n");
while(i<20)
{
i++;
sleep(1);
printf("process things,i=%d\n",i);
}
return 0;
}
四、共享内存
特点:共享内存创建之后一直存在于内核中,直到被删除或系统关闭;和管道不一样,读取后内容仍在共享内存中 1、shmget函数 int shmget(key_t key,int size,int shmflg);//创建一个共享内存对象,这是一块缓存,类似用户空间的数组或malloc函数分配的空间一样 参数,key,IPC_PRIVATE(ipcs -m查询出来的key始终是0,实现有亲缘关系进程之间的通信)或ftok(可实现无亲缘关系进程之间的通信)的返回值 参数,size,共享内存的大小 参数,shmflg,同open函数的权限位,也可以用8进制表示法 返回值,成功:共享内存段标识符,也就是文件描述符ID,失败返回-1
2、ftok函数 //创建key值 char ftok(const char *path,char key); 参数,path,文件路径和文件名 参数,key,一个字符 返回值,成功返回一个key值,失败返回-1
ipcs -m-q-s 查看IPC对象,-m查看共享内存,-q查看消息队列,-s查看信号灯 ipcrm -m id 删除IPC对象
3、shmat函数 //将共享内存映射到用户空间的地址中 void *shmat(int shmid,const void *shmaddr,int shmflg); 参数,shmid,ID号 参数,shmaddr,映射到的地址,NULL为系统自动完成的映射 参数,shmflg,SHM_RDONLY共享内存只读,默认是0表示共享内存可读写 返回值,成功返回映射后的地址,失败返回NULL
4、shmdt函数 //将进程里的地址映射删除 int shmdt(const void *shmaddr); 参数,shmaddr,共享内存映射后的地址,也就是shmat函数的返回值 返回值,成功0,失败-1
5、shmctl函数 //删除共享内存对象 int shmctl(int shmid,int cmd,struct shmid_ds *buf); 参数,shmid,要操作的共享内存标识符,是shmget函数返回值 参数,cmd,IPC_STAT(获取对象熟悉,ipcs -m),IPC_SET(设置对象熟悉),IPC_RMID(删除对象,ipcrm -m) 参数,buf,指定IPC_STAT/IPC_SET时用以保存/设置熟悉 返回值,成功0,失败-1 //示例1,shm.c
#include "sys/types.h"
#include "sys/shm.h"
#include "signal.h"
#include "unistd.h"
#include "stdio.h"
#include "stdlib.h"
int main()
{
int shmid;
int key;
char *p;
key=ftok("./a.c",'a');
if(key <0)
{
printf("create key failure\n");
return -2;
}
printf("create key success key=%d\n",key);
shmid=shmget(key,128,IPC_CREAT | 0777);
if(shmid<0)
{
printf("create share memory failure\n");
return -1;
}
printf("create share memory sucess shmid=%d\n",shmid);
system("ipcs -m");
p=(char *)shmat(shmid,NULL,0);
if(p==NULL)
{
printf("shmat function failure\n");
return -3;
}
fgets(p,128,stdin);
printf("share memory data:%s",p);
printf("second read share memory data:%s",p);
shmdt(p);
shmctl(shmid,IPC_RMID,NULL);
system("ipcs -m");
return 0;
}
#include "sys/types.h"
#include "sys/shm.h"
#include "signal.h"
#include "unistd.h"
#include "stdio.h"
#include "stdlib.h"
void myfun(int signum)
{
return;
}
int main()
{
int shmid;
char *p;
int pid;
shmid=shmget(IPC_PRIVATE,128,0777);
if(shmid<0)
{
printf("create share memory failure\n");
return -1;
}
printf("create share memory sucess shmid=%d\n",shmid);
system("ipcs -m");
pid=fork();
if(pid>0)
{
signal(SIGUSR2,myfun);
p=(char *)shmat(shmid,NULL,0);
if(p==NULL)
{
printf("shmat function failure\n");
return -3;
}
while(1)
{
printf("parent process start write share memory:\n");
fgets(p,128,stdin);
kill(pid,SIGUSR1);
pause();
}
}
if(pid==0)
{
signal(SIGUSR1,myfun);
p=(char *)shmat(shmid,NULL,0);
if(p==NULL)
{
printf("child process shmat function failure\n");
return -4;
}
while(1)
{
pause();
printf("share memory data:%s",p);
kill(getppid(),SIGUSR2);
}
}
shmdt(p);
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
五、消息队列
1、msgget函数 //创建或打开消息队列 int msgget(key_t key,int flag); 参数,key,和消息队列关联的key值,类似共享内存的key 参数,flag,消息队列的访问权限 返回值,成功返回消息队列ID,失败返回-1
第三章、线程管理
进程:一个正在执行的程序,它是资源分配的最小单位,进程中的事情需要按照一定的顺序逐个进行 线程:有时又称轻量级进程,程序执行的最小单位,系统独立调度和分派cpu的基本单位,它是进程中的一个实体,一个进程中可以有多个线程,这些线程共享进程的所有资源,线程本身只包含一点必不可少的资源 进程的弊端:由于进程是资源拥有者,创建、撤销与切换存在较大的开销,因此引入轻型进程;对称多处理机(SMP)的出现,可以满足多个运行单位,而多个进程并行开销过大。 并发:指在同一时刻,只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果,看起来是同时发生,单核。 并行:指在同一时刻,有多条指令在多个处理器上同时执行,是真正的同时发生 同步:彼此有依赖关系的调用不应该同时发生,而同步就是阻止那些同时发生的事情 异步:两个彼此独立的操作是异步的,表明事情独立的发生
一、线程ID
线程 进程
标识符类型 pthread_t pid_t 获取ID pthread_self() getpid() 创建 pthread_create() fork() pthread_t类型:unsigned long int(linux),结构体(FreeBDS5.2、Mac OS10.3)
示例:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
pid_t pid;
pthread_t tid;
pid=getpid();
tid=pthread_self();
printf("pid is %u,tid is %x\n",pid,tid);
return 0;
}
gcc pid.c -lpthread -o pid//编译,这里注意-lpthread要放在源文件的后面,否则生成的tid无效(0) 打印//pid is 11746,tid is ad5cc700
二、创建线程
int pthread_create(pthread_t *tidp,const pthread_attr_t *attr,void *(start_rtn)(void),void *arg); 第一个参数:新线程的ID,如果成功则新线程的ID回填到tidp指向的内存 第二个参数:线程属性(调度策略,继承性,分离性等) 第三个参数:回调函数,新线程要执行的函数 第四个参数:回调函数的参数 返回值,成功返回0,失败返回错误码(定义在:cat /usr/include/asm-generic/errno.h)
示例:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
void print_id(char *s)
{
pid_t pid;
pthread_t tid;
pid=getpid();
tid=pthread_self();
printf("%s pid is %u,tid is 0x%x\n",s,pid,tid);
}
void *thread_fun(void *arg)
{
print_id(arg);
return (void *)0;
}
int main()
{
pthread_t ntid;
int err;
err = pthread_create(&ntid,NULL,thread_fun,"new thread");
if(err != 0)
{
printf("create new thread failed\n");
}
print_id("main thread");
sleep(2);
return 0;
}
打印 main thread pid is 12358,tid is 0x82ecd700 new thread pid is 12358,tid is 0x826df700
三、主线程
1、当c程序运行时,首先运行main函数,在线程代码中,这个特殊的执行流被称为初始线程或主线程,主线程能做任何普通线程可以做的事; 2、主线程的特殊性在于,它在main函数返回的时候,会导致进程结束,进程内所有的线程也将会结束,可以在主线程调用pthread_exit函数,这样进程会等待所有线程都结束时才终止; 3、主线程接受参数的方式是通过argc和argv,而普通线程只有一个参数void*; 4、绝大多数情况下,主线程在默认堆栈上运行,这个堆栈可以增长到足够的长度,而普通线程的堆栈是受限制的,一旦溢出会产生错误,可以修改普通线程的堆栈长度; 5、主线程随着进程的创建而创建,普通线程主要通过函数pthread_create创建(有时新线程在当前线程从pthread_create返回之前就已经运行了,甚至已经运行完毕了)。
示例:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
struct student{
int age;
char name[20];
};
void *thread_fun(void *stu)
{
sleep(1);
printf("student age is %d,name is %s\n",((struct student*)stu)->age,((struct student*)stu)->name);
return (void *)0;
}
int main(int argc,char *argv[])
{
pthread_t tid;
int err;
int *rval;
struct student stu;
stu.age = 20;
memcpy(stu.name,"zhangsan",20);
err = pthread_create(&tid,NULL,thread_fun,(void*)(&stu));
if(err!=0)
{
printf("create new thread failed\n");
return 0;
}
printf("main thread have %d argc\n",argc);
for(int i=0;i<argc;i++)
{
printf("main thread args is %s\n",argv[i]);
}
pthread_exit(rval);
return 0;
}
四、线程的状态
1、就绪,线程能够运行,但是在等待可用的处理器 当线程刚被创建时或者当线程被接触阻塞以后就处于就绪状态,当一个运行的线程被抢占时,它立刻又回到就绪状态 2、运行,线程正在运行,当处理器选中一个就绪的线程执行时,它立刻变成运行状态 3、阻塞,线程在等待处理器以外的其他条件 线程在以下情况发生阻塞:试图加锁一个已经被锁住的互斥量,等待某个条件变量,调用singwait等尚未发生的信号,执行无法完成的I/O信号,由于内存页错误等 4、终止,线程从启动函数返回,或者调用pthread_exit函数,或者被取消
线程的分离属性 创建线程时默认是非分离的; 分离一个正在运行的线程并不影响它,仅仅时通知当前系统该线程结束时,其所属的资源可以回收; 一个没有被分离的线程在终止时会保留它的虚拟内存,包括他们的堆栈和其他系统资源,有时这种线程被成为僵尸线程; 如果线程具有分离属性,线程终止时会被立刻回收,回收将释放调用所有在线程终止时未释放的系统资源和进程资源,包括线程返回值的内存空间、堆栈、报错寄存器的内存空间等; 终止被分离的线程会释放所有的系统资源,但是你必须自己释放有该线程占用的程序资源,需要在线程终止前解锁所有的互斥量,这样别的线程也可以释放该线程的资源。
五、线程终止
exit的危险性:如果进程中任意一个线程调用了exit,_Exit,_exit,那么整个进程就会终止
不终止进程的退出方式 1、从启动例程中返回,返回值时线程的退出码 2、线程可以被同一个进程中的其他线程取消 3、线程调用pthread_exit(void *rval)函数,rval是退出码
示例//3种方式终止线程
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
void *thread_fun(void *arg)
{
if(strcmp("1",(char*)arg)==0)
{
printf("new thread return!\n");
return (void*)1;
}
if(strcmp("2",(char*)arg)==0)
{
printf("new thread pthread_exit!\n");
pthread_exit((void *)2);
}
if(strcmp("3",(char*)arg)==0)
{
printf("new thread exit!\n");
exit(3);
}
}
int main(int argc,char *argv[])
{
int err;
pthread_t tid;
err = pthread_create(&tid,NULL,thread_fun,(void*)argv[1]);
if(err!=0)
{
printf("create new thread failed\n");
return 0;
}
sleep(1);
printf("main thread quit\n");
return 0;
}
打印//gcc t.c -lpthread chw@ubuntu:~/learnlinux$ ./a.out 1 new thread return! main thread quit chw@ubuntu:~/learnlinux$ ./a.out 2 new thread pthread_exit! main thread quit chw@ubuntu:~/learnlinux$ ./a.out 3 new thread exit! chw@ubuntu:~/learnlinux$
六、线程的连接与分离
1、pthread_join int pthread_join(pthread_t tid,void **rval); pthread_join()即是子线程合入主线程,主线程阻塞等待子线程结束,然后回收子线程资源 描述 :pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的 调用该函数的线程会一直阻塞,直到指定的线程tid调用pthread_exit、从启动例程返回或被取消 参数tid就是指定线程的id 参数rval是指定线程的返回码,如果线程被取消,那么rval被置为PTHREAD_CANCELED(-1) 返回值,成功返回0,失败返回错误码 如果指定线程已经处于分离状态,那么调用pthread_join就会失败;
2、pthread_detach int pthread_detach(pthread_t tid); pthread_detach()即主线程与子线程分离,子线程结束后,资源自动回收 pthread_join()函数的替代函数,可回收创建时detachstate属性设置为PTHREAD_CREATE_JOINABLE的线程的存储空间。该函数不会阻塞父线程。 pthread_join()函数用于只是应用程序在线程tid终止时回收其存储空间。如果tid尚未终止,pthread_detach()不会终止该线程。当然pthread_detach(pthread_self())也是可以得 pthread_detach函数可以分离一个线程,线程可以自己分离自己; 返回值:pthread_detach() 在调用成功完成之后返回0。其他任何返回值都表示出现了错误。如果检测到以下任一情况,pthread_detach()将失败并返回相应的值:EINVAL:tid是分离线程,ESRCH:tid不是当前进程中有效的为分离线程
示例:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
void *thread_fun1(void *arg)
{
printf("this is thread1\n");
return (void *)1;
}
void *thread_fun2(void *arg)
{
printf("this is thread2\n");
pthread_detach(pthread_self());
pthread_exit((void*)2);
}
int main()
{
int err1,err2;
pthread_t tid1,tid2;
void *rval1,*rval2;
err1 = pthread_create(&tid1,NULL,thread_fun1,NULL);
err2 = pthread_create(&tid2,NULL,thread_fun2,NULL);
if(err1 || err2)
{
printf("create new thread failed\n");
return 0;
}
printf("this is main thread\n");
printf("join1 rval is %d\n",pthread_join(tid1,&rval1));
printf("join2 rval is %d\n",pthread_join(tid2,&rval2));
printf("thread1 exit code is %d\n",(int *)rval1);
printf("thread2 exit code is %d\n",(int *)rval2);
printf("this is main thread\n");
return 0;
}
打印 情况一,thread_fun2调用pthread_detach this is main thread this is thread2 this is thread1 join1 rval is 0 join2 rval is 22//线程2已分离,此时join会报错,错误码22(cat /usr/include/asm-generic/errno-base.h查询错误码对应错误,man pthread_join查询错误描述) thread1 exit code is 1 thread2 exit code is -2007452768//线程2已分离,获取的返回值无效 this is main thread 情况二,thread_fun2不调用pthread_detach this is main thread this is thread2 this is thread1 join1 rval is 0//pthread_join返回0表示成功 join2 rval is 0 thread1 exit code is 1 thread2 exit code is 2 this is main thread
七、线程取消
1、pthread_cancel,取消线程 int pthread_cancel(pthread_t tid); 取消tid指定的线程,成功返回0,取消只是发送一个请求,并不是等待线程终止,发送成功也不意味着tid一定会终止
2、pthread_setcancelstate,取消状态 int pthread_setcancelstate(int state,int *oldstate); 取消状态,就是线程对取消信号的处理方式,忽略或者响应,线程创建时默认响应取消信号; 设置本线程对cancel信号的反应,state有两种值,PTHREAD_CANCEL_ENABLE(缺省)和PTHREAD_CANCEL_DISABLE,分别表示收到信号后设置为CANCELED状态和忽略CANCEL信号继续运行; old_state如果不为NULL则存入原来的cancel状态以便恢复。
3、pthread_setcanceltype,取消类型 int pthread_setcanceltype(int type,int *oldtype); 取消类型,是线程对取消信号的响应方式,立即取消或者延时取消,创建线程时默认延时取消; 设置本线程取消动作的执行时机,type有两种取值:PTHREAD_CANCEL_DEFFERED和PTHREAD_CANCEL_ASYCHRONOUS,仅当cancel状态为enable时有效,分别表示收到信号后继续运行至下一个取消点再退出和立即执行取消动作(退出); oldtype,出参,如果不为NULL则存入原来的取消动作类型值。
4、取消点 取消一个线程,它通常需要被取消线程的配合,线程在很多时候会查看自己是否有取消请求,如果有就主动退出,这些查看是否有取消的地方成为取消点; 取消点包括:pthread_join()、pthread_testcancel()、pthread_cond_wait()、pthread_cond_timedwait、sem_wait()、sigwait()、wtite、read等大多数会阻塞的系统调用。
示例:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
void *thread_fun(void *arg)
{
int stateval;
int typeval;
stateval = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
if(stateval != 0)
{
printf("set cancel state failed\n");
}
printf("Im new thread\n");
sleep(4);
printf("about to cancel\n");
stateval = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
if(stateval != 0)
{
printf("set cancel state failed\n");
}
typeval = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);
if(typeval != 0)
{
printf("set cancel type failed\n");
}
printf("first cancel point\n");
printf("second cancel point\n");
return (void *)20;
}
int main()
{
pthread_t tid;
int err,cval,jval;
void *rval;
err = pthread_create(&tid,NULL,thread_fun,NULL);
if(err != 0)
{
printf("create thread failed\n");
return 0;
}
sleep(2);
cval = pthread_cancel(tid);
if(cval != 0)
{
printf("cancel thread failed\n");
}
jval = pthread_join(tid,&rval);
printf("new thread exit code is %d\n",(int *)rval);
return 0;
}
打印 情况一,1号位置PTHREAD_CANCEL_ENABLE设置可以取消,2号位置注释掉,此时遇到printf(“first cancel point\n”)取消点,取消线程 Im new thread about to cancel first cancel point new thread exit code is -1 情况二,1号位置设置PTHREAD_CANCEL_DISABLE不可取消,2号位置注释掉,线程执行完后return20 Im new thread about to cancel first cancel point second cancel point new thread exit code is 20 情况三,1号位置PTHREAD_CANCEL_ENABLE设置可以取消,2号位置设置立即取消,没有等到取消点,立即取消 Im new thread about to cancel new thread exit code is -1
八、信号处理 1、pthread_kill int pthread_kill (pthread_t tid, int sig); 向线程发送signal,大部分signal的默认动作是终止进程的运行,需要用sigaction()去抓信号并加上处理函数; 向指定的线程发送sig信号,如果线程内代码不做处理(没有实现signal处理函数),则整个进程退出,如果需要获得正确的行为,就要在线程内实现sigaction()函数; 如果sig参数不为0,一定要实现线程的信号处理函数,明确要做的事; 如果sig为0,这是一个保留信号,其实并没有发送信号,作用是判断线程是否还在。
示例:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <errno.h>
void *thread_fun(void *arg)
{
sleep(1);
printf("this is new thread\n");
return (void *)0;
}
int main()
{
pthread_t tid;
int err;
int s;
void *rval;
err = pthread_create(&tid,NULL,thread_fun,NULL);
if(err != 0)
{
printf("create new thread failed\n");
return 0;
}
s = pthread_kill(tid,SIGQUIT);
if(s == ESRCH)
{
printf("thread tid is not found\n");
}
pthread_join(tid,&rval);
printf("this is main thread\n");
return 0;
}
情况一,子线程不sleep,主线程sleep(1),pthread_kill(tid,0),不调用pthread_join,主线程sleep等待子线程执行完后再执行pthread_kill(tid,0),此时子线程已结束,打印not found this is new thread thread tid is not found this is main thread 情况二,主线程不sleep,调用pthread_kill(tid,SIGQUIT),pthread_join(tid,&rval),子线程sleep(1),此时主线程发送信号给子线程,但子线程没有处理,进程直接结束,没有打印信息
待续,有时间会继续学习后面的课程
|