IO 与进程线程

news2024/11/26 2:54:02

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>

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/538958.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

36、Adaptive Forms(4)Create Adapt Form

文章目录 36、Adaptive Forms&#xff08;4&#xff09;Create Adapt FormData Model服务配置创建Adapt Form Template创建Adapt Form 36、Adaptive Forms&#xff08;4&#xff09;Create Adapt Form DataModel创建好后&#xff0c;就可以进行数据的获取和存储。 Data Model…

抖音seo源码开发/技术自研底层逻辑

抖音seo霸屏&#xff0c;是一种专为抖音视频创作者和传播者打造的视频批量剪辑&#xff0c;批量分发产品。使用抖音seo霸屏软件&#xff0c;可以帮助用户快速高效的制作出高质量的优质视频。 使用方法&#xff1a;1. 了解用户的行为习惯 2. 充分利用自身资源进行开发 3. 不…

开心档之Java 基本数据类型

Java 基本数据类型 目录 Java 基本数据类型 内置数据类型 实例 实例 类型默认值 实例 引用类型 Java 常量 自动类型转换 自动类型转换 实例 强制类型转换 实例 隐含强制类型转换 变量就是申请内存来存储值。也就是说&#xff0c;当创建变量的时候&#xff0c;需…

4.5.1 虚拟局域网(一)

4.5.1 虚拟局域网&#xff08;一&#xff09; 一、虚拟局域网的划分 虚拟局域网的划分是非常灵活的&#xff0c;可以根据端口进行划分&#xff0c;也可以根据MAC地址进行划分&#xff0c;也可以根据网络层协议进行划分甚至根据IP组播进行划分。 &#xff08;一&#xff09;基…

百度工程师移动开发避坑指南——内存泄漏篇

作者 | 启明星小组 在日常编写代码时难免会遇到各种各样的问题和坑&#xff0c;这些问题可能会影响我们的开发效率和代码质量&#xff0c;因此我们需要不断总结和学习&#xff0c;以避免这些问题的出现。接下来我们将围绕移动开发中常见问题做出总结&#xff0c;以提高大家的开…

【Prompting】ChatGPT Prompt Engineering开发指南(6)

ChatGPT Prompt Engineering开发指南&#xff1a;Expanding/The Chat Format Expanding自定义对客户电子邮件的自动回复提醒模型使用客户电子邮件中的详细信息 The Chat Format总结内容来源 在本教程中&#xff0c;第一部分学习生成客户服务电子邮件&#xff0c;这些电子邮件是…

前端(HTML)

网络传输三大基石:URL,HTTP,HTML 前端使用URL利用HTTP协议去向服务器端发送请求某个资源&#xff0c;服务器端响应浏览器一个HTML页面&#xff0c;浏览器对HTML页面解析 HTML的标准结构: 【1】先用普通文本文档新建一个文本&#xff0c;将文本的后缀改为.html 或者 .htm 我…

chatgpt赋能Python-mac版的python怎么用

Mac版Python的使用指南 Python是一种高级编程语言&#xff0c;常用于Web开发、数据分析、机器学习等领域。在Mac系统上&#xff0c;Python的安装和使用非常方便。本文将介绍如何在Mac上安装和使用Python并演示几个常见的Python用例。 Python在Mac上的安装 Mac电脑自带Python…

springboot+jsp+javaweb学生信息管理系统 05hs

springboot是基于spring的快速开发框架, 相比于原生的spring而言, 它通过大量的java config来避免了大量的xml文件, 只需要简单的生成器便能生成一个可以运行的javaweb项目, 是目前最火热的java开发框架 &#xff08;1&#xff09;管理员模块&#xff1a;系统中的核心用户是管…

蓝牙耳机什么牌子的好用?发烧友实测2023年蓝牙耳机排名

从AirPods入坑蓝牙耳机开始&#xff0c;断断续续已经买过二十多款蓝牙耳机了&#xff0c;我每天都会逛逛数码板块&#xff0c;最近看到了2023年蓝牙耳机排名&#xff0c;为检验是否名副其实&#xff0c;我购入了排名前五的品牌进行了一个月的测试&#xff0c;接下来我就来分享一…

