课程知识点索引

  • 一.命令行解析
  • 1.getopt函数的介绍使用,形参的意义,如何使用它实现ls -a 和ls -l的解析
  • 2.open,read,write,close,fopen,fread,fwrite,fclose,fseek,opendir,readdir函数
  • 二.文件与目录基本操作
  • 1.在vim打开的程序文件中,光标定位到某个函数时按下shift+K可以打开该函数的man介绍页
  • 2.通过软链接实现大部分标准头文件集合在一个head.h文件中来使用
  • 三四、高级IO,主要讲了select()函数监控文件描述符还有同步IO
  • select、epoll
  • 5多进程
  • 6进程体系与进程管理
  • C和C++在进程空间有什么不同?
  • 僵尸进程与孤儿进程
  • 退出进程
  • 进程组、进程的用户和组
  • 进程ID
  • 守护进程
  • 7-1 共享内存
  • 原子锁概念、shmget()、ftok()、shmat
  • 8-条件变量,互斥锁
  • pthread_cond_wait内部具有某些原子性


一.命令行解析

1.getopt函数的介绍使用,形参的意义,如何使用它实现ls -a 和ls -l的解析

getopt函数详解

optind这个参数代表的就是传入参数下一个要被getoption读取的argv字符串数组的下标。

第一次读取argv时会将里面的带-的选项调整顺序放在前面,不需要选项的参数放在最后。如下:将 -a t -b r 顺序调整为-a -b r t

android高级编程第4版 编程高级教程_#include

如下是按照opstring=“ab:c::”;来匹配字母的,
选项为-b时因为要有参数,所以会将选项后的参数一起读,然后argv[3]
之前的都被一次读完,后面的第四个就是接下来循环需要读的

如果不需要选项参数的选项后面跟有选项参数,那么会getoption读取,放在optopt里,并返回的是?如下图

选项-a时因为不需要参数,所以

android高级编程第4版 编程高级教程_#include_02

2.open,read,write,close,fopen,fread,fwrite,fclose,fseek,opendir,readdir函数

带f的是经过了一层封装,带有缓存,在用户层,不带的是系统调用在内核层
open与fopen的区别

二.文件与目录基本操作

1.在vim打开的程序文件中,光标定位到某个函数时按下shift+K可以打开该函数的man介绍页

2.通过软链接实现大部分标准头文件集合在一个head.h文件中来使用

过程如下:

android高级编程第4版 编程高级教程_#include_03


将test.c中的头文件如下:添加到common目录下的head.h头文件里

android高级编程第4版 编程高级教程_子进程_04


test.c改为:

android高级编程第4版 编程高级教程_#include_05


通过软链接将common目录链接到程序所在文件夹下,后面再通过指定头文件位置来引用head.h文件里的头文件

三四、高级IO,主要讲了select()函数监控文件描述符还有同步IO

写、读都是高级IO
一般write向磁盘中写入内容时,不是立即就会写入磁盘中,在内核里会有缓存,到了一定的大小才会统一向磁盘写入,这就会导致

一般会有用户层面的缓冲,内核的缓冲和磁盘驱动层的缓冲

写文件的时候会经历以下这些个缓存:
用户缓存–>标准IO库缓存–>内核缓存–>磁盘缓存

读文件就是反过来的一些操作

高级IO有:同步IO、立即IO、标准IO(也叫缓冲IO)、非阻塞IO

,如下man 2 open中

android高级编程第4版 编程高级教程_android高级编程第4版_06


android高级编程第4版 编程高级教程_子进程_07

像stdio中引用的函数printf、scanf等都是带有标准IO的,printf是行缓冲
标准IO会进行两次拷贝,从用户空间到标准IO中一次,再从标准IO到内核又一次
open是直接从用户空间到内核空间,fopen和标准IO一样

这里介绍一个命令dd,可以从一个文件下考文件到另一个文件下,可以指定拷贝时缓存IO的大小,从而用来测试IO的速度
下面为例,随着缓冲区bs越来越大,拷贝的时间也就越来越快,但后面又变大是因为文件系统底层的block最大就只有4096,就是按着一个一个block块来存放文件的

android高级编程第4版 编程高级教程_android高级编程第4版_08

非阻塞IO用法:

android高级编程第4版 编程高级教程_子进程_09


通过下面这个命令就可以读取到上述输入的值

android高级编程第4版 编程高级教程_网络_10


下面这个通过定义了两个api来设定scanf是阻塞还是非阻塞,定义为非租塞且没有读到数据时返回的错误是EAGIAN

android高级编程第4版 编程高级教程_linux_11

android高级编程第4版 编程高级教程_子进程_12

select、epoll

android高级编程第4版 编程高级教程_网络_13


