0


IO 与进程线程

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 <stdio.h>

int main(int argc, char const *argv[])
{
    // for(int i = 0; i < 300; i++)
    //     printf("%4d", i);
    // while(1);

    // 结构体,stdout _IO_buf_end
    printf("hello");
    printf("%d\n", stdout->_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
       失败或读到文件末尾:EOF
int fputc(int c, FILE * stream)
功能:向文件中写入一个字符
参数:c:要写的字符
   stream:文件流
返回值:成功:写的字符的ASCII
      失败:EOF
#include <stdio.h>

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 <stdio.h>

int main(int argc, const char *argv[])
{
FILE *p;    //定义结构体指针
int ch;
if (argc!=2)   //传入的参数不等于2个的时候进行提示
{
printf("usage:%s <filename>\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;                                      
}
                                                   
                                                   

补充:

int  feof(FILE * stream);
功能:判断文件有没有到结尾
返回:到达文件末尾,返回非零值
int ferror(FILE * stream);
功能:检测文件有没有出错
返回:文件出错,返回非零值
void perror(const char *s);
功能:根据errno打印错误信息
参数:s:要打印的字符串
#include <stdio.h>

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结束读取
int  fputs(const char *s,  FILE *stream);
功能:向文件中写字符串
参数:s:要写的内容
    stream:文件流
返回值:成功:非负整数
       失败:EOF

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

要求:使用fgets实现

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

#include <stdio.h>
#include <string.h>
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 0
            for(int i=0;buf[i]!='\0';i++)
            {
                if(buf[i]=='\n')
                n++;
            }
#endif
      if(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 <stdio.h>
#include <time.h>
#include <string.h>
#include <unistd.h>

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 <stdio.h>
#include <string.h>
#include <stdlib.h>

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 :要读取的文件
返回值:成功:读取对象的个数
      读到文件尾或失败:0
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:按对象写
参数:同上    
返回值:成功:写的元素个数
      失败 :-1
Fread和fwrite函数注意:
1)两个函数的返回值为:读或写的对象数
2)对于二进制数据我们更愿意一次读或写整个结构。
#include <stdio.h>

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 <stdio.h>

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 <stdio.h>

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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

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

cp srcfile newfile -> ./a.out srcfile newfile

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

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

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

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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
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
      失败:-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;  /* 最后状态改变时间 */
    };
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

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);
功能:获得目录流
参数:要打开的目录
返回值:成功:目录流
       失败: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:目录流
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
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 -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

3.2 动态库的制作

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

补充:

头文件:

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

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

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

gcc main.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 <stdio.h>
#include <unistd.h>

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 child
        printf("in the child\n");
    }
    else
    {
        // int s;
        // wait(&s); //回收子进程资源,阻塞函数
        // printf("%d\n", s);
        wait(NULL);
        //in the parent
        printf("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 <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

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 child
        printf("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 parent
        printf("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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>

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 <stdio.h>
#include <sys/types.h>
#include <unistd.h>

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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>

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 <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>

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 <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>

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 结束线程

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

3.3 回收线程

int  pthread_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结束程序。

要求:先输入后输出

提示:标志位 int flag = 0;

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

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 函数

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,函数不阻塞
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <semaphore.h>

char buf[32] = "";
sem_t sem, sem1;
void *print(void *arg)
{
    while (1)
    {
        //P操作:申请资源,-1
        sem_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操作:释放资源, +1
        sem_post(&sem);
        if (strcmp(buf, "quit") == 0)
            break;
    }
    pthread_join(tid, NULL);

    return 0;
}

5.线程互斥

5.1 概念

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

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

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

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

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

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

t1: 循环倒置数组中元素

t2:循环打印数组中元素

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

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

pthread_mtex_init(&lock, NULL);

案例:存钱和取钱的例子

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

#include <stdio.h>
#include <pthread.h>
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_signal(&cond1);
        pthread_mutex_unlock(&lock);
    }
}
int main(int argc, char const *argv[])
{
    pthread_t t;
    if(pthread_create(&t, NULL, getMoney, NULL) != 0)
    {
        perror("create thread err");
        return -1;
    }
    //互斥锁
    pthread_mutex_init(&lock, NULL);
    //条件变量
    if(pthread_cond_init(&cond, NULL) != 0)
    {
        perror("cond init err");
        return -1;
    }
    if(pthread_cond_init(&cond1, NULL) != 0)
    {
        perror("cond init err");
        return -1;
    }
    //存钱
    while(1)
    {
        pthread_mutex_lock(&lock);
        if(money >= 100)
            pthread_cond_wait(&cond1, &lock);
        scanf("%d", &money); //50
        if(money >= 100)
            pthread_cond_signal(&cond); //产生条件
        pthread_mutex_unlock(&lock);  
    }
    pthread_join(t, NULL);
    return 0;
}

六、进程间通信

7种

6种

传统的进程间通信方式:

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

system V IPC对象:

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

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 <stdio.h>
#include <unistd.h>

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); //SIGPIPE
    printf("after\n");

    return 0;
}

