1. 1. Ubuntu和CentOS
    1. 1.0.1. Linux系统目录:
    2. 1.0.2. Linux系统文件和目录权限
  2. 1.1. 软连接:快捷方式
  3. 1.2. 硬链接:
  4. 1.3. 创建用户:
    1. 1.3.1. 修改文件所属用户:
    2. 1.3.2. 删除用户:
    3. 1.3.3. 创建用户组:
    4. 1.3.4. 修改文件所属用户组:
    5. 1.3.5. 删除组:
    6. 1.3.6. 使用chown 一次修改所有者和所属组:
  5. 1.4. 一些查看和操作命令
    1. 1.4.1. find命令:找文件
    2. 1.4.2. 管道运算符 |
    3. 1.4.3. awk 拆分
    4. 1.4.4. touch 创建文件
    5. 1.4.5. man命令
  6. 1.5. 软件安装:
    1. 1.5.1. 软件源:
      1. 1.5.1.1. 这是在线安装,同时也支持离线安装
      2. 1.5.1.2. PS:
    2. 1.5.2. 压缩包
  7. 1.6. GCC编译器
    1. 1.6.1. objdump反汇编
  8. 1.7. 静态库和动态库
    1. 1.7.1. 静态库
      1. 1.7.1.1. 制作过程:
      2. 1.7.1.2. 使用
      3. 1.7.1.3. 缺点:
    2. 1.7.2. 动态库
      1. 1.7.2.1. 制作过程
      2. 1.7.2.2. 动态库测试
  9. 1.8. GDB调试器
  10. 1.9. makefile
    1. 1.9.0.1. 最简单的makefile文件
    2. 1.9.0.2. make -n 测试
  • 1.10. 系统调用
  • 1.11. 错误处理
  • 1.12. 文件操作
  • 2. 多进程
    1. 2.0.1. ps
    2. 2.0.2. 父子进程
      1. 2.0.2.1. 函数
      2. 2.0.2.2. 读时共享 写时拷贝
      3. 2.0.2.3. 可以通过valgrind命令查看不同进程的释放和分配
      4. 2.0.2.4. gdb调试
      5. 2.0.2.5. wait
      6. 2.0.2.6. exit
      7. 2.0.2.7. 孤儿进程
      8. 2.0.2.8. 僵尸进程
    3. 2.0.3. 进程替换
  • 2.1. 进程间通讯
    1. 2.1.1. 无名管道
      1. 2.1.1.1. pipe
      2. 2.1.1.2. 管道的读写特点:
      3. 2.1.1.3. ulimit -a 查看管道缓冲区大小
      4. 2.1.1.4. 设置为非阻塞的方法
    2. 2.1.2. 有名管道
      1. 2.1.2.1. FIFO文件
      2. 2.1.2.2. 管道的读写特点:
    3. 2.1.3. 共享存储映射
    4. 2.1.4. 匿名映射
  • 2.2. 信号
    1. 2.2.1. 信号的编号
    2. 2.2.2. 信号四要素
    3. 2.2.3. 信号状态
      1. 2.2.3.1. 阻塞信号 相当于黑名单
      2. 2.2.3.2. 未决信号 相当于所有未接电话
      3. 2.2.3.3. 设置屏蔽信号集
    4. 2.2.4. 生成信号
      1. 2.2.4.1. kill
      2. 2.2.4.2. raise
      3. 2.2.4.3. abort
      4. 2.2.4.4. alarm
      5. 2.2.4.5. setitimer
    5. 2.2.5. 捕获信号
      1. 2.2.5.1. sign函数
      2. 2.2.5.2. sigaction
  • 3. 守护进程(线程)
    1. 3.1. 会话
    2. 3.2. 守护进程
  • 4. 线程
  • 5. 线程同步
    1. 5.1. 互斥锁
    2. 5.2. 死锁
    3. 5.3. 读写锁
    4. 5.4. 条件变量
    5. 5.5. 信号量
  • Linux系统编程

    记录一下忘记了过来查

    Ubuntu和CentOS

    Ubuntu系统和CentOS都是Linux系统

    CentOS可以直接安装换系统,也可以通过虚拟机安装得到

    Ubuntu可以在电脑上同时使用Linux和Windows系统

    Linux系统: “所见皆文件”

    • 方便性:使计算机系统易于使用
    • 有效性:以更有效的方式使用计算机系统资源
    • 扩展性:方便用户有效开发、测试和引进新功能
    • 开放性:所谓开放性,是指系统能遵循世界标准规范,特别是遵循开放系统互连OSI 国际标准。

    Linux系统目录:

    bin:存放二进制可执行文件
    
    boot:存放开机启动程序
    
    dev:存放设备文件: 字符设备、块设备
    
    home:存放普通用户
    
    etc:用户信息和系统配置文件 passwd、group
    
    lib:库文件:libc.so.6
    
    root:管理员宿主目录(家目录)
    
    usr:用户资源管理目录
    

    Linux系统文件类型: 7/8 种

    普通文件:-
    
    目录文件:d
    
    字符设备文件:c
    
    块设备文件:b
    
    软连接:l
    
    管道文件:p
    
    套接字:s
    
    未知文件。
    

    Linux系统文件和目录权限

    文件权限细分
    权限细节总共分为10个槽位

    1701699914895

    举例:drwxr-xr-x,表示:
    •这是一个文件夹,首字母d表示
    •所属用户(右上角图序号2)的权限是:有r有w有x,rwx
    •所属用户组(右上角图序号3)的权限是:有r无w有x,r-x (-表示无此
    权限)
    •其它用户的权限是:有r无w有x,r-x

    文件类型 所有者的读写可视型 同组用户的读写可实行 其他的读写可实行

    r是读 数字是4

    w是写 2

    x是执行 1

    数字的细节如下:r记为4,w记为2,x记为1,可以有:
    •0:无任何权限, 即 —
    •1:仅有x权限, 即 –x
    •2:仅有w权限 即 -w-
    •3:有w和x权限 即 -wx
    •4:仅有r权限 即 r–
    •5:有r和x权限 即 r-x
    •6:有r和w权限 即 rw-
    •7:有全部权限 即 rwx
    举例: 751所表示的权限为: rwx(7) r-x(5) –x(1)

    默认读写读写读写 -rw-rw-rw 其文件权限掩码为666

    原来的掩码通过umask指令得到 一般是0002转二进制就是0010 即只有其他用户有写权限

    想要修改为最大权限的话,就是0666-0002=0664,实际上虽然可以这样计算,但其实是转二进制之后按位与非 06666 & ~0002 = 0664

    而对于文件夹的命令 最大权限是0777 umask是0002,按照上述计算,得到0775

    想要直接设置文件权限的话,在终端输入umask 0,再运行umask查看文件权限,会得到0000

    软连接:快捷方式

    为保证软连接可以任意搬移, 创建时务必对源文件使用 绝对路径。
    

    硬链接:

    ln file  file.hard
    
    操作系统给每一个文件赋予唯一的 inode,当有相同inode的文件存在时,彼此同步。
    
    删除时,只将硬链接计数减一。减为0时,inode 被释放。
    

    创建用户:

    sudo adduser 新用户名		--- useradd
    

    修改文件所属用户:

    sudo chown 新用户名 待修改文件。
    
    sudo chown wangwu a.c
    

    删除用户:

    sudo deluser 用户名
    

    创建用户组:

    sudo addgroup 新组名
    

    修改文件所属用户组:

    sudo chgrp 新用户组名 待修改文件。
    
    sudo chgrp g88 a.c
    

    删除组:

    sudo delgroup 用户组名
    

    使用chown 一次修改所有者和所属组:

    sudo chown 所有者:所属组  待操作文件。
    

    一些查看和操作命令

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    pwd 查看当前文件目录
    cd ~ - ..
    rm -rf 连带子文件夹一块删除
    cp test1 ./pipe/test 复制
    mkdir ~/test/pythread 在~/test路径下创建名为pythread文件夹

    vim ——Insert —— Esc :wq :q!
    bc 计算器命令 直接敲bc回车就行

    ulimit -a
    netstap -apn | grep 8000 查看端口号位8000的网络连接状态

    1704185502003

    当前系统默认打开19万个文件,但是栈限制打开500个

    堆每个指令栈8192bit 8k

    bit 位

    byte 字节

    1byte = 8 bit

    1K = 1024 bit = 2^10 bit

    1M = 2^10 K

    1G = 2^10 M

    想要修改文件打开最大个数 就ultimate -n 1024

    如果想修改别的 可以看括号里的值 后面加上自己想改的数字

    find命令:找文件

    -type 按文件类型搜索  d/p/s/c/b/l/ f:文件
    
    -name 按文件名搜索
    
        find ./ -name "*file*.jpg"
    
    -maxdepth 指定搜索深度。应作为第一个参数出现。
    
        find ./ -maxdepth 1 -name "*file*.jpg"
    
    
    -size 按文件大小搜索. 单位:k、M、G
    
        find /home/itcast -size +20M -size -50M
    
    -atime、mtime、ctime 天  amin、mmin、cmin 分钟。
    
    -exec:将find搜索的结果集执行某一指定命令。
    
        find /usr/ -name '*tmp*' -exec ls -ld {} \;
    
    -ok: 以交互式的方式 将find搜索的结果集执行某一指定命令
    
    1
    2
    3
    4
    find ../ -size +21k -size -23k exec ls -l {} \;
    在大括号之前存放找到文件之后执行的命令(查看详细信息)
    相当于 +管道| +xargs + 找到文件之后执行的命令
    find ../ -size +21k -size -23k | xargs ls -l {}

    管道运算符 |

    1
    2
    find ./ -maxdepth -type f -exec ls -l {} \;
    find ./ -maxdepth -type f | -xargs ls -l

    两者都是找到文件之后执行

    但是exec是所有命令一股脑执行

    xargs当结果比较大的时候分开处理,分区,处理完第一片再处理第二片,但是对于文件名中有空格的文件会误拆分

    1701949684490

    此时需要用

    1
    find ./ -maxdepth -type f -print0| -xarg -0 ls -l

    第一个-print是find的参数

    xargs拆分原来是用空格作为区分依据的现在改成0

    -xargs:将find搜索的结果集执行某一指定命令。 当结果集数量过大时,可以分片映射。

    ​ find /usr/ -name ‘tmp‘ | xargs ls -ld

    -print0:
    find /usr/ -name ‘tmp‘ -print0 | xargs -0 ls -ld

    awk 拆分

    awk 行拆分

    sed 列拆分

    很复杂暂时不看

    grep命令:找文件内容

    grep -r 'copy' ./ -n
    
        -n参数::显示行号
    
    ps aux | grep 'cupsd'  -- 检索进程结果集。
    

    touch 创建文件

    1
    2
    3
    touch 'abd.h'
    touch 'a.c'
    touch 'abc xyz'

    man命令

    1
    man fopen

    查看fopen的说明 相当于help

    软件安装:

    CentOS的软件安装工具不是apt-get ,而是yum,Ubuntu使用apt-get

    1
    2
    3
    4
    5
    1. 联网

    2. 更新软件资源列表到本地。 sudo apt-get,yum update

    3. 安装 sudo apt-get/yum install 软件名

    软件源:

    一般是国内的服务器从国外的服务器下载安装软件,然后用户从国内的服务器上下载软件安装,在下载的同时会更新国内服务器的软件列表(2)

    1701951815880

    4. 卸载	sudo apt-get/yum remove 软件名
    
    5. 使用软件包(.deb) 安装:	sudo dpkg -i 安装包名。
    

    这是在线安装,同时也支持离线安装

    1701952056166

    Linux和fred bsd

    Linux 两大分支

    参考的这个 https://www.cnblogs.com/garinzhang/p/diff_between_yum_apt-get_in_linux.html

    RedHat系列:Redhat、Centos、Fedora等

    Debian系列:Debian、Ubuntu等

    RedHat 系列

    • 常见的安装包格式 rpm包,安装rpm包的命令是“rpm -参数”
    • 包管理工具 yum
    • 支持tar包

    Debian系列

    • 常见的安装包格式 deb包,安装deb包的命令是“dpkg -参数”
    • 包管理工具 apt-get
    • 支持tar包

    .deb 安装包 相当于.exe文件

    安装安装包

    1
    sudo dpkg -i sl_3.03-17_amd64.deb

    删除安装包

    1
    sudo dpkg -r sl_3.03-17_amd64.deb

    -r是递归的意思

    源码包安装:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    1.解压缩源代码包
    2. cd dir
    3. ./configure
    检测文件是否缺失,创建Makefile(用于编译当前程序),检测编译环境
    4. make
    编译源码,生成库和可执行程序
    5. sudo make install
    把库和可执行程序,安装到系统路径下6. sudo make distclean
    删除和卸载软件

    PS:

    安装的时候yum出错了,先是报这个错

    1
    failure: repodata/repomd.xml from dag: [Errno 256] No more mirrors to try.

    再是报这个错

    1
    Error: File contains parsing errors: file:///etc/yum.repos.d/docker-ce.repo

    最后是按照这个帖子记录的解决的

    1
    https://blog.csdn.net/weixin_39137153/article/details/125454385

    看不懂,但是解决了。记录一下后面知道的多了再回来看看

    按照这个链接安装rar

    1
    https://blog.csdn.net/qq_21875331/article/details/110948805

    但是在线链接还是不行

    改离线安装

    压缩包

    tar压缩:

    1. tar -zcvf 要生成的压缩包名	压缩材料。
    z: 以zip的方式进行压缩
    c: 创建打包文件
    v: 显示压缩过程 
    f: file  指定档案文件名称
    t: 列出档案中的文件
    x:解压
        tar zcvf  test.tar.gz  file1 dir2   使用 gzip方式压缩。
    


    ​ 也可以 tar zcvf test.mp3 file1 dir2 会成功,但是不利于我们文件管理
    ​ tar.gz表示用tar命令以gzip的方式进行压缩

    1
    tar jcvf  test.tar.gz  file1 dir2   使用 bzip2方式压缩。  这个跟gzip压缩方式一样,也不能压缩目录,也不能同时压缩多个文件?  明明吧这些文件压缩到了test的压缩包里

    1701952830500

    tar解压:

    将 压缩命令中的 c --> x
    
        tar zxvf  test.tar.gz   使用 gzip方式解压缩。
    
        tar jxvf  test.tar.gz   使用 bzip2方式解压缩。
    

    rar压缩:

    rar a -r  压缩包名(带.rar后缀) 压缩材料。
    
        rar a -r testrar.rar	stdio.h test2.mp3
    

    rar解压:

    unrar x 压缩包名(带.rar后缀)
    

    zip压缩:

    zip -r 压缩包名(带.zip后缀) 压缩材料。
    
        zip -r testzip.zip dir stdio.h test2.mp3
    

    zip解压:

    unzip 压缩包名(带.zip后缀) 
    
        unzip  testzip.zip 
    

    GCC编译器

    gcc -E S c

    Hello.c .i .s .o .out

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    deng@itcast:~/share/3rd/1gcc$ ls 1hello.c

    第一步: 进行预处理

    deng@itcast:~/share/3rd/1gcc$ gcc -E 1hello.c -o 1hello.i

    第二步: 生成汇编文件

    deng@itcast:~/share/3rd/1gcc$ gcc -S 1hello.i -o 1hello.s

    第三步: 生成目标代码

    deng@itcast:~/share/3rd/1gcc$ gcc -c 1hello.s -o 1hello.o

    第四步: 生成可以执行文件
    deng@itcast:~/share/3rd/1gcc$ gcc 1hello.o -o 1hello

    第五步: 执行 deng@itcast:~/share/3rd/1gcc​$ ./1hello
    得到hello itcast

    或者直接将源文件生成一个可以执行文件

    deng@itcast:/share/3rd/1gcc$ gcc 1hello.c -o 1hello deng@itcast:/share/3rd/1gcc$ ./1hello hello itcast

    如果不指定输出文件名字, gcc编译器会生成一个默认的可以执行a.out

    deng@itcast:/share/3rd/1gcc$ gcc 1hello.c
    deng@itcast:
    /share/3rd/1gcc$ ls 1hello 1hello.c 1hello.i 1hello.o 1hello.s a.out deng@itcast:~/share/3rd/1gcc$ ./a.out
    hello itcast

    gcc常用选项

    选项 作用
    -o file 指定生成的输出文件名为file
    -E 只进行预处理
    -S(大写) 只进行预处理和编译
    -c(小写) 只进行预处理、编译和汇编
    -v / –version 查看gcc版本号
    -g 包含调试信息
    -On n=0~3 编译优化,n越大优化得越多
    -Wall 提示更多警告信息
    -D 编译时定义宏

    测试程序(-D选项):

    deng@itcast:~/test$ gcc 2test.c -DSIZE=10

    deng@itcast:~/test$ ./a.out

    SIZE: 10

    1
    2
    3
    4
    5
    6
    #include <stdio.h>
    int main(void)
    {
    printf("SIZE: %d\n", SIZE);
    return 0;
    }

    objdump反汇编

    objdump -D a.out

    1704458658980

    1704458920024

    静态库和动态库

    在经过编译之后的一些可执行程序中的某些代码是高度重复的,可以先得到指令之后编号索引压缩打包之后放在那里,避免反复编译,实现代码重用(听起来像java的编译方式)

    但是这些库不能单独执行,只能配合其他程序执行

    静态库

    libhccl.a

    lib:前缀 , hccl:库名 ,.a:后缀

    制作过程:

    步骤1:将c源文件生成对应的.o文件

    gcc -c add.c -o add.o

    gcc -c sub.c -o sub.o

    gcc -c mul.c -o mul.o

    gcc -c div.c -o div.o

    步骤2:使用打包工具ar将准备好的.o文件打包为.a文件 libtest.a

    ar -rcs libtest.a add.o sub.o mul.o div.o

    其中ar -rcs

    • r更新
    • c创建
    • s建立索引

    使用

    写add.c,sub.c,mul.c,div.c是需要头文件的,假设头文件是head.h,这四个文件编译生成的四个.o文件压缩成libhccl.a,这些文件都在 ./ 文件目录里,需要编译的文件是test.c

    原来使用gcc编译文件是

    gcc test.c -o test

    使用静态库变成了

    gcc -L./ -I./ -lhccl -o test

    参数说明:

    • -L:表示要连接的库所在目录
    • -I./: I(大写i) 表示指定头文件的目录为当前目录
    • -l(小写L):指定链接时需要的库,去掉前缀和后缀

    静态链接:由链接器在链接时将库的内容加入到可执行程序中。 gcc -static test.c -o test_static

    缺点:

    1703254019517

    动态库

    动态库可以解决静态库的上述问题

    1703254256120

    libhccl.so

    • 前缀:lib
    • 库名称:自己定义即可
    • 后缀:.so

    制作过程

    步骤一:生成目标文件,此时要加编译选项:-fPIC(fpic)

    deng@itcast:~/test/5share_lib$ gcc -fPIC -c add.c

    deng@itcast:~/test/5share_lib$ gcc -fPIC -c sub.c

    deng@itcast:~/test/5share_lib$ gcc -fPIC -c mul.c

    deng@itcast:~/test/5share_lib$ gcc -fPIC -c div.c

    参数:-fPIC 创建与地址无关的编译程序(pic,position independent code),是为了能够在多个应用程序间共享。

    步骤二:生成共享库,此时要加链接器选项: -shared(指定生成动态链接库)

    deng@itcast:~/test/5share_lib$ gcc -shared add.o sub.o mul.o div.o -o libtest.so

    步骤三: 通过nm命令查看对应的函数

    deng@itcast:~/test/5share_lib$ nm libtest.so | grep add

    00000000000006b0 T add

    deng@itcast:~/test/5share_lib$ nm libtest.so | grep sub

    00000000000006c4 T sub

    (一个命令20 bit?)

    ldd查看可执行文件的依赖的动态库

    deng@itcast:~/share/3rd/2share_test$ ldd test

    linux-vdso.so.1 => (0x00007ffcf89d4000) libtest.so => /lib/libtest.so (0x00007f81b5612000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f81b5248000) /lib64/ld-linux-x86-64.so.2 (0x00005562d0cff000)

    动态库测试

    引用动态库编译成可执行文件(跟静态库方式一样)

    deng@itcast:~/test/6share_test$ gcc test.c -L. -I. -ltest (-I. 大写i -ltest 小写L)

    然后运行:./a.out,发现竟然报错了!!!

    1527582099959

    • 当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径。此时就需要系统动态载入器(dynamic linker/loader)。
    • 对于elf格式的可执行程序,是由ld-linux.so*来完成的,它先后搜索elf文件的 DT_RPATH段 — 环境变量LD_LIBRARY_PATH — /etc/ld.so.cache文件列表 — /lib/, /usr/lib目录找到库文件后将其载入内存。

    3)如何让系统找到动态库

    • 拷贝自己制作的共享库到/lib或者/usr/lib(不能是/lib64目录)
    • 临时设置LD_LIBRARY_PATH:

    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库路径

    • 永久设置,把export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库路径,设置到~/.bashrc或者 /etc/profile文件中

      deng@itcast:~/share/3rd/2share_test$ vim ~/.bashrc

      最后一行添加如下内容:

      export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/deng/share/3rd/2share_test

      使环境变量生效

      deng@itcast:/share/3rd/2share_test$ source ~/.bashrc deng@itcast:/share/3rd/2share_test$ ./test
      a + b = 20 a - b = 10

    • 将其添加到 /etc/ld.so.conf文件中

      编辑/etc/ld.so.conf文件,加入库文件所在目录的路径

      运行sudo ldconfig -v,该命令会重建/etc/ld.so.cache文件

      deng@itcast:~/share/3rd/2share_test$ sudo vim /etc/ld.so.conf

      文件最后添加动态库路径(绝对路径)

      1539308885923

      使生效

      deng@itcast:~/share/3rd/2share_test$ sudo ldconfig -v

    • 使用符号链接, 但是一定要使用绝对路径

      deng@itcast:~/test/6share_test$ sudo ln -s /home/deng/test/6share_test/libtest.so /lib/libtest.so

    动态链接:连接器在链接时仅仅建立与所需库函数的之间的链接关系,在程序运行时才将所需资源调入可执行程序。 gcc test.c -o test_share

    GDB调试器

    gdb a.out 进入调试

    b main 设置断点

    run 开始运行

    n 下一步

    quit 退出

    makefile

    目标: 依赖1 依赖2 …依赖n
    命令1
    命令2

    命令n

    1703771878565

    1703772213173

    1703772383589

    ​ 以上图为例,make 的默认目标是test,但是test的依赖都没有,所以去根据对应的规则寻找对应的依赖,所以先生成加减乘除的.o文件作为依赖,再根据这些依赖生成一个可执行文件

    vim 1.h

    1
    2
    test:
    echo "hello world"

    之后保存退出

    再 make -f 1.mk1就会出现 echo “hello world”

    1703676527881

    执行下面这段代码 先执行test1,再执行test2,再执行test

    1
    2
    3
    4
    5
    6
    test:test1 test2
    echo "test"
    test1:
    echo "test1"
    test2:
    echo "test2"

    rm -rf a.out强制递归删除a.out文件

    最简单的makefile文件

    先在含有test.c add.c sub.c mul.c div.c的文件夹里执行

    vim MakeFile命令

    1
    2
    test:test.c add.c sub.c mul.c div.c
    gcc test.c add.c sub.c mul.c div.c -o test

    缺点:效率低,修改一个文件,所有文件会被全部编译

    ** 第二个版本Makefile**

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    test:test.o add.o sub.o mul.o div.o
    gcc test.o add.o sub.o mul.o div.o -o test
    test.o:test.c
    gcc -c test.c
    add.o:add.c
    gcc -c add.c
    sub.o:sub.c
    gcc -c sub.c
    mul.o:mul.c
    gcc -c mul.c
    div.o:div.c
    gcc -c div.c

    1703772663222

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26

    #自定义变量
    OBJS=add.o sub.o mul.o div.o test.o
    TARGET=test

    $(TARGET):$(OBJS)
    gcc $(OBJS) -o $(TARGET)

    add.o:add.c
    gcc -c add.c -o add.o

    sub.o:sub.c
    gcc -c sub.c -o sub.o

    mul.o:mul.c
    gcc -c mul.c -o mul.o

    div.o:div.c
    gcc -c div.c -o div.o

    test.o:test.c
    gcc -c test.c -o test.o

    clean:
    rm -rf $(OBJS) $(TARGET)

    自动变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36

    #变量
    OBJS=add.o sub.o mul.o div.o test.o add.o
    TARGET=test
    CC=gcc

    #$@: 表示目标
    #$<: 表示第一个依赖
    #$^: 表示所有的依赖

    $(TARGET):$(OBJS)
    #$(CC) $(OBJS) -o $(TARGET)
    $(CC) $^ -o $@
    echo $@
    echo $<
    echo $^

    add.o:add.c
    $(CC) -c $< -o $@

    sub.o:sub.c
    $(CC) -c $< -o $@

    mul.o:mul.c
    $(CC) -c $< -o $@

    div.o:div.c
    $(CC) -c $< -o $@

    test.o:test.c
    $(CC) -c $< -o $@

    clean:
    rm -rf $(OBJS) $(TARGET)


    模式规则

    1
    2
    3
    4
    5
    6
    7
    模式规则示例:

    %.o:%.c

    $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@


    Makefile第三个版本:

    1
    2
    3
    4
    5
    6
    7
    OBJS=test.o add.o sub.o mul.o div.o
    TARGET=test
    $(TARGET):$(OBJS)
    gcc $(OBJS) -o $(TARGET)

    %.o:%.c
    gcc -c $< -o $@

    make -n 测试

    系统调用

    CPU可以运行在内核态或者用户态,想要切换到内核态通过软件中断

    img

    1527650975663

    系统调用是内核给用户执行程序的接口可以调用系统内核的服务,比如获取时间,文件读写,但是是一个通道,不能执行其他的

    1704166479949

    system calls是系统调用,library routines是库函数

    1704166792240

    在系统调用时,涉及到程序的部分变量从磁盘进入内存,其中在进入内存的过程中由于磁盘(机械硬盘)和内存的读写速度不一致,中间还有一个缓存

    缓存还有另一个作用:提高磁盘访问的速度

    程序运行时内存需要不断从磁盘中读取数据,多次读取的数据必然会有重复,所以需要一个角色负责存储读取频率高的数据,这即是缓存,磁盘的缓存叫做磁盘缓存。

    磁盘缓存指的是把从磁盘中读出的数据存储到内存中的方式,这样一来,当接下来需要读取相同的内容时,就不会再通过实际的磁盘,而是通过磁盘缓存来读取。磁盘缓存大大提高了磁盘访问的速度。

    根据读写速度。寄存器>一级缓存>二级缓存>三级缓存>内存>硬盘

    区别

    ​ 1、内存是一种高速,造价昂贵的存储设备;而磁盘速度较慢、造价低廉。

      2、内存属于内部存储设备,磁盘属于外部存储设备。

      3、内存是通过电流来实现存储;磁盘是通过磁记录来实现存储。所以电脑断电后,内存中的数据会丢失,而磁盘中的数据可以长久保留。

      

    虚拟内存(磁盘的一部分)

     虚拟内存是指把磁盘的一部分作为假想内存来使用。虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续可用的内存(一个完整的地址空间),但是实际上,它通常被分割成多个物理碎片,还有部分存储在外部磁盘管理器上,必要时进行数据交换。

      计算机中的程序都要通过内存来运行,如果程序占用内存很大,就会将内存空间消耗殆尽。为了解决这个问题,WINDOWS 操作系统运用了虚拟内存技术,通过拿出一部分硬盘来当作内存使用,来保证程序耗尽内存仍然有可以存储的空间。虚拟内存在硬盘上的存在形式就是PAGEFILE.SYS 这个页面文件。

    磁盘的物理结构

         磁盘的物理结构指的是其存储数据的形式。磁盘是通过其物理表面划分成多个空间来使用的。划分的方式有两种:可变长方式和扇区方式。前者是将物理结构划分成长度可变的空间,后者是将磁盘结构划分为固定长度的空间。windows所使用的是扇区的方式。扇区中,把磁盘表面分成若干个同心圆的空间的线就是磁道。把磁道按照固定大小的存储空间划分而成的就是扇区。
    

      扇区是磁盘进行物理读写的最小单位。windows中,一般一个扇区512个字节。

    img

     磁盘又通常是由一些旋转着的金属碟片和一个装在步进马达上的读写头组成的。读/写头同一时刻只能出现在一个地方,然后它必须“寻址”到另外一个位置来执行另一次读写操作。所以就有了寻址的耗时,此外还有旋回耗时,读写头需要等待碟片上的目标数据“旋转到位”才能进行操作。(暂定)

    虚拟地址空间

    32位机器,该地址空间位4G

    2^32 = 2^22K = 2^12M = 2^2G =4G

    1527650975663

    环境变量包括系统路径等

    命令行参数类似于args,options

    栈空间存储函数变量

    共享库是共享存储映射,内存和文件的通信方式 mmap

    堆空间存放局部变量

    .bss #define epoch

    .data #define epoch 100

    .test 写的程序编译得到二进制

    在进程里平时所说的指针变量,保存的就是虚拟地址。当应用程序使用虚拟地址访问内存时,处理器(CPU)会将其转化成物理地址(MMU)。

    MMU:将虚拟的地址转化为物理地址。

    这样做的好处在于:

    • 进程隔离,更好的保护系统安全运行
    • 屏蔽物理差异带来的麻烦,方便操作系统和编译器安排进程地址

    错误处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    #include <stdio.h>  //fopen
    #include <errno.h> //errno
    #include <string.h> //strerror(errno)
    int main()
    {
    FILE *fp = fopen("xxxx", "r");
    if (NULL == fp)
    {
    printf("%d\n", errno); //打印错误码
    printf("%s\n", strerror(errno)); //把errno的数字转换成相应的文字
    //两个都根据错误码,但是strerror 需要传递错误码,peerror不用,直接显示报错
    ///根据errno值输出错误信息
    //提示字符串: 出错原因
    perror("fopen err"); //打印错误原因的字符串
    }
    return 0;
    }

    当编译遇到报错时,缺少包含的头文件

    1704183816012

    看一下sleep函数的用法

    man 2 sleep

    1704183880123

    man 3 sleep

    1704183910645

    文件操作

    • (空过去)

    多进程

    ps

    进程是一个具有一定独立功能的程序,它是操作系统动态执行的基本单元。

    ps命令可以查看进程的详细状况,常用选项(选项可以不加“-”)如下:

    选项 含义
    -a 显示终端上的所有进程,包括其他用户的进程
    -u 显示进程的详细状态
    -x 显示没有控制终端的进程
    -w 显示加宽,以便显示更多的信息
    -r 只显示正在运行的进程

    ps aux

    ps ef

    ps -a

    只查看后台进程(没有占用终端一直在后台进行)使用jobs命令查看

    1704458083591

    查看到进程对应的PID之后

    比如查找火狐的进程号是35909

    ps -Lf 35909 就可以查看这个程序的线程池

    父子进程

    1527909577065

    函数

    如果是父进程,fork返回进程号+1(子进程pid),getpid返回其进程号

    如果是子进程,fork返回0,getpid返回父进程号+1(子进程pid)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    int main()
    {
    pid_t pid;
    pid = fork();
    if (pid < 0)
    { // 没有创建成功
    perror("fork");
    return 0;
    }

    if (0 == pid)
    { // 子进程
    printf("子进程 pid:%d ppid:%d fork:%d\n",getpid(),getppid(),pid);

    }
    else if (pid > 0)
    { // 父进程
    printf("父进程 pid:%d fork:%d\n",getpid(),pid);
    }

    return 0;

    1704250319831

    子进程是通过fork()函数从父进程复制而来的,包括栈空间。尽管变量b是在main()函数中定义的局部变量,但在子进程中,fork()函数会复制父进程的栈空间,包括变量b。因此,在子进程中,变量b的地址与父进程中的变量b的地址相同。但是这是在两个栈中的相同位置的两个地址,即使b在子进程中的值发生改动,他的地址也不会变

    读时共享 写时拷贝

    对于全局变量a,我在没有改动它时,他是读时共享的,也就是它只有一个在父进程中的地址,但我对他进行改动时,他会在子进程中创建一个相同的地址,a的地址不管创建没创建它的地址显示出来都是一样的,但是值不一样

    1
    2
    3
    4
    5
    6
    7
    8
    int main()
    {
    fork();
    int num = 10
    printf("id ==== %d\n", getpid()); // 获取进程号
    num = 11;
    return 0;
    }

    资源的复制是在需要写入的时候才会进行,在此之前,只有以只读方式共享。

    如果父子进程变量都没有改动(没有num=11),那么只有读时共享,两者共用一个变量。如果num有变动,那么进行写时拷贝,两者分别拥有一个num

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    #include <unistd.h>
    //父了进程地址空间
    int main(void)
    {
    int var = 88;
    pid_t pid = -l;
    //创建一个子进程
    pid = fork();
    if (-1 == pid)
    {
    perror("fork");
    return 1;
    }

    if (0 == pid)
    {
    //子进程
    sleep(1);
    printf("子进程睡醒之后 var = d\n",var); //88
    }

    else
    {
    //父进程
    printf("父进程之前 var = %d\n",var); //88
    var++;
    printf("父进程之后 var = %d\n",var); //89
    }
    return 0;
    }

    可以通过valgrind命令查看不同进程的释放和分配

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    #include<stdio.h>
    9 #include<math.h>
    10 #include<string.h>
    11 #include<sys/types.h>
    12 #include<unistd.h>
    13 #include<stdlib.h>
    14 int a = 10; // 全局变量
    15 int num = 100;
    16 int main()
    17 {
    18 int b = 20; //局部变量
    19 int var = 88;
    20 pid_t pid;
    21 int *p = NULL;
    22 p = malloc(sizeof(int));
    23 if( NULL == p)
    24 {
    25 printf("malloc failed ...\n");
    26 return 1;//不能return 0会出错
    27
    28 }
    29 memset(p, 0, sizeof(int));
    30 *p = 200;
    31
    32 pid = fork();
    33 if (pid < 0)
    35 { // 没有创建成功
    36 perror("fork");
    37 }
    38
    39 if (0 == pid)
    40 { // 子进程
    41 printf("子进程 &a:%p &b: %p\n",&a,&b);
    42 a = 111;
    43 b = 222; // 子进程修改其值
    44 printf("son: a = %d, b = %d\n", a, b);
    45 printf("子进程 &a:%p &b: %p\n",&a,&b);
    46 sleep(1);
    47 printf("子进程睡醒之后 *p = %d num = %d var = %d\n",*p,num,var);
    //free(p);
    //p = NULL; 二次释放 不加有内存泄露
    48
    49 }
    50 else if (pid > 0)
    51 { // 父进程
    52 printf("父进程之前 *p = %d num = %d var = %d\n",*p,num,var);
    53 num++;
    54 (*p)++;
    55 var++;
    56 printf("父进程 &a:%p &b: %p\n",&a,&b);
    57 sleep(1); // 保证子进程先运行
    58 printf("father: a = %d, b = %d\n", a, b);
    59 printf("父进程 &a:%p &b: %p\n",&a,&b);
    60 printf("父进程之前 *p = %d num = %d var = %d\n",*p,num,var);
    //free(p);
    //p = NULL; 二次释放
    61
    62 }
    63
    64 return 0;
    65 }

    1704270221206

    可以看到有内存泄露

    gdb调试

    • set follow-fork-mode child 设置gdb在fork之后跟踪子进程。
    • set follow-fork-mode parent 设置跟踪父进程(默认)。
    • 注意,一定要在fork函数调用之前设置才有效。不加默认调试子进程

    gdb a.out之后run

    set follow-fork-mode child

    wait

    1
    2
    3
    int status = 1;

    wait(&status)

    exit

    1
    2
    3
    exit 
    _exit() _Exit()
    return 0

    孤儿进程

    父进程停止,子进程依然在进行,一般会被另一个进程收养,在终端操作是1号进程,在图形界面操作是一个pid不确定但是名为upstart进程

    僵尸进程

    子进程结束了,父进程没有回收其资源

    查看是否有僵尸进程 ps aux | grep z

    这样可能搜不到,查看一下是否还有gcc生成的a.out 有僵尸进程在运行ps aux | grep a.out

    1704288566639

    把所有是a.out都杀死 killall a.out

    僵尸进程已经是死的,不能被杀死,但是杀死其父进程或者父进程自动退出相当于杀死了僵尸

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    int main(void)
    {
    int i =0;
    pid_t pid =-1;
    //创建一个子进程
    pid = fork();
    if (-1 == pid)
    {
    perror("fork");
    return 1;
    }
    //子进程
    if (0 == pid)
    {
    for (i = 0;i< 5;i++)
    {
    printf("子进程做事 %d\n",i);
    sleep(1);
    }
    printf("子进程想不开了,结束了自己...\n");
    //子进程退出
    exit(0);
    }
    sleep(100);
    printf("父进程睡醒了 父进程退出....\n");
    return 0;
    }

    进程替换

    1704357101891

    进程间通讯

    目的:数据传输 资源共享 通知事件 进程控制

    1527923816604

    无名管道

    半双工,同一时刻只能往同一方向;只能在父子兄弟进程之间使用;不是普通 的文件不属于文件系统,只在内存中;先入先出

    pipe

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    int main()
    {
    int dfs[2]; //定义管道
    int ret = -1;//定义管道状态
    char buf[64];//定义传输的信息
    pid_t pid = -1;//定义子进程状态
    //开启管道
    ret = pipe(dfs);
    if(ret<0)
    {
    perror("pipe");
    return 1;
    }
    //开启子进程
    pid = fork();
    if(pid<0)
    {
    perror("fork");
    return 1;
    }
    if(pid==0)//子进程
    {
    close(dfs[1]);
    memset(buf,0,64);
    ret = read(dfs[0],buf,64);
    if(ret<0)
    {
    perror("read");
    exit(-1);
    }
    printf("buf:%s",buf)

    close(dfs[0]);
    exit(0);

    }
    if(pid>0)//父进程
    {
    close(dfs[0]);
    // ret = write(dfs[1],"ABCDEFGHIJ",10);
    memset(buf,0,64);
    sprintf(buf,"ABCDEFGHIJ",i++);
    ret = write(dfs[1].buf,strlen(buf))
    if(ret<0)
    {
    perror("write");
    return 1;
    }
    close(dfs[1]);

    }

    }

    管道的读写特点:

    • 如果读端和写端都开着
      • 如果写端不填进东西,读端一直读,读到管道里没有东西会堵塞;
      • 如果读端不读出东西,管道被填满了,管道堵塞;
    • 如果读端关闭写端开着
      • 写进程在写端运行的时候会收到一个信号然后退出
    • 如果读端关闭写端开着
      • 读进程读取全部的内容,然后返回0

    ulimit -a 查看管道缓冲区大小

    一般为4k 512个字节,8块,2^9*8=2^10*4=4k

    设置为非阻塞的方法

    设置方法:

    1
    //获取原来的flags
    1
    int flags = fcntl(fd[0], F_GETFL);
    1
    // 设置新的flags
    1
    flag |= O_NONBLOCK;
    1
    // flags = flags | O_NONBLOCK;
    1
    fcntl(fd[0], F_SETFL, flags);

    结论: 如果写端没有关闭,读端设置为非阻塞, 如果没有数据,直接返回-1。

    有名管道

    FIFO文件

    pipe不在文件系统中只在内存中,但是FIFO在文件系统中作为文件存在,但是其内容放在内存中;不止是有血缘的进程,也可以通信与不相关的进程

    在终端中输入以下命令

    mkfifo fifo1(fifo1是创建的管道名称)

    有名管道的读和写是在两个文件中调用的,一个文件专门读,一个文件写,运行也是要gcc write.c -o write之后./write

    (应该是错了待改)

    就是错的傻逼玩意

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67

    int main()
    {
    //int dfs[2]; //定义管道

    int ret = -1;//定义管道状态
    char buf[64];//定义传输的信息
    pid_t pid = -1;//定义子进程状态


    //开启子进程
    pid = fork;
    if(pid<0)
    {
    perror("fork");
    return 1;
    }
    if(pid==0)//子进程
    {
    close(dfs[1]);
    memset(buf,0,64);
    ret = read(dfs[0],buf,64);
    if(ret<0)
    {
    perror("read");
    exit(-1);
    }
    printf("buf:%s",buf)

    //close(dfs[0]);
    exit(0);

    }
    if(pid>0)//父进程
    {
    dfs = open("相对文件路径",0644);//0644是open函数中的 mode 参数
    //int fd = open("xxx.txt", O_RDWR); //读写文件
    //int fd = open("my_fifo", O_WRONLY);
    //int fd = open("my_fifo", O_RDONLY);//等着只写
    if(dfs<0)
    {
    perror("open");
    return 1;
    }
    //开启管道
    ret = pipe(dfs);
    if(ret<0)
    {
    perror("pipe");
    return 1;
    }
    //close(dfs[0]);
    // ret = write(dfs[1],"ABCDEFGHIJ",10);
    memset(buf,0,64);
    sprintf(buf,"ABCDEFGHIJ",i++);
    ret = write(dfs[1].buf,strlen(buf))
    if(ret<=0)//写满了
    {
    perror("write");
    return 1;
    }
    //close(dfs[1]);
    close(dfs);
    return 0;
    }

    }

    管道的读写特点:

      1. 一个为只读而打开一个管道的进程会阻塞直到另外一个进程为只写打开该管道

      2)一个为只写而打开一个管道的进程会阻塞直到另外一个进程为只读打开该管道

    • 如果读端和写端都开着

      • 如果写端不填进东西,读端一直读,读到管道里没有东西会堵塞;
      • 如果读端不读出东西,管道被填满了,管道堵塞;
    • 如果读端关闭写端开着

      • 写进程在写端运行的时候会收到一个信号然后退出
    • 如果读端关闭写端开着

      • 读进程读取全部的内容,然后返回0

    读管道:

    Ø 管道中有数据,read返回实际读到的字节数。

    Ø 管道中无数据:

    u 管道写端被全部关闭,read返回0 (相当于读到文件结尾)

    u 写端没有全部被关闭,read阻塞等待

    写管道:

    Ø 管道读端全部被关闭, 进程异常终止(也可使用捕捉SIGPIPE信号,使进程终止)

    Ø 管道读端没有全部关闭:

    u 管道已满,write阻塞。

    u 管道未满,write将数据写入,并返回实际写入的字节数。

    共享存储映射

    1527925077595

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    //存储映射
    int main(void)
    {
    int fd = -1;
    int ret = -1;
    pid_t pid = - 1;
    void *addr = NULL;
    //void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 如果第一个参数不填空会考虑地址对齐,因为这个指针的起始地址又是以4k为单位,这就会导致总线错误,非法
    //1.以读写的方式打开一个文件
    fd = open ("txt",O_RDWR);
    if ( -1 == fd)
    {
    perror ( "open");
    return 1;
    }
    //2.将文件映射到内存
    addr = mmap(NULL1024,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);

    if (addr == MAP_FAILED)
    {
    perror ( "mmap" );
    return 1;
    }

    printf("文件存储映射ok. . . .\n" );
    //3.关闭文件
    close(fd);
    //4.写存储映射区
    memcpy ( addr, " 1234567890",10) ;
    // strcpy((char*)ptr, "i am u father!!");
    //5.断开存储映射
    munmap (addr, 1024);

    return 0;

    }

    匿名映射

    就是mmap取掉最后一个fds

    只能在父子进程之间进行

    信号

    信号的编号

    POSIX.1对可靠信号例程进行了标准化。

    信号四要素

    编号 名称 时间 默认处理动作

    查看详细信息man 7 signal

    1527928967909

    在标准信号中,有一些信号是有三个“Value”,第一个值通常对alpha和sparc架构有效,中间值针对x86、arm和其他架构,最后一个应用于mips架构。一个‘-’表示在对应架构上尚未定义该信号。

    信号状态

    阻塞信号 相当于黑名单

    未决信号 相当于所有未接电话

    信号产生后由于某些原因(主要是阻塞)不能抵达

    据此可以分为未决信号集(可读可写)和阻塞信号集(只能读)

    对于信号集的操作有固定的函数<signal.h>

    1
    2
    3
    4
    5
    6
    #include <signal.h>  
    int sigemptyset(sigset_t *set); //将set集合置空
    int sigfillset(sigset_t *set)//将所有信号加入set集合
    int sigaddset(sigset_t *set, int signo); //将signo信号加入到set集合
    int sigdelset(sigset_t *set, int signo); //从set集合中移除signo信号
    int sigismember(const sigset_t *set, int signo); //判断信号是否存在
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32

    int main()
    {
    sigset_t set; // 定义一个信号集变量
    int ret = 0;
    sigemptyset(&set); // 清空信号集的内容
    // 判断 SIGINT 是否在信号集 set 里
    // 在返回 1, 不在返回 0
    ret = sigismember(&set, SIGINT);
    if (ret == 0)
    {
    printf("SIGINT is not a member of set \nret = %d\n", ret);
    }
    sigaddset(&set, SIGINT); // 把 SIGINT 添加到信号集 set
    sigaddset(&set, SIGQUIT);// 把 SIGQUIT 添加到信号集 set
    // 判断 SIGINT 是否在信号集 set 里
    // 在返回 1, 不在返回 0
    ret = sigismember(&set, SIGINT);
    if (ret == 1)
    {
    printf("SIGINT is a member of set \nret = %d\n", ret);
    }
    sigdelset(&set, SIGQUIT); // 把 SIGQUIT 从信号集 set 移除
    // 判断 SIGQUIT 是否在信号集 set 里
    // 在返回 1, 不在返回 0
    ret = sigismember(&set, SIGQUIT);
    if (ret == 0)
    {
    printf("SIGQUIT is not a member of set \nret = %d\n", ret);
    }
    return 0;
    }

    设置屏蔽信号集

    sigprocmask函数

    设置之后这个集合里面的信号就不会去执行信号处理函数了,再发送这个信号就没有反应了,解除屏蔽之后,只能接收最后一次发送的这个信号

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    int main()
    {
    // 自定义信号集
    sigset_t myset, old;
    sigemptyset(&myset);// 清空 -》 0

    // 添加要阻塞的信号
    sigaddset(&myset, SIGINT);
    sigaddset(&myset, SIGQUIT);
    sigaddset(&myset, SIGKILL);

    // 自定义信号集设置到内核中的阻塞信号集
    sigprocmask(SIG_BLOCK, &myset, &old);

    sigset_t pend;
    int i = 0;
    while (1)
    {
    // 读内核中的未决信号集的状态
    sigpending(&pend);
    for (int i = 1; i<32; ++i)
    {
    if (sigismember(&pend, i))
    {
    printf("1");
    }
    else if (sigismember(&pend, i) == 0)
    {
    printf("0");
    }
    }
    printf("\n");
    sleep(1);
    i++;

    // 10s之后解除阻塞
    if (i > 10)
    {
    // sigprocmask(SIG_UNBLOCK, &myset, NULL);
    sigprocmask(SIG_SETMASK, &old, NULL);
    }
    }

    return 0;
    }

    读取未决信号集

    1
    2
    3
    4
    #include <signal.h>

    int sigpending(sigset_t *set);
    功能:读取当前进程的未决信号集

    生成信号

    kill

    在main函数的父进程中 pid = fork(); kill(pid,15);;

    raise

    在main函数中自己给自己发送一个信号raise(15);也相当于kill(getpid(),15)

    等价于 raise(SIGTERM)

    abort

    给自己发送一个异常中止编号为6的信号,直接abort();

    alarm

    设置闹钟

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    int main()
    {
    int seconds = 0;
    //设定闹钟,5秒之后超时会发送对应信号
    seconds = alarm(5);
    printf("seconds = %d\n", seconds);
    sleep(2);
    //之前没有超市的闹钟会被新的设置覆盖
    seconds = alarm(5);
    printf("seconds = %d\n", seconds);
    printf("按下任意键继续")
    getchar()//读入,如果没有按下任意键超时会返回闹钟
    return 0;
    }

    setitimer

    捕获信号

    捕获信号之后可以不再让信号中断停止,而是进行其他操作(信号处理函数)

    sign函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    // 信号处理函数
    void signal_handler(int signo)
    {
    if (signo == SIGINT)
    {
    printf("recv SIGINT\n");
    }
    else if (signo == SIGQUIT)
    {
    printf("recv SIGQUIT\n");
    }
    }

    int main()
    {
    printf("wait for SIGINT OR SIGQUIT\n");

    /* SIGINT: Ctrl+c ; SIGQUIT: Ctrl+\ */
    // 信号注册函数
    signal(SIGINT, signal_handler);
    signal(SIGQUIT, signal_handler);

    while (1); //不让程序结束

    return 0;
    }

    由于历史原因在不同版本的Unix和不同版本的Linux中可能有不同的行为。因此应该尽量避免使用它,取而代之使用sigaction函数。

    sigaction

    1
    2
    3
    #include <signal.h>

    int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

    注意一个语法

    函数指针变量: void(*sa_handler)(int) 信号处理函数指针,在sigaction中用的act

    函数指针类型: typedef void(*sa_handler)(int)相当于定义了一个以sa_handler为地址的函数变量,这个函数对应的return是coid(没有返回),输入参数是int。在signal函数中用的signal_handler

    守护进程(线程)

    在前台执行sleep 3000

    在后台执行sleep 3000 &

    用户只要远程登录服务器,就会在登录系统之后打开一个shell进程,用于执行这个shell进程的人机交互界面,就是终端

    比如服务器只要启动,就会打开进程号为0的父进程,然后其他的进程通过fork这个进程,再加以修改,进行其他的动作

    1704787003331

    PPID 父进程

    PID 进程组号

    PGID 组进程

    SID 会话ID

    TTY 终端的名字 在终端李输入tty可以查看当前终端的名字

    TGID 不知道

    STAT 状态 未决 阻塞 到达

    UID 不知道

    TIME 时间

    COMMAND 执行指令

    会话

    是进程组的集合

    守护进程

    在后台执行的进程,通常独立于控制终端存在,大部分进程如果执行的控制终端关闭进程也会跟着关闭

    创建步骤:

    1. 创建子进程,父进程退出(必须)
    1
    2
    3
    4
    5
    pid = fork();
    if(pid>0)
    {
    exit(0);
    }
    • 所有工作在子进程中进行形式上脱离了控制终端,如果是正常的只有一个sleep()函数执行后会没有反应,只有sleep完之后才会出现cc@itcast:~/test/pipe$,这样操作之后会接着出现cc@itcast:~/test/pipe$相当于后台进程,没有占用控制终端。但是只是形式上脱离,当关闭这个终端窗口时这个进程还是会死掉。
    1. 在子进程中创建新会话(必须)
    1
    pid = setsid();
    • setsid()函数
    • 使子进程完全独立出来,脱离控制
    • 但终端窗口关掉,这个进程也不会收影响
    1. 改变当前目录为根目录(不是必须)
    1
    ret = chdir("/");
    • chdir()函数
    • 防止占用可卸载的文件系统
    • 也可以换成其它路径
    1. 重设文件权限掩码(不是必须)
    1
    umask(0);  //没有屏蔽任何权限   
    • umask()函数
    • 防止继承的文件创建屏蔽字拒绝某些权限
    • 增加守护进程灵活性
    1. 关闭文件描述符(不是必须)
    1
    2
    3
    close(0);  STDIN_FILENO
    close(1); STDOUT_FILENO
    close(2); STDERR_FILENO
    • 继承的打开文件不会用到,浪费系统资源,无法卸载
    1. 开始执行守护进程核心工作(必须)

    守护进程退出处理程序模型

    一般在终端中想要将时间写入txt文件

    在终端中输入date可以获取当前时间

    1704803425742

    输入date >> txt 就可以写入了

    vim txt

    1704803471968

    所以在main函数只需要执行

    system("date>>/tmp/txt.log");

    只是在main中执行即可,之前已经让父进程自动退出了,之前父子进程中是

    1
    2
    3
    4
    5
    6
    7
    8
    if (pid>0) //父进程
    {

    }
    if(pid == 0)//子进程
    {

    }

    现在是

    1
    2
    3
    4
    5
    6
    7
    8
    9
    if(pid >0)
    {
    exit(0);
    }
    while(1)
    {
    system("date>>/txt.log");
    sleep(1);
    }

    另一种获取时间的方式

    动态的查看文件内容

    tail -f /txt.log

    线程

    就是轻量级的进程

    进程是CPU分配资源的最小单位,线程是操作系统调度的最小单位

    线程:共享对方的地址空间

    进程:复制对方的地址空间

    线程号

    线程创建

    线程回收

    线程退出

    exit(0)是退出整个进程

    return NULL pthread_exit(NULL) 退出线程

    线程取消

    pthread_cancel(tid)

    一个线程栈的大小是8M

    线程属性设置

    线程同步

    互斥:同一时刻一个资源只能被一个进程使用,两个任务不能同时运行,互斥具有唯一性和排他性

    同步:运行必须按照规定的先后次序,比如A的运行依赖于B的运行结果,同步具有次序。

    互斥锁

    在访问共享资源(打印机)之前加锁,在访问共享资源之后解锁

    如果加锁不成功就会阻塞,等待加锁成功

    两种场景

    打印机场景

    第一个线程打印ABCDEF,第二个线程打印abcdef,两者混杂在一起,可能是AaBbcCDdEeFf这样

    两个线程都对变量num = 100,num++,想得到的是102,

    但是第一个线程读取内存中的num到寄存器里之后操作num++得到101再写回去,第二个线程也是读取num=100再num++得到101写回去,最后只是把101写了两次并没有得到102

    步骤

    在main函数之外定义一个pthread_t t变量

    在main函数中初始化,如果没有特殊初始化函数的第二个变量一般为NULL

    在线程回调函数加锁,操作,解锁

    死锁

    1704979252333

    因为操作不当导致程序阻挡不能往下运行了,两个进程永远在互相等待

    竞争不可抢占资源引起死锁

    也就是我们说的第一种情况,而这都在等待对方占有的不可抢占的资源。

    竞争可消耗资源引起死锁

    有p1,p2,p3三个进程,p1向p2发送消息并接受p3发送的消息,p2向p3发送消息并接受p1的消息,p3向p1发送消息并接受p2的消息,如果设置是先接到消息后发送消息,则所有的消息都不能发送,这就造成死锁。

    进程推进顺序不当引起死锁

    有进程p1,p2,都需要资源A,B,本来可以p1运行A –> p1运行B –> p2运行A –> p2运行B,但是顺序换了,p1运行A时p2运行B,容易发生第一种死锁。互相抢占资源。

    避免死锁:在申请超时的时候释放自己的资源,或者得到一个资源后操作完释放后再申请另一个资源,或者申请资源的顺序,都是先申请资源1再申请资源2

    读写锁

    银行取钱

    两个人同时在一个账户取钱

    一个程序加了读锁,其他程序可以加读锁但不能加写锁

    一个程序加了写锁,其他程序读锁和写锁都不能加

    条件变量

    与互斥锁共同使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <pthread.h>
    //互斥量
    pthread_mutes_t mutex;
    //条件变量
    pthread_cond_t cond;

    //第一个线程控制第二个线程每隔3秒钟跑一次
    void *fun1(void *arg)
    {
    while(1)
    {
    //加锁
    pthread_mutex_lock(&mutex);
    flag = 1;
    //解锁
    pthread_mutex_unlock(&mutex);

    //唤醒因为条件而阻塞的线程(即每隔两次改变一次条件变量)
    pthread_cond_signal(&cond);
    sleep(2);

    }
    return NULL;
    }
    void *fun2(void *arg)
    {
    while(1)
    {
    //加锁
    pthread_mutex_lock(&mutex);
    //表示条件不满足
    if(flag==0)
    {
    //等待条件满足 先阻塞住
    pyhread_cond_wait(&cond,$mutex);
    }
    printf("线程二因为条件满足开始运行。。。。\n");
    flag = 0;

    //解锁
    pthread_mutex_unlock(&mutex);
    }
    return NULL;

    }

    int main()
    {
    pthread_t tid1,tid2;
    int ret = -1;
    //初始化条件变量
    ret = pthread_cond_init(&cond,NULL);
    if(0!=ret)
    {
    printf("pthread_cond_init failed...\n");
    return 1;
    }
    //初始化互斥变量
    ret = pthread_mutex_init(&mutex,NULL);
    if(0!=ret)
    {
    printf("pthread_cond_init failed...\n");
    return 1;
    }
    //创建两个线程
    pthread_create(&tid1,NULL,fun1,NULL);
    pthread_create(&tid2,NULL,fun2,NULL);

    //回收线程资源
    ret = pthread_join(tid1,NULL);
    if(0!= ret)
    {
    printf("pthread_join failed...\n");
    return 1;
    }
    ret = pthread_join(tid2,NULL);
    if(0!= ret)
    {
    printf("pthread_join failed...\n");
    return 1;
    }
    //销毁互斥量
    pthread_mutex_destroy(&mutex);
    //销毁条件变量
    pthread_cond_destroy(&cond);

    return 0;
    }

    生产者消费者体条件变量模型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <pthread.h>
    //互斥量
    pthread_mutes_t mutex;
    //条件变量
    pthread_cond_t cond;
    //链表节点
    typedef struct _node_t{
    int data;//这是一个整数类型的成员变量,用于存储数据
    struct _node_t *next;//这是一个指向node_t结构体类型的指针成员变量,用于指向下一个节点。
    // struct node_t *next 和 struct _node_t next 都会报错,因为node_t类型在结构体声明之后才有,但是结构体指针在typedef struct _node_t的时候就有了,而且占8个字节
    }node_t;//该结构体类型的名字是node_t,在结构体的定义末尾使用typedef关键字对其进行了重命名
    node_t *head = NULL;
    //生产者线程
    void *producer(void *arg)
    {
    while(1)
    {
    node_t *new = malloc(sizeof(node_t));//不断创建链表
    if(NULL == new)
    {
    printf("malloc failed...\n");
    break;
    }
    memset(new,0,sizeof(node));

    //1-100
    new->data = random()%100 + 1;
    new->next = NULL;

    //头插法
    new->next = head;
    head = new;
    //随机睡眠
    sleep(random()%3+1);
    }

    //return NULL;
    pthread_exit(NULL);
    }
    //消费者线程
    void *customer(void*args)
    {
    ddd
    }
    int main()
    {
    pthread_t tid1,tid2;
    int ret = -1;
    //初始化条件变量
    ret = pthread_cond_init(&cond,NULL);
    if(0!=ret)
    {
    printf("pthread_cond_init failed...\n");
    return 1;
    }
    //初始化互斥变量
    ret = pthread_mutex_init(&mutex,NULL);
    if(0!=ret)
    {
    printf("pthread_cond_init failed...\n");
    return 1;
    }
    //创建两个线程
    pthread_create(&tid1,NULL,fun1,NULL);
    pthread_create(&tid2,NULL,fun2,NULL);

    //回收线程资源
    ret = pthread_join(tid1,NULL);
    if(0!= ret)
    {
    printf("pthread_join failed...\n");
    return 1;
    }
    ret = pthread_join(tid2,NULL);
    if(0!= ret)
    {
    printf("pthread_join failed...\n");
    return 1;
    }
    //销毁互斥量
    pthread_mutex_destroy(&mutex);
    //销毁条件变量
    pthread_cond_destroy(&cond);

    return 0;
    }

    信号量

    用于进程和线程间的同步和互斥

    用于管理线程和进程,统计正在运行的线程数,然后控制哪一个线程运行,哪一个线程阻塞

    比如生产者消费者只能生成一个再消费一个,现在可以用信号量设置生产者最多只能生产四个,那么生产者生产4个不再生产,消费者消费一个之后生产者才能再生产,生产者生产几个消费者才能消费几个