这里的read without blocking 是说读的时候不会被阻塞与读函数设置为非阻塞状态再去读是不一样的

这个意思就是读的时候被读的内容已经在了,就直接可以读取,不会被挂起

select()也叫IO多路复用,可以监控多个文件的状态,可读可写时(也就是上述的文件IO处于ready)可以通过select()返回值来感知到,一般用于上层应用来感知底层文件的变化

select()的算法复杂度为n^2,先在用户态设置好数据结构,然后通过select传进内核,在内核态中运转监控文件状态,发现变化后,再从内核态返回到用户态告诉用户,在用户态和内核态之间来回跳转比较费时间,效率不高

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

int main(void)
{
    fd_set rfds;
    struct timeval tv;
    int retval;

    /* Watch stdin (fd 0) to see when it has input. */

    FD_ZERO(&rfds);
    FD_SET(0, &rfds);

    /* Wait up to five seconds. */

    tv.tv_sec = 5;
    tv.tv_usec = 0;

    retval = select(1, &rfds, NULL, NULL, &tv);
    /* Don't rely on the value of tv now! */

    if (retval == -1)
        perror("select()");
    else if (retval)
        printf("Data is available now.\n");
        /* FD_ISSET(0, &rfds) will be true. */
    else
        printf("No data within five seconds.\n");

    exit(EXIT_SUCCESS);
}

上面这个程序运行结果如下:

android高级编程第4版 编程高级教程_linux_14


就是select监控到了输入文件有数据输入,但是并没有读取该操作所以2就在标准输入I的缓存中,和直接在命令行输入一个2按回车是一样的

epoll和select是差不多的用法,底层是用红黑树写的,性能及其强悍

5多进程

写拷贝技术,fork一个子进程时并不会将所有数据都重新在拷贝,只有在数据有变化时才会重新拷贝

fork子进程后可以变身(就是将从父进程的拷贝过来的运行代码变为新的程序代码)

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>

int main(int argc, char const *argv[])
{
    pid_t pid =0;
    if ((pid =fork())<0)
    {
        perror("fork()");
    }
    if (pid)
    {
        printf("this is parent process--<%d>--<%d>--<%d>\n",getppid(),getpid(),pid);
    }
    else
    {
        printf("this is child process--<%d>--<%d>\n", getppid(), getpid());
    }
    
    
    return 0;
}

结果如下:

android高级编程第4版 编程高级教程_linux_15


原因是父进程先于子进程执行完毕,子进程成为孤儿进程,就会被高级进程收养生成10个子进程的程序,如下:

android高级编程第4版 编程高级教程_#include_16


运行结果

android高级编程第4版 编程高级教程_linux_17


父进程必须在子进程全部结束后在结束,不然子进程

子进程结束了,但父进程没有结束也没有用wait(),那么子进程就会成为僵尸进程,所占的资源就不会被释放

所以要用wait来通知父进程相应的子进程该释放资源了

代码例子如下:

android高级编程第4版 编程高级教程_网络_18


下面就是用exec函数族来实现进程变身,子进程在执行了exec()函数后会将从父进程那拷贝过来的代码段全部替换为cat程序的代码段,所以下面这个程序根本就不会执行printf和后面的代码

android高级编程第4版 编程高级教程_网络_19

作业:用多进程的方式写一个run程序,run a.c 可以编译并执行a.c文件,如果a.c不存在,则先调用vim创建并打开一个a.c文件,然后退出后在编译并执行该文件

6进程体系与进程管理

进程空间中包含哪些东西:

文件、用户、组
上下文等

fd分配原则是:找到当前没有分配的数其中最小的那一个
pid分配是: 每次都当前往后加一个,用完了在循环回来

android高级编程第4版 编程高级教程_子进程_20

C和C++在进程空间有什么不同?

僵尸进程与孤儿进程

android高级编程第4版 编程高级教程_android高级编程第4版_21

退出进程

android高级编程第4版 编程高级教程_android高级编程第4版_22

进程组、进程的用户和组

android高级编程第4版 编程高级教程_子进程_23

进程ID

android高级编程第4版 编程高级教程_android高级编程第4版_24


android高级编程第4版 编程高级教程_linux_25

守护进程

android高级编程第4版 编程高级教程_网络_26


第三步为什么要设置一个新的进程组合会话?

举个例子:

我们用ssh连接了一个远程服务器,在服务器上开启了几个进程放在后台运行,结果我关闭了ssh连接后,服务器后台运行的进程也关闭了,这个就是因为,开启的那几个进程是sshd连接进程的子进程(他们之间是有关联的)属于同一个进程组和会话,所以在关闭连接后还想让进程在服务器后台运行,则得重新设置进程组和会话,以及改变上下文