1.3 注意事项

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

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

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

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

练习:父子进程通信。

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

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

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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>

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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>

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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>

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     
       失败 -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
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    // kill(getpid(), SIGKILL);

    raise(SIGKILL); //给自己发信号

    // while (1)
    //     ;
    while (1)
        pause(); //将当前进程挂起(阻塞),直到收到信号结束

    return 0;
}

typedef void (*sighandler_t)(int); //typedef unsigned int INT;

typedef void (*)(int) sighandler_t;

sighandler_t signal(int signum, void (*handler)(int) );

void handler(int sig)

{

if(sig == SIGINT)

printf("xxx\n");

else if(sig == SIGQUIT)

}

signal(SIGINT, handler);

signal(SIGQUIT, handler)

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

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

./r srcfile

./w newfile

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

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

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

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

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

司机:父进程

捕捉信号:

忽略信号:

售票员:子进程

捕捉信号:

忽略信号:

./a.out

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

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值
       失败:-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

代码案例:

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 <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <string.h>

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 < 0)
    {
        if (errno == EEXIST)
            shmid = shmget(key, 128, 0666);
        else
        {
            perror("shmget err");
            return -1;
        }
    }
    printf("shmid:%d\n", shmid);
    //映射
    struct msg *p = NULL;
    p = shmat(shmid, NULL, 0); //NULL:系统自动进行映射 0:可读可写
    if(p == (struct msg *)-1)
    {
        perror("shmat err");
        return -1;
    }
    p->flg = 0;
    while(1)
    {
        scanf("%s", p->buf);
        p->flg = 1;
        if(strcmp(p->buf, "quit") == 0)
            break;
    }

    return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <string.h>

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),也叫信号量。它是不同进程间或一个给定进程内部不同线程间同步的机制;System V的信号灯是一个或者多个信号灯的一个集合。其中的每一个都是单独的计数信号灯。而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
       失败:-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);

5.4 命令

ipcs -s:查看信号灯集

ipcrm -s semid:删除信号灯集

例子:

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的信号灯初值设置为10
        sem.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 <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <sys/sem.h>
#include <string.h>

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 <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <sys/sem.h>
#include <string.h>

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 -q msgid :删除消息队列

6.4. 函数接口

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)

例子:

#include <stdio.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <errno.h>
#include <string.h>
#include <sys/msg.h>

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;
}

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

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

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

#include <stdio.h>                     //send.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <errno.h>
#include <string.h>
#include <sys/msg.h>

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 <stdio.h>                               //recieve.c
#include <stdio.h>
#include <sys/types.h>                          
#include <sys/ipc.h>
#include <errno.h>
#include <string.h>
#include <sys/msg.h>

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;
}
标签: c语言 ubuntu linux

本文转载自: https://blog.csdn.net/qq_52049228/article/details/130565655
版权归原作者 代码大魔王ㅤ 所有, 如有侵权,请联系我们删除。

“IO 与进程线程”的评论:

还没有评论