k8s系列(四)——资源对象

k8s系列四——资源对象 pod概念 思考&#xff1a;为什么k8s会引出pod这个概念&#xff0c;容器不能解决么&#xff1f; 我的理解&#xff1a;一组密切相关的服务使用容器的话&#xff0c;如果他们的镜像不在一个容器里的话&#xff0c;那么就需要配置反向代理进行通信&#xf…

Packet Tracer – 配置中继

Packet Tracer – 配置中继 地址分配表 设备 接口 IP 地址 子网掩码 交换机端口 VLAN PC1 NIC 172.17.10.21 255.255.255.0 S2 F0/11 10 PC2 NIC 172.17.20.22 255.255.255.0 S2 F0/18 20 PC3 NIC 172.17.30.23 255.255.255.0 S2 F0/6 30 PC4 NIC 1…

java 根据指定字段排序(mysql)

需求&#xff1a; 查询数据的时候&#xff0c;由前端指定字段和排序方式进行排序。 这时候要怎么做呢&#xff1f; 要定义一个相应的类&#xff0c;排序的时候&#xff0c;是动态拼接的。 要考虑多个字段&#xff0c;不同排序方式的情况。 处理 OrderField import io.swagge…

基于matlab的ADC输入动态范围测量代码

如图&#xff0c;本文主要分享基于matlab的ADC输入数据有效位分析的代码。 fidfopen(C:\Users\Administrator\Desktop\Test.txt,r); % numptinput(Data Record Size (Number of Points)? );% fclkinput(Sampling Frequency (MHz)? ); numpt16384; fclk50; numbit14; [v1]fs…

SDK案例记录

目前的极简配置 注意事项 默认的属性配置中&#xff0c;大多采用环境变量的形式&#xff0c;方便不同设备通用 比如“常规”->“输出目录”为 $(SolutionDir)..\bin\win_msvc2017$(Platform)\$(Configuration)\案例运行前的配置&#xff08;除MwBatchSimPlugin&#xff0…

华丽家族股东大会21项议案全被否

5月17日晚间&#xff0c;A股上市公司华丽家族发布关于收到上海证券交易所监管工作函的公告&#xff0c;交易所对相关事项提出监管要求。 在此之前&#xff0c;华丽家族当天召开股东大会&#xff0c;21项股东大会议案全部未通过。历史上&#xff0c;股东大会议案全部被否的情形…

『python爬虫』24. selenium之无头浏览器-后台静默运行(保姆级图文)

目录 1. 无头浏览器2. 分析被爬取数据的网页结构3. 完整代码总结 欢迎关注 『python爬虫』 专栏&#xff0c;持续更新中 欢迎关注 『python爬虫』 专栏&#xff0c;持续更新中 1. 无头浏览器 一般性的selenium会打开浏览器页面&#xff0c;展示图形化页面给我们看&#xff0c;我…

Spring Boot注入Servlet、Filter、Listener 注解方式和使用RegistrationBean二种方式 加源码分析

目录 Spring Boot 注入Servlet、Filter、Listener 官方文档 基本介绍 应用实例1-使用注解方式注入 创建/Servlet_.java 修改Application.java , 加入ServletComponentScan 完成测试 创建Filter_.java 创建static/css/t.css, 作为测试文件 完成测试, 注意观察后台 注…

【数据结构】--- 博主拍了拍你并向你扔了一“棵”二叉树(概念+结构)

文章目录 前言&#x1f31f;一、树概念及结构&#xff1a;&#x1f30f;1.1树的概念&#xff1a;&#x1f30f;1.2树的相关概念&#xff1a;&#x1f30f;1.3树的表示&#xff1a;&#x1f4ab;1.3.1左孩子右兄弟表示法&#xff1a;&#x1f4ab;1.3.2双亲表示法&#xff1a; &…

Golang每日一练(leetDay0069) 数字范围按位与、快乐数

目录 201. 数字范围按位与 Bitwise-and-of-numbers-range &#x1f31f;&#x1f31f; 202. 快乐数 Happy Number &#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Rust每日一练 专栏 Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每…