android高级编程第4版 编程高级教程_linux_27


android高级编程第4版 编程高级教程_子进程_28


内核2.6之前的调度方式如下:

android高级编程第4版 编程高级教程_android高级编程第4版_29

android高级编程第4版 编程高级教程_android高级编程第4版_30

android高级编程第4版 编程高级教程_#include_31


android高级编程第4版 编程高级教程_网络_32

android高级编程第4版 编程高级教程_linux_33


android高级编程第4版 编程高级教程_linux_34

7-1 共享内存

原子锁概念、shmget()、ftok()、shmat

调试命令ipcs
ipcrm

以下为两个可执行程序访问同一个共享内存
也就是两个没有直系关系的进程访问同一个共享内存
实现的结果是,一个终端输入的msg在另一个终端打印出来,互相打印出对方输入的msg

/*************************************************************************
	> File Name: 2.shm_1.c
	> Author: suyelu
	> Mail: suyelu@126.com
	> Created Time: Sun 28 Mar 2021 04:36:58 PM CST
 ************************************************************************/
 //Usage : ./a.out -t 1|2 -m msg
#include "stdio.h"
#include "stdlib.h"
#include "sys/ipc.h"
#include "sys/types.h"
#include "sys/shm.h"
#include "string.h"
#include "unistd.h"
#include "errno.h"

struct SHM {
    int flag;
    int type;
    char msg[512];
};

int main(int argc, char **argv) {
    int opt;
    struct SHM shm_data;
    memset(&shm_data, 0, sizeof(shm_data));
    if (argc != 5) {
        fprintf(stderr, "Usage : %s -t 1|2 -m msg.\n", argv[0]);
        exit(1);
    }
    while ((opt = getopt(argc, argv, "t:m:")) != -1) {
        switch (opt) {
            case 't':
                shm_data.type = atoi(optarg);
                break;
            case 'm':
                strcpy(shm_data.msg, optarg);
                break;
            default:
                fprintf(stderr, "Usage : %s -t 1|2 -m msg.\n", argv[0]);
                exit(1);
        }
    }
    key_t key;
    int shmid;
    struct SHM *share_memory = NULL;
    key = ftok("./shm.c", 328);
    if ((shmid = shmget(key, sizeof(struct SHM), IPC_CREAT | IPC_EXCL | 0600)) < 0){   //创建共享存储空间,并返回一个共享存储标识符,0600是所有用户都可读可写可执行
        if (errno == EEXIST) {                                              //如果共享存储空间已经存在
            printf("shm exist!\n");
	          if ((shmid = shmget(key, sizeof(struct SHM), 0600)) < 0) { //flag只有0600表示用户权限,其它都为NULL,则如果已经存在key所对应的共享内存,则返回该内存的shm_id
            	   perror("shmat");
                   exit(1);
            }


        } else {                                                             //其它错误,直接退出
            perror("shmget");
            exit(1);
        }
    } else {                                                               //共享存储空间创建成功
        printf("Success!\n");



    }
    if ((share_memory = (struct SHM *)shmat(shmid, NULL, 0)) < 0) {    //将共享存储空间映射到当前程序进程空间的某个位置
            perror("shmat");
            exit(1);
    }
    while (1) {
        if (!share_memory->flag) {
            printf("<%d> : get shm data\n", shm_data.type);
            share_memory->flag = 1;
            sprintf(share_memory->msg, "<%d> : %s", shm_data.type, shm_data.msg);
            sleep(1);
        } else {
            printf("%s\n", share_memory->msg);
            share_memory->flag = 0;
        }
    }


    return 0;
}

8-条件变量,互斥锁

android高级编程第4版 编程高级教程_子进程_35

下面这个

android高级编程第4版 编程高级教程_子进程_36

android高级编程第4版 编程高级教程_linux_37

android高级编程第4版 编程高级教程_android高级编程第4版_38


android高级编程第4版 编程高级教程_网络_39


下面是上述程序的流程图,有一个女神线程和多个追求者线程

实现的效果是打开一个终端运行女神程序,再打开其它几个终端运行程序追求者,然后在女神终端输入一个数字,在追求者终端中随机会有一个打印出相应数字

android高级编程第4版 编程高级教程_网络_40

从上述流程图可以看到搞清楚为什么pthread_cond_wait?

1.传入后解锁mutex

其它线程可以获得锁,进而进入pthread_cond_wait等待直到所有线程等待,并且其它线程可以改变进入pthread_cond_wait的判断条件

2. 返回前再次锁mutex

android高级编程第4版 编程高级教程_linux_41


android高级编程第4版 编程高级教程_网络_42


 

pthread_cond_wait内部具有某些原子性

android高级编程第4版 编程高级教程_android高级编程第4版_43