Linux基础

用户和用户组管理

su - root # 切换到root用户
useradd tom # 添加用户tom
userdel tom # 删除用户tom,但不删除用户目录
userdel -r tom # 删除用户tom并将用户目录删除
useradd tom -g group # 添加用户tom并将其指定到group组中
usermod tom -g group # 将用户tom指定到组group中
usermod tom -G group # 将用户tom指定到组group中,但是也不离开原来的组(用户tom同时在两个组里)
usermod -d /home/fox tom # 更改用户tom登录后的初始目录为/home/fox/,前提是用户tom有进入/home/fox/目录的权限
passwd tom # 更改用户tom的密码

groupadd monster # 创建组monster
useradd -g monster fox # 创建用户fox并指定所在组为monster
chgrp monster apple.txt # 将apple.txt所在组指定为monster

vim /etc/passwd # 记录用户信息的文件
vim /etc/shadow # 记录用户加密密钥
vim /etc/group # 记录组信息

管理文件/目录所有者和所在组/权限

ls ahl # 查看文件权限和所有者和所有组
ls -l
############################################################################################################
# drwxrwxr-x. 2 tom tom 4096 12月 21 22:52 dic
# 第0位是文件类型:d文件夹;-普通文件;l链接文件;c字符设备文件,如鼠标键盘;b块设备,如硬盘
# 第1-3位是文件的所有者对该文件的权限:r读;w写,但不代表可以删除,对文件所在目录有写权限才可以删除;x执行;
# 第4-6位是文件所属组对该文件的权限:rwx同上,-无对应权限:如rw-,有读写权限,无执行权限;r-x,有读权限和执行权限,无写权限
# 第7-9位是其他组对该文件的权限,rwx作用到目录,r可以读取,ls该目录的内容;w可以修改:创建删除重命名;x可以进入该目录
# 文件权限可用数字表示,r=4,w=2,x=1,因此r+w+x=7
# 数字2,文件:硬链接数 目录:子目录数量(包括隐含的.目录和..目录)
# 所有者 所在组
# 4096,文件大小(Byte),如果是文件夹则显示4096
############################################################################################################
chmod u=rwx,g=rx,o=x 文件/文件名 # u所有者;g所有组;o其他人;a所有人
chmod o+w 文件/目录名 # 给其他用户赋予写权限
chmod a-x 文件/目录名 # 取消所有人的执行权限
chmod 750 文件/目录名 # 给所有者赋予读写执行权限(4+2+1),给所有组赋予读和执行的权限(4+1),其他人无权限(0)

chown tom apple.txt # 将apple.txt文件的所有者改为tom
chown tom /home/dic # 将dic文件夹所有者改为tom
chown -R tom /home/dic # 递归更改所有者为tom,包括dic及其子文件和子文件夹
chgrp group 文件/目录 # 更改文件/目录的所在组
chgrp -R group 文件/目录 # 递归更改文件/目录及其子文件和子文件夹的所在组

更改运行级别

systemctl get-default # 查看默认运行级别
systemctl set-default multi-user.target # 切换到多用户级别
systemctl set-default graphical.target # 切换到图形界面运行级别

找回root密码

#找回密码#
在启动到选择启动的系统核心时,按e,定位到linux16所在行,在行末尾追加init=/bin/sh,然后按Ctrl+X,进入单用户模式,如下
sh-4.2# 
mount -o remount,rw /
passwd
#输入密码
touch /.autorelabel
exec /sbin/init
#等待系统自动重启
#重启后重新以root登录,使用更改后的密码登录,登录后可以将密码改回习惯密码

文件操作

cd~ # 进入当前用户目录
cd # 进入当前用户目录
cd .. # 返回上级目录

mkdir /home/tom/dir # 在/home/tom/目录中创建文件夹dir
mkdir -p /home/tom/dic/tion/dir # 创建多级目录

rmdir /home/tom/dir # 删除空目录dir
rm -rf /home/tom/dic/tion # 递归强制删除非空目录dir及其子文件和子文件夹

touch hello.txt # 创建空文件hello.txt

cp /home/tom/hello.txt /home/tom/hi.txt # cp 源文件路径 目标文件路径
cp -r /home/tom/dic/ /home/tom/dir # 递归复制文件夹dic及其子文件和子文件夹到dir下
\cp ./hello.txt ./hi.txt # 强制覆盖已存在的hi.txt,但不提示覆盖提醒

rm -r -f hello.txt # -r递归删除 -f强制删除不提醒

mv hello.txt hey.txt # 重命名hello.txt为hey.txt
mv hey.txt ./dir/

查看、修改文件内容

cat -n /etc/profile # 查看文件内容,带行号,显示全部
cat -n /etc/profile |more # 可加管道命令 |more,显示一页,回车显示更多,空格翻译一页,q退出,Ctrl+F向下滚动一屏,Ctrl+B向上滚动一屏 =显示当前行号 :f输出文件名以及当前行号

less /etc/profile # 不是一次性加载文件,适合查看大文件,空格翻页,pageup上一页,pagedown下一页,/要搜索的内容 n向下查找 N向上查找,?要搜索的内容 n向下查找 N向上查找,q退出

head /etc/profile # 默认查看前10行
head -n 5 /etc/profile # 查看前5行

tail /etc/profile # 默认查看后10行
tail -n 5 /etc/profile # 查看后5行
tail -f hi.txt # 实时追踪文档的更新,使用echo、ls、cat覆盖或追加的才可以检测到,覆盖追加时如果不存在,则会自动创建

echo 123 > hi.txt # 覆盖写入123
echo 456 >> hi.txt # 追加写入456
ls -l >> hi.txt # 将当前目录的文件或文件夹列表追加写入
cat hi.txt >> hello.txt # 将hi.txt文件内容追加写入到hello.txt中
cal >> cal.txt # 将当前日历信息追加到cal.txt

查询指令

echo hello,world!
echo "hello,world!"
echo $HOSTNAME
histroy # 查看历史指令
histroy 10 # 查看最近10条历史指令
!70 # 执行历史指令编号为70的指令

date
date +%Y # +%m +%d
date "+%Y-%m-%d %H:%M:%S"
date -s "2022-12-15 17:12:01" # 设置当前系统

cal # 显示本月日历
cal 2022 # 显示2022所有月份信息

find /home -name hi.txt # 查找home文件夹下文件名为hi.txt的文件
find /home -user tom # 查找属于用户tom的文件
find /home -size +200M # 查找大于200M的文件,+大于-小于,没有符号为等于,单位有k、M、G、T
find /home -size 200M # 查找大小为200M的文件

locate hi.txt # 基于数据库快速查询,显示hi.txt所在路径,使用前应该先试用updatedb创建locate数据库
updatedb # 创建locate数据库

which ls # 查找指令ls所在目录

grep -n "yes" hi.txt
cat hi.txt | grep -n -i "yes" # 过滤hi.txt文件,显示含有"yes"的行(忽略大小写,-i选项),并且显示行号(-n)实现

建立软链接

ln -s /root/ /home/myroot # 创建一个软链接myroot链接到root目录 如果cd /home/myroot/则会进入/root/目录
cd myroot # 进入后使用pwd显示的仍然是软链接的所在路径
pwd # 显示/home/myroot
rm /home/myroot # 删除软链接,不会影响root目录,软链接相当于Windows的快捷方式

压缩/解压缩指令

gzip /home/tom/hi.txt # 压缩hi.txt为hi.txt.gz,删除源文件
gunzip /home/tom/hi.txt.gz # 解压缩hi.txt.gz,删除源压缩文件

zip -r tom.zip /home/tom # 将/home/tom下所有的文件和文件夹递归压缩为tom.zip
unzip -d ./tom/ /home/tom.zip # 将/home/tom/tom.zip解压到当前目录的tom文件夹内 -d指定解压目录

tar -zcvf tom.tar.gz /home/tom/hi.txt /home/tom/hi.txt # 将hi.txt和hello.txt压缩到tom.tar.gz中
# -c 产生打包文件 -v 显示详细信息 -f 指定压缩文件名 -z 打包同时压缩 -x 解包.tar文件
tar -zcvf mytom.tar.gz /home/tom # 压缩tom文件夹到mytom.tar.gz,解压后为/home/tom/...
tar -zxvf mytom.tar.gz # 解压mytom.tar.gz到当前目录
tar -zxvf mytom.tar.gz -C /home/tom/mytom

进程操作

ps # -A列出所有进程 -w显示加宽可以显示较多的信息 -au显示较详细的信息 -aux显示所有包含其他使用者的进程

crond任务调度

crontab -e # 编辑crontab定时任务
crontab -l # 查询crontab任务
crontab -r # 删除当前用户所有的crontab任务

Debian软件包管理

Filename_Version-Reversion_Architectrue.deb
文件名_版本-修订版本_体系架构.deb

dpkg -i <package> # 安装一个在本地文件系统存在的Debian软件包
dpkg -r <package> # 移除一个已经安装的软件包
dpkg -P <package> # 移除已安装软件包及配置文件
dpkg -L <package> # 列出安装的软件包清单
dpkg -s <package> # 显示软件包的安装状态

APT软件包管理

工作原理:使用软件源配置文件/etc/apt/sources.list列出最合适访问的镜像地址

配置项遵循该格式:DebType AddressType://Hostaddress/Ubuntu Distribution Component1 Component2

例:deb http://cn.archive.ubuntu.com/ubuntu/ feisty main restricted universe multiverse

sudo apt-get update # 扫描每一个软件源服务器,并为该服务器所具有的软件包资源建立索引文件,存放在本地的/var/lib/apt/lists目录中
sudo apt-get upgrade # 将系统所有的软件包升级为最新
sudo apt-get install
sudo apt-get --reinstall install # 重新安装
sudo apt-get remove # 不完全卸载,只会删除该删除的和相关与其有依赖关系的软件包
sudo apt-get --purge remove # 完全卸载,删除软件包的同时,还删除软件包所使用的配置文件
sudo apt-get autoremove # 将不满足依赖关系的软件包自动卸载
sudo apt-get source # 下载源码包
sudo apt-get check # 检查依赖关系完整性,与下一条指令是一对
sudo apt-get -f install # 修复依赖depends关系完整性,但不会处理推荐recommends和建议suggests关系的软件包
sudo apt-get clean # 清理软件包缓冲目录/var/cache/apt/archives/,除了lock锁文件和partical目录
sudo apt-get autoclean # 清理旧的软件包缓存,保留最新的
sudo apt-cache show # 获取软件包的详细信息
sudo apt-cache policy # 查询软件包的安装状态
sudo apt-cache depends # 查看软件包的依赖
sudo apt-cache rdepends # 查看被谁依赖

apt-get install 分为四步

  1. 扫描本地存放的软件包更新列表(由apt-get update命令刷新更新列表)
  2. 进行软件依赖关系检查,找到支持该软件正常运行的所有软件包
  3. 从软件源所指的镜像站点中,下载相关软件包
  4. 解压软件包,自动完成应用程序的安装和配置

网络配置

/etc/interfaces/

/etc/hosts

/etc/resolv.conf

ifconfig # 查看本地网络配置,如果没有,需要安装net-tools
ifconfig eth0 # 只查看第一块网卡的配置
sudo ifconfig eth0 192.168.1.1 netmask 255.255.255.0 # 临时为eth0配置ip地址和子网掩码

配置动态IP

dhclient
sudo /etc/init.d/networking restart # 网络服务重启

DHCP分为四个阶段

  1. 客户端寻找DHCP服务器(DHCPDISCOVER),客户端广播申请动态IP的请求
  2. 服务器提供可分配的IP地址 (DHCPOFFER) :所有接收到请求的DHCP服务器都将向客户端提供一个IP地址;
  3. 客户端接受IP地址租借 (DHCPREQUEST) :客户端从多个IP选择中挑选一个通知DHCP服务器,并标识出所选中的服务器;
  4. 服务器确认租借IP(DHCPACK) : 被选中的DHCP服务器最后发出一个确认信息,包含IP地址、子网掩码、默认网关、DNS服务器和租借期 (客户端使用这个IP的这段时间,称为租借期)。

配置DNS服务器

nslookup www.baidu.com # 查询域名解析地址
nslookup www.baidu.com ns1.sfn.cn # 使用指定到DNS服务器解析百度
vim /etc/resolv.conf
nameserver 192.168.1.1
ping -c 3 www.baidu.com # ping百度,发送三次ICMP数据包

gcc

gcc编译过程

hello.c预处理-->hello.i编译-->hello.s汇编-->hello.o链接-->hello

  1. 预处理, C 编译器对各种预处理命令进行处理,包括头文件包含、宏定义的扩展、条件编译的选择等;
  2. 编译,将预处理得到的源代码文件,进行“翻译转换”,产生出机器语言的目标程序,得到机器语言的汇编文件;
  3. 汇编,将汇编代码翻译成了机器码,但是还不可以运行;
  4. 链接,处理可重定位文件,把各种符号引用和符号定义转换成为可执行文件中的合适信息,通常是虚拟地址。

hello.c

#include <stdio.h>

int main(void)
{
    printf("hello,world!\n");
    return 0;
}

预处理

gcc hello.c -o hello.i -E

此时查看hello.i的内容vim hello.i

# 1 "hello.c"
# 1 "<built-in>"
# 1 "<命令行>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<命令行>" 2
# 1 "hello.c"
# 1 "/usr/include/stdio.h" 1 3 4
# 27 "/usr/include/stdio.h" 3 4
# 1 "/usr/include/features.h" 1 3 4
# 375 "/usr/include/features.h" 3 4
# 1 "/usr/include/sys/cdefs.h" 1 3 4
# 392 "/usr/include/sys/cdefs.h" 3 4
# 1 "/usr/include/bits/wordsize.h" 1 3 4
# 393 "/usr/include/sys/cdefs.h" 2 3 4
# 376 "/usr/include/features.h" 2 3 4
# 399 "/usr/include/features.h" 3 4
# 1 "/usr/include/gnu/stubs.h" 1 3 4
# 10 "/usr/include/gnu/stubs.h" 3 4
# 1 "/usr/include/gnu/stubs-64.h" 1 3 4
# 11 "/usr/include/gnu/stubs.h" 2 3 4
# 400 "/usr/include/features.h" 2 3 4
# 28 "/usr/include/stdio.h" 2 3 4





# 1 "/usr/lib/gcc/x86_64-redhat-linux/4.8.5/include/stddef.h" 1 3 4
# 212 "/usr/lib/gcc/x86_64-redhat-linux/4.8.5/include/stddef.h" 3 4
typedef long unsigned int size_t;
# 34 "/usr/include/stdio.h" 2 3 4

# 1 "/usr/include/bits/types.h" 1 3 4
# 27 "/usr/include/bits/types.h" 3 4
# 1 "/usr/include/bits/wordsize.h" 1 3 4
# 28 "/usr/include/bits/types.h" 2 3 4


typedef unsigned char __u_char;
typedef unsigned short int __u_short;
typedef unsigned int __u_int;
typedef unsigned long int __u_long;


typedef signed char __int8_t;
typedef unsigned char __uint8_t;
typedef signed short int __int16_t;
typedef unsigned short int __uint16_t;
typedef signed int __int32_t;
typedef unsigned int __uint32_t;

typedef signed long int __int64_t;
typedef unsigned long int __uint64_t;







typedef long int __quad_t;
typedef unsigned long int __u_quad_t;
# 130 "/usr/include/bits/types.h" 3 4
# 1 "/usr/include/bits/typesizes.h" 1 3 4
# 131 "/usr/include/bits/types.h" 2 3 4


typedef unsigned long int __dev_t;
typedef unsigned int __uid_t;
typedef unsigned int __gid_t;
typedef unsigned long int __ino_t;
typedef unsigned long int __ino64_t;
typedef unsigned int __mode_t;
typedef unsigned long int __nlink_t;
typedef long int __off_t;
typedef long int __off64_t;
typedef int __pid_t;
typedef struct { int __val[2]; } __fsid_t;
typedef long int __clock_t;
typedef unsigned long int __rlim_t;
typedef unsigned long int __rlim64_t;
typedef unsigned int __id_t;
typedef long int __time_t;
typedef unsigned int __useconds_t;
typedef long int __suseconds_t;

typedef int __daddr_t;
typedef int __key_t;


typedef int __clockid_t;


typedef void * __timer_t;


typedef long int __blksize_t;




typedef long int __blkcnt_t;
typedef long int __blkcnt64_t;


typedef unsigned long int __fsblkcnt_t;
typedef unsigned long int __fsblkcnt64_t;


typedef unsigned long int __fsfilcnt_t;
typedef unsigned long int __fsfilcnt64_t;


typedef long int __fsword_t;

typedef long int __ssize_t;


typedef long int __syscall_slong_t;

typedef unsigned long int __syscall_ulong_t;



typedef __off64_t __loff_t;
typedef __quad_t *__qaddr_t;
typedef char *__caddr_t;


typedef long int __intptr_t;


typedef unsigned int __socklen_t;
# 36 "/usr/include/stdio.h" 2 3 4
# 44 "/usr/include/stdio.h" 3 4
struct _IO_FILE;



typedef struct _IO_FILE FILE;





# 64 "/usr/include/stdio.h" 3 4
typedef struct _IO_FILE __FILE;
# 74 "/usr/include/stdio.h" 3 4
# 1 "/usr/include/libio.h" 1 3 4
# 32 "/usr/include/libio.h" 3 4
# 1 "/usr/include/_G_config.h" 1 3 4
# 15 "/usr/include/_G_config.h" 3 4
# 1 "/usr/lib/gcc/x86_64-redhat-linux/4.8.5/include/stddef.h" 1 3 4
# 16 "/usr/include/_G_config.h" 2 3 4




# 1 "/usr/include/wchar.h" 1 3 4
# 82 "/usr/include/wchar.h" 3 4
typedef struct
{
  int __count;
  union
  {

    unsigned int __wch;



    char __wchb[4];
  } __value;
} __mbstate_t;
# 21 "/usr/include/_G_config.h" 2 3 4
typedef struct
{
  __off_t __pos;
  __mbstate_t __state;
} _G_fpos_t;
typedef struct
{
  __off64_t __pos;
  __mbstate_t __state;
} _G_fpos64_t;
# 33 "/usr/include/libio.h" 2 3 4
# 50 "/usr/include/libio.h" 3 4
# 1 "/usr/lib/gcc/x86_64-redhat-linux/4.8.5/include/stdarg.h" 1 3 4
# 40 "/usr/lib/gcc/x86_64-redhat-linux/4.8.5/include/stdarg.h" 3 4
typedef __builtin_va_list __gnuc_va_list;
# 51 "/usr/include/libio.h" 2 3 4
# 145 "/usr/include/libio.h" 3 4
struct _IO_jump_t; struct _IO_FILE;
# 155 "/usr/include/libio.h" 3 4
typedef void _IO_lock_t;





struct _IO_marker {
  struct _IO_marker *_next;
  struct _IO_FILE *_sbuf;



  int _pos;
# 178 "/usr/include/libio.h" 3 4
};


enum __codecvt_result
{
  __codecvt_ok,
  __codecvt_partial,
  __codecvt_error,
  __codecvt_noconv
};
# 246 "/usr/include/libio.h" 3 4
struct _IO_FILE {
  int _flags;




  char* _IO_read_ptr;
  char* _IO_read_end;
  char* _IO_read_base;
  char* _IO_write_base;
  char* _IO_write_ptr;
  char* _IO_write_end;
  char* _IO_buf_base;
  char* _IO_buf_end;

  char *_IO_save_base;
  char *_IO_backup_base;
  char *_IO_save_end;

  struct _IO_marker *_markers;

  struct _IO_FILE *_chain;

  int _fileno;



  int _flags2;

  __off_t _old_offset;



  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];



  _IO_lock_t *_lock;
# 294 "/usr/include/libio.h" 3 4
  __off64_t _offset;
# 303 "/usr/include/libio.h" 3 4
  void *__pad1;
  void *__pad2;
  void *__pad3;
  void *__pad4;
  size_t __pad5;

  int _mode;

  char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];

};


typedef struct _IO_FILE _IO_FILE;


struct _IO_FILE_plus;

extern struct _IO_FILE_plus _IO_2_1_stdin_;
extern struct _IO_FILE_plus _IO_2_1_stdout_;
extern struct _IO_FILE_plus _IO_2_1_stderr_;
# 339 "/usr/include/libio.h" 3 4
typedef __ssize_t __io_read_fn (void *__cookie, char *__buf, size_t __nbytes);







typedef __ssize_t __io_write_fn (void *__cookie, const char *__buf,
     size_t __n);







typedef int __io_seek_fn (void *__cookie, __off64_t *__pos, int __w);


typedef int __io_close_fn (void *__cookie);
# 391 "/usr/include/libio.h" 3 4
extern int __underflow (_IO_FILE *);
extern int __uflow (_IO_FILE *);
extern int __overflow (_IO_FILE *, int);
# 435 "/usr/include/libio.h" 3 4
extern int _IO_getc (_IO_FILE *__fp);
extern int _IO_putc (int __c, _IO_FILE *__fp);
extern int _IO_feof (_IO_FILE *__fp) __attribute__ ((__nothrow__ , __leaf__));
extern int _IO_ferror (_IO_FILE *__fp) __attribute__ ((__nothrow__ , __leaf__));

extern int _IO_peekc_locked (_IO_FILE *__fp);





extern void _IO_flockfile (_IO_FILE *) __attribute__ ((__nothrow__ , __leaf__));
extern void _IO_funlockfile (_IO_FILE *) __attribute__ ((__nothrow__ , __leaf__));
extern int _IO_ftrylockfile (_IO_FILE *) __attribute__ ((__nothrow__ , __leaf__));
# 465 "/usr/include/libio.h" 3 4
extern int _IO_vfscanf (_IO_FILE * __restrict, const char * __restrict,
   __gnuc_va_list, int *__restrict);
extern int _IO_vfprintf (_IO_FILE *__restrict, const char *__restrict,
    __gnuc_va_list);
extern __ssize_t _IO_padn (_IO_FILE *, int, __ssize_t);
extern size_t _IO_sgetn (_IO_FILE *, void *, size_t);

extern __off64_t _IO_seekoff (_IO_FILE *, __off64_t, int, int);
extern __off64_t _IO_seekpos (_IO_FILE *, __off64_t, int);

extern void _IO_free_backup_area (_IO_FILE *) __attribute__ ((__nothrow__ , __leaf__));
# 75 "/usr/include/stdio.h" 2 3 4




typedef __gnuc_va_list va_list;
# 90 "/usr/include/stdio.h" 3 4
typedef __off_t off_t;
# 102 "/usr/include/stdio.h" 3 4
typedef __ssize_t ssize_t;







typedef _G_fpos_t fpos_t;




# 164 "/usr/include/stdio.h" 3 4
# 1 "/usr/include/bits/stdio_lim.h" 1 3 4
# 165 "/usr/include/stdio.h" 2 3 4



extern struct _IO_FILE *stdin;
extern struct _IO_FILE *stdout;
extern struct _IO_FILE *stderr;







extern int remove (const char *__filename) __attribute__ ((__nothrow__ , __leaf__));

extern int rename (const char *__old, const char *__new) __attribute__ ((__nothrow__ , __leaf__));




extern int renameat (int __oldfd, const char *__old, int __newfd,
       const char *__new) __attribute__ ((__nothrow__ , __leaf__));








extern FILE *tmpfile (void) ;
# 209 "/usr/include/stdio.h" 3 4
extern char *tmpnam (char *__s) __attribute__ ((__nothrow__ , __leaf__)) ;





extern char *tmpnam_r (char *__s) __attribute__ ((__nothrow__ , __leaf__)) ;
# 227 "/usr/include/stdio.h" 3 4
extern char *tempnam (const char *__dir, const char *__pfx)
     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) ;








extern int fclose (FILE *__stream);




extern int fflush (FILE *__stream);

# 252 "/usr/include/stdio.h" 3 4
extern int fflush_unlocked (FILE *__stream);
# 266 "/usr/include/stdio.h" 3 4






extern FILE *fopen (const char *__restrict __filename,
      const char *__restrict __modes) ;




extern FILE *freopen (const char *__restrict __filename,
        const char *__restrict __modes,
        FILE *__restrict __stream) ;
# 295 "/usr/include/stdio.h" 3 4

# 306 "/usr/include/stdio.h" 3 4
extern FILE *fdopen (int __fd, const char *__modes) __attribute__ ((__nothrow__ , __leaf__)) ;
# 319 "/usr/include/stdio.h" 3 4
extern FILE *fmemopen (void *__s, size_t __len, const char *__modes)
  __attribute__ ((__nothrow__ , __leaf__)) ;




extern FILE *open_memstream (char **__bufloc, size_t *__sizeloc) __attribute__ ((__nothrow__ , __leaf__)) ;






extern void setbuf (FILE *__restrict __stream, char *__restrict __buf) __attribute__ ((__nothrow__ , __leaf__));



extern int setvbuf (FILE *__restrict __stream, char *__restrict __buf,
      int __modes, size_t __n) __attribute__ ((__nothrow__ , __leaf__));





extern void setbuffer (FILE *__restrict __stream, char *__restrict __buf,
         size_t __size) __attribute__ ((__nothrow__ , __leaf__));


extern void setlinebuf (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));








extern int fprintf (FILE *__restrict __stream,
      const char *__restrict __format, ...);




extern int printf (const char *__restrict __format, ...);

extern int sprintf (char *__restrict __s,
      const char *__restrict __format, ...) __attribute__ ((__nothrow__));





extern int vfprintf (FILE *__restrict __s, const char *__restrict __format,
       __gnuc_va_list __arg);




extern int vprintf (const char *__restrict __format, __gnuc_va_list __arg);

extern int vsprintf (char *__restrict __s, const char *__restrict __format,
       __gnuc_va_list __arg) __attribute__ ((__nothrow__));





extern int snprintf (char *__restrict __s, size_t __maxlen,
       const char *__restrict __format, ...)
     __attribute__ ((__nothrow__)) __attribute__ ((__format__ (__printf__, 3, 4)));

extern int vsnprintf (char *__restrict __s, size_t __maxlen,
        const char *__restrict __format, __gnuc_va_list __arg)
     __attribute__ ((__nothrow__)) __attribute__ ((__format__ (__printf__, 3, 0)));

# 412 "/usr/include/stdio.h" 3 4
extern int vdprintf (int __fd, const char *__restrict __fmt,
       __gnuc_va_list __arg)
     __attribute__ ((__format__ (__printf__, 2, 0)));
extern int dprintf (int __fd, const char *__restrict __fmt, ...)
     __attribute__ ((__format__ (__printf__, 2, 3)));








extern int fscanf (FILE *__restrict __stream,
     const char *__restrict __format, ...) ;




extern int scanf (const char *__restrict __format, ...) ;

extern int sscanf (const char *__restrict __s,
     const char *__restrict __format, ...) __attribute__ ((__nothrow__ , __leaf__));
# 443 "/usr/include/stdio.h" 3 4
extern int fscanf (FILE *__restrict __stream, const char *__restrict __format, ...) __asm__ ("" "__isoc99_fscanf")

                               ;
extern int scanf (const char *__restrict __format, ...) __asm__ ("" "__isoc99_scanf")
                              ;
extern int sscanf (const char *__restrict __s, const char *__restrict __format, ...) __asm__ ("" "__isoc99_sscanf") __attribute__ ((__nothrow__ , __leaf__))

                      ;
# 463 "/usr/include/stdio.h" 3 4








extern int vfscanf (FILE *__restrict __s, const char *__restrict __format,
      __gnuc_va_list __arg)
     __attribute__ ((__format__ (__scanf__, 2, 0))) ;





extern int vscanf (const char *__restrict __format, __gnuc_va_list __arg)
     __attribute__ ((__format__ (__scanf__, 1, 0))) ;


extern int vsscanf (const char *__restrict __s,
      const char *__restrict __format, __gnuc_va_list __arg)
     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__format__ (__scanf__, 2, 0)));
# 494 "/usr/include/stdio.h" 3 4
extern int vfscanf (FILE *__restrict __s, const char *__restrict __format, __gnuc_va_list __arg) __asm__ ("" "__isoc99_vfscanf")



     __attribute__ ((__format__ (__scanf__, 2, 0))) ;
extern int vscanf (const char *__restrict __format, __gnuc_va_list __arg) __asm__ ("" "__isoc99_vscanf")

     __attribute__ ((__format__ (__scanf__, 1, 0))) ;
extern int vsscanf (const char *__restrict __s, const char *__restrict __format, __gnuc_va_list __arg) __asm__ ("" "__isoc99_vsscanf") __attribute__ ((__nothrow__ , __leaf__))



     __attribute__ ((__format__ (__scanf__, 2, 0)));
# 522 "/usr/include/stdio.h" 3 4









extern int fgetc (FILE *__stream);
extern int getc (FILE *__stream);





extern int getchar (void);

# 550 "/usr/include/stdio.h" 3 4
extern int getc_unlocked (FILE *__stream);
extern int getchar_unlocked (void);
# 561 "/usr/include/stdio.h" 3 4
extern int fgetc_unlocked (FILE *__stream);











extern int fputc (int __c, FILE *__stream);
extern int putc (int __c, FILE *__stream);





extern int putchar (int __c);

# 594 "/usr/include/stdio.h" 3 4
extern int fputc_unlocked (int __c, FILE *__stream);







extern int putc_unlocked (int __c, FILE *__stream);
extern int putchar_unlocked (int __c);






extern int getw (FILE *__stream);


extern int putw (int __w, FILE *__stream);








extern char *fgets (char *__restrict __s, int __n, FILE *__restrict __stream)
     ;
# 638 "/usr/include/stdio.h" 3 4
extern char *gets (char *__s) __attribute__ ((__deprecated__));


# 665 "/usr/include/stdio.h" 3 4
extern __ssize_t __getdelim (char **__restrict __lineptr,
          size_t *__restrict __n, int __delimiter,
          FILE *__restrict __stream) ;
extern __ssize_t getdelim (char **__restrict __lineptr,
        size_t *__restrict __n, int __delimiter,
        FILE *__restrict __stream) ;







extern __ssize_t getline (char **__restrict __lineptr,
       size_t *__restrict __n,
       FILE *__restrict __stream) ;








extern int fputs (const char *__restrict __s, FILE *__restrict __stream);





extern int puts (const char *__s);






extern int ungetc (int __c, FILE *__stream);






extern size_t fread (void *__restrict __ptr, size_t __size,
       size_t __n, FILE *__restrict __stream) ;




extern size_t fwrite (const void *__restrict __ptr, size_t __size,
        size_t __n, FILE *__restrict __s);

# 737 "/usr/include/stdio.h" 3 4
extern size_t fread_unlocked (void *__restrict __ptr, size_t __size,
         size_t __n, FILE *__restrict __stream) ;
extern size_t fwrite_unlocked (const void *__restrict __ptr, size_t __size,
          size_t __n, FILE *__restrict __stream);








extern int fseek (FILE *__stream, long int __off, int __whence);




extern long int ftell (FILE *__stream) ;




extern void rewind (FILE *__stream);

# 773 "/usr/include/stdio.h" 3 4
extern int fseeko (FILE *__stream, __off_t __off, int __whence);




extern __off_t ftello (FILE *__stream) ;
# 792 "/usr/include/stdio.h" 3 4






extern int fgetpos (FILE *__restrict __stream, fpos_t *__restrict __pos);




extern int fsetpos (FILE *__stream, const fpos_t *__pos);
# 815 "/usr/include/stdio.h" 3 4

# 824 "/usr/include/stdio.h" 3 4


extern void clearerr (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));

extern int feof (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;

extern int ferror (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;




extern void clearerr_unlocked (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
extern int feof_unlocked (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
extern int ferror_unlocked (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;








extern void perror (const char *__s);






# 1 "/usr/include/bits/sys_errlist.h" 1 3 4
# 26 "/usr/include/bits/sys_errlist.h" 3 4
extern int sys_nerr;
extern const char *const sys_errlist[];
# 854 "/usr/include/stdio.h" 2 3 4




extern int fileno (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;




extern int fileno_unlocked (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
# 873 "/usr/include/stdio.h" 3 4
extern FILE *popen (const char *__command, const char *__modes) ;





extern int pclose (FILE *__stream);





extern char *ctermid (char *__s) __attribute__ ((__nothrow__ , __leaf__));
# 913 "/usr/include/stdio.h" 3 4
extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));



extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;


extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 943 "/usr/include/stdio.h" 3 4

# 2 "hello.c" 2

int main(void)
{
    printf("hello,world!\n");
    return 0;
}

编译

gcc -S hello.i
gcc -S hello.i -o hello.s

以上两条命令均可生成hello.s,查看hello.s的内容,会发现是汇编语言

	.file	"hello.c"
	.section	.rodata
.LC0:
	.string	"hello,world!"
	.text
	.globl	main
	.type	main, @function
main:
.LFB0:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movl	$.LC0, %edi
	call	puts
	movl	$0, %eax
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE0:
	.size	main, .-main
	.ident	"GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-44)"
	.section	.note.GNU-stack,"",@progbits

汇编

gcc -c hello.s

该命令生成hello.o,其内容是01机器码

链接

链接分为两种:动态链接、静态链接,gcc默认使用动态链接

动态链接生成的程序文件较小,但是要依赖动态库才可运行,静态链接可以脱离动态库运行,但是生成的文件体积较大

gcc hello.o -o hello

该命令生成最终的可执行文件hello

[git@centos fork.git]$ ./hello 
hello,world!
[git@centos fork.git]$ ls -lh
-rwxrwxr-x. 1 git git 8.2K 1月  12 00:29 hello
-rw-rw-r--. 1 git git   82 1月  12 00:13 hello.c
-rw-rw-r--. 1 git git  17K 1月  12 00:19 hello.i
-rw-rw-r--. 1 git git 1.5K 1月  12 00:28 hello.o
-rw-rw-r--. 1 git git  449 1月  12 00:25 hello.s

若要使用静态链接,centos7.6若报错/usr/bin/ld: 找不到 -lccollect2: 错误:ld 返回 1

说明需要需要安装glibc-static

sudo yum install glibc-static

静态链接

gcc hello.o -o hello_static -static
[git@centos fork.git]$ ls -lh
-rwxrwxr-x. 1 git git 8.2K 1月  12 00:29 hello
-rw-rw-r--. 1 git git   82 1月  12 00:13 hello.c
-rw-rw-r--. 1 git git  17K 1月  12 00:19 hello.i
-rw-rw-r--. 1 git git 1.5K 1月  12 00:28 hello.o
-rw-rw-r--. 1 git git  449 1月  12 00:25 hello.s
-rwxrwxr-x. 1 git git 842K 1月  12 00:38 hello_static

可以看到,hello_static的体积比hello要大得多

使用-I可以把指定的目录添加到头文件搜索路径中

gcc hello.c -I /home/git/include

库文件链接

gcc hello.c 

Makefile工程管理器

Make工程管理器是一个“自动编译管理器”,自动是指能够根据时间戳自动发现更新过的文件而减少编译的工作量,它通过读入Makefile文件的内容来执行大量的编译工作

Make将只编译改动过的代码文件,而不用完全编译

Makefile是make工具读入的唯一配置文件

make工具的选项

-C dir读入指定目录下的Makefile

-f file读入当前目录下的file文件作为Makefile

-i忽略所有的命令执行错误

-I dir指定被包含的Makefile所在目录

-n 只打印要执行的命令,但不执行这些命令

-p 显示make变量数据库和隐含规则

-s 在执行命令时不显示命令

-w 如果make在执行过程中改变目录,打印当前目录名

Makefile文件组成

  1. 由make工具创建的目标体 (target),通常是目标文件或可执行文件
  2. 要创建的目标体所依赖的文件 (dependency_file)
  3. 创建每个目标体时需要运行的命令 (command)

命令行前面必须是一个TAB键,否则编译错误为:** missing separator. Stop.*

Makefile基本结构

target:dependcy_files

< TAB >command

例:

hello.o:hello.c hello.h
	gcc -c hello.c -o hello.o

gcc选项:

  1. -Wall 表示允许发出gcc所有有用的报警信息
  2. -c 只编译不链接,生成目标文件.o
  3. -o file 表示把输出文件输出到file里
test:f1.o f2.o main.o # 目标文件:依赖
	gcc f1.o f2.o main.o -o test
f2.o:f2.c
	gcc -c -Wall f2.c -o f2.o
f1.o:f1.c
	gcc -c -Wall f1.c -o f1.o
main.o:main.c
	gcc -c -Wall main.c -o main.o
.PHONY:clean
clean:
	rm *.o test

变量定义的方式

  1. 递归展开方式 VAR=var
  2. 简单方式 VAR:=var
  3. 变量使用 $(VAR)
  4. ?=定义变量
  5. +=可以为已经定义了的变量添加新的值

递归展开方式的例子

foo=$(bar)
bar=$(ugh)
ugh=Huh?
echo $(foo) # 结果为Huh?

简单方式的赋值方式类似C语言

?=定义变量的例子:

dir:=/foo/bar
FOO?=bar # 如果FOO先前没有定义过,那么FOO将是bar

预定义变量

AR 库文件维护程序的名称,默认值为ar。AS汇编程序的名称,默认值为as

CCC编译器的名称,默认值为cc

CPP C预编译器的名称,默认值为$(CC) -E
CXX C++编译器的名称,默认值为g++

FC FORTRAN编译器的名称,默认值为f77

RM 文件删除程序的名称,默认值为rm -f

OBJS= f1.o f2.o
OBJS+= main.o
CC= gcc
CFLAGS= -c -Wall
test:$(OBJS)
	$(CC) $(OBJS) -o test
f2.o:f2.c
	$(CC) $(CFLAGS) -c f2.c -o f2.o
f1.o:f1.c
	$(CC) $(CFLAGS) -c f1.c -o f1.o
main.o:main.c
	$(CC) $(CFLAGS) -c main.c -o main.o
.PHONY:clean
clean:
	rm *.o test

自动变量

$* 不包含扩展名的目标文件名称

$+ 所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件

$< 第一个依赖文件的名称

$? 所有时间戳比目标文件晚的的依赖文件,并以空格分开

$@ 目标文件的完整名称

$^ 所有不重复的目标依赖文件,以空格分开

$% 如果目标是归档成员,则该变量表示目标的归档成员名称

OBJS= f1.o f2.o
OBJS+= main.o
CC= gcc
CFLAGS= -c -Wall -I include
test:$(OBJS)
	$(CC) $(OBJS) -o test
f2.o:$<
	$(CC) $(CFLAGS) -c f2.c -o $@
f1.o:f1.c
	$(CC) $(CFLAGS) -c f1.c -o $@
main.o:main.c
	$(CC) $(CFLAGS) -c main.c -o main.o
.PHONY:clean
clean:
	rm *.o test

config.mk文件

config.mk文件用于存放一些变量的声明

例:

OBJS=f1.o f2.o
OBJS+=main.o
CFLAGS=-c -Wall -I include
CC= gcc

对应的Makefile,使用include引入config.mk

include config.mk
test:$(OBJS)
	$(CC) $(OBJS) -o test
f2.o:$<
	$(CC) $(CFLAGS) -c f2.c -o $@
f1.o:f1.c
	$(CC) $(CFLAGS) -c f1.c -o $@
main.o:main.c
	$(CC) $(CFLAGS) -c main.c -o main.o
.PHONY:clean
clean:
	rm *.o test

Makefile隐含规则

  1. *.o的目标的依赖目标会自动推导为*.c,并且其生成命令是$(CC) -c $(CPPFLAGS) $(CFLAGS)
include config.mk
test:$(OBJS)
	$(CC) $(OBJS) -o test
f2.o:f2.c
f1.o:f1.c
main.o:main.c
.PHONY:clean
clean:
	rm *.o test
  1. <n>目标依赖于<n>.o,通过运行C编译器来运行链接程序生成(一般是ld),其生成命令是:$(CC) $(LDFLAGS) <n>.o
  2. $(LOADFLAGS) $(LDLIBS)这个规则对于只有一个源文件的工程也有效,同时也对多个Object文件(由不同源文件生成)的也有效

例如:

​ 规则:x:x.o y.o z.o并且x.c y.c z.c三个文件均存在时

隐含规则将会执行以下命令:

cc -c x.c -o x.o
cc -c y.c -o y.o
cc -c z.c -o z.o
cc x.o y.o z.o -o x

使用隐含规则2、3的的例子

CFLAGS=-c -Wall -I include
test:f1.o f2.o main.o
.PHONY:clean
clean:
	rm *.o test

VPATH虚路径

当make在当前目录下找不到需要的文件时,就会去VPATH指定到目录下寻找,VPATH的目录与目录之间用冒号或空格分隔

格式:VPATH = 目录1:目录2:目录3:目录n

VPATH = 目录1 目录2 目录3 目录n

例:VPATH = src:../headers

当前目录永远是最优先搜索的地方

CFLAGS=-c -Wall -I include
VPATH=src1 src2 main
f1:f1.o f2.o main.o

.PHONY:clean
clean:
	find ./ -name "*.o" -exec rm {} \;; rm f1

Makefile的嵌套

f1文件夹下的Makefile

../$(OBJS_DIR)/f1.o:f1.c
	$(CC) -c $^ -o $@

f1文件夹所在目录的Makefile

cc=gcc
sUBDIRS=f1 \
		f2 \
		main \
		obj
0BJS=f1.o f2.o main.o
BIN=myapp
0BJS_DIR=obj
BIN_DIR=bin
export CC 0BJS BIN 0BJS_DIR BIN_DIR # export让子Makefile也可以调用这些变量

all:CHECK_DIR $(SUBDIRS)
CHECK_DIR:
	mkdir -p $(BIN_DIR)
$(SUBDIRS):ECHO # 依次进入各级目录(f1 f2 main obj)进行make
	make -c $@
ECHO:
	@echo $(SUBDIRS)
	@echo begin compile
CLEAN:
	$(RM) $(OBJS_DIR)/*.o
	@rm -rf $(BIN_DIR)

文件I/O

Linux 把大部分系统资源当作文件并呈现给用户,用户只需按照文件 I/O 的方式,就能完成数据的输入输出。 Linux 文件按其代表的具体对象

  1. 普通文件,即一般意义上的文件、磁盘文件
  2. 设备文件,代表的是系统中一个具体的设备
  3. 管道文件、 FIFO 文件,一种特殊文件,常用于进程间通信
  4. 套接字(socket)文件,主要用在网络通信方面

文件I/O常用打开、读、写、关闭四种操作

Linux 系统提供的文件 I/O 接口函数,是以最基本的系统服务形式提供的,又称它们为基本 I/O 函数。这些函数有个共同的特点: 它们都通过文件描述符(file descriptor)来完成对指定文件的 I/O 操作

文件描述符

文件描述符 fd(file descriptor)是进程中用来表示某个文件的整数, 有的文献资料中又称它为文件句柄(file handle)

有效的文件描述符取值范围从 0 开始,直到系统定义的某个极限值,这些指定范围的整数, 实际上是进程文件描述符表的索引。文件描述符表是进程用来保存它所打开的文件信息的、 由操作系统维护的一个登记表,用户程序不能直接访问该表。文件描述符的取值范围,
反映了文件描述符表的大小, 表示这个进程最多可以同时打开多少个文件。 在大多数 Linux系统中,可通过命令ulimit -n查询到这个数值的大小。

对于内核而言, 进程所打开的文件都由文件描述符引用。当进程打开一个现存文件或创建一个新文件时,内核返回一个文件描述符给进程。当读、写一个文件时, 先调用 open()或 creat()函数取得代表该文件的文件描述符 fd, 然后将 fd 作为参数传递给 read()或 write()
等文件操作函数

通常情况下,文件描述符 0、 1、 2 在进程启动时已被占用,代表进程在启动过程中打开的文件。

文件描述符 含义 桌面/服务器 Linux 嵌入式 Linux
0 标准输入(stdin) 键盘 串口终端
1 标准输出(stdout) 终端屏幕 串口终端
2 标准错误(stderr) 终端屏幕 串口终端

文件I/O常用头文件

#include<sys/types.h> // 定义数据类型,如 ssize_t, off_t 等
#include <fcntl.h> // 定义 open, creat 等函数原型,创建文件权限的符号常量 S_IRUSR 等
#include <unistd.h> // 定义 read, write, close, lseek 等函数原型
#include <errno.h> // 与全局变量 errno 相关的定义
#include <sys/ioctl.h> // 定义 ioctl 函数原型

open()

函数原型:int open(const char *pathname, int flags, mode_t mode)

只有创建新文件时, open()的最后一个参数 mode 才会起作用,否则将忽略它

open()的参数 flags,当设置了 O_CREAT 标志时,可以创建一个新文件,也可用另外一个函数 creat()创建新文件

头文件:unistd.h

参数说明

参数 打开文件标志 含义
pathname - C 字符串形式的文件名
flags O_RDONLY 以只读方式打开文件,与 O_WRONLY 和 O_RDWR 互斥
O_WRONLY 以只写方式打开文件,与 O_RDONLY 和 O_RDWR 互斥
O_RDWR 以可读写方式打开文件,与 O_WRONLY 和 O_RDONLY 互斥
O_CREAT 如果要打开的文件不存在,则创建该文件
O_EXCL 该标志与 O_CREAT 共同使用时,会去检查文件是否存在,若文件不存 在则创建该文件,否则将导致打开文件失败。此外,打开文件链接时, 使用该标志将导致失败
O_NOCTTY 如果要打开的文件为终端设备,则不把该终端设备当成控制终端
O_TRUNC 若文件存在且以可写方式打开,此标志会清除文件内容,并将其长度置 为 0
O_APPEND 读写文件都从文件的尾部开始,所写入的数据会以追加的方式插入到文 件末尾
O_NONBLOCK 以不可阻塞方式打开文件,也就是不管有无数据需要读写或者等待,都 会立即返回
O_NDELAY 以不可阻塞方式打开文件,也就是不管有无数据需要读写或者等待,都 会立即返回(已过时,由 O_NONBLOCK 替代)
O_SYNC 以同步方式打开文件
O_NOFOLLOW 如果文件名所指向的文件本身为符号链接,则会导致打开文件失败
O_DIRECTORY 如果文件名所指向的文件本身并非目录,则会导致打开文件失败
mode 创建文件的权限模式,可以使用八进制数来表示新文件的权限,也可采 用<fcntl.h>中定义的符号常量,如表 11.3 所示。当打开已有文件时,将 忽略这个参数

mode可用的符号常量表

符号常量 含义 符号常量 含义
S_IRWXU 0x700 所属用户读、写和执行权限 S_IRWXG 0x070 组用户读、写和执行权限
S_IRUSR 0x400 所属用户读权限 S_IRGRP 0x040 组用户读权限
S_IWUSR 0x200 所属用户写权限 S_IWGRP 0x020 组用户写权限
S_IXUSR 0x100 所属用户执行权限 S_IXGRP 0x010 组用户执行权限
S_IRWXO 0x007 其他用户读、写和执行权限 S_IWOTH 0x002 其他用户写权限
S_IROTH 0x004 其他用户读权限 S_IXOTH 0x001 其他用户执行权限

creat()

函数原型:int creat(const char *pathname, mode_t mode);

头文件:unistd.h

creat()的参数 pathname 和 mode 的含义, 与 open()的同名参数含义相同, 某些条件下调用 creat()的效果与 open()是相同的,open()或者 creat()都能创建新文件

与open()的区别:

  1. creat()创建文件时, 如果文件已存在,则会把已存在的文件内容清空、长度截为 0,然后返回对应的文件描述符;如果文件不存在,则直接创建,然后返回创建文件的描述符;
  2. 当 open()的参数 flags 设置了 O_CREAT 时,如果文件已存在,则直接打开并返回文件描述符; 如果文件不存在,则创建新文件,然后返回对应的文件描述符

close()

函数原型:int close(int fd);

头文件:unistd.h

参数:fd文件描述符

返回值:成功返回0,失败返回-1,同时设置全局errno

当一个文件被打开多次时,比如被多个进程同时打开,或在同一个进程中被打开多次,每打开一次,该文件内部的引用计数就增加 1,对该文件每调用一次 close(),文件引用计数则减 1,当计数值减到 0 时,内核才关闭该文件。当进程终止时,内核会回收进程资源,也
按上述规则关闭进程打开的全部文件

read()

函数原型:ssize_t read(int fd, void *buf, size_t count);

参数:fd,文件描述符;buf用于接收数据的缓冲区;count要请求读取数据的字节数

返回值:读取成功,返回实际读取的字节数,如果已到达文件结尾,返回 0,否则返回-1 表示出错, 同时设置全局变量 errno 报告具体错误的原因

实际读取的字节数,可以小于请求的字节数 count

  1. 文件长度小于请求的长度,即还没达到请求的字节数时,就已到达文件结尾。如果文件是 50 字节长,而 read 请求读 100 字节(count=100),则首次调用 read 时,它返回 50,紧接着的下次调用,它返回 0,表示已到达文件结尾
  2. 读设备文件时,有些设备每次返回的数据长度小于请求的字节数,如终端设备一般按行返回,即每读到一行数据,就返回

关于ssize_t

ssize_t 和 size_t 是系统头文件中定义的数据类型, ssize_t 表示 signed int, size_t 表示unsigned int, 两者均是与 CPU 位数有关的整型值,在 32 位系统中, 表示 32 位整型值 int,在 64 位系统中,表示 64 位整型值 long int

/* 在 32 位系统中 */
typedef int ssize_t; /* 32 位有符号整型值 */
typedef unsigned int size_t; /* 32 位无符号整型值*/
/* 在 64 位系统中 */
typedef long int ssize_t; /* 64 位有符号整型值 */
typedef unsigned long int size_t; /* 64 位无符号整型值 */

write()

函数原型: ssize_t write(int fd, const void *buf, size_t count);

参数:fd,文件描述符;buf,数据缓冲区,存放要写入的数据;count,请求写入的字节数,实际写入的字节数可以小于请求写的字节数

返回值:写入成功,返回实际写入的字节数,出错则返回-1, 同时设置全局变量 errno 报告具体错误的原因,比如 errno=ENOSPC 表示磁盘空间已满

fsync()

write()函数一旦返回,表明所写的数据已提交到系统内部缓存了,但此时数据并不一定已经被写入磁盘等持久存储设备中。要确保已修改过的数据全部写入持久存储设备中,正确的做法是调用 fsync 函数进行文件数据同步,强制把已修改过的文件数据写入持久存储设备中。

嵌入式系统通常采用闪存(Flash Memory)作系统盘, write()返回后也应该用 fsync()及时把修改过的文件数据写入闪存中。如果不调用 fsync(),在 write()返回后马上就复位或重新上电,则所作的修改就可能没有被更新, 从而造成文件数据丢失。

函数原型:int fsync(int fd);

头文件:unistd.h

参数:fd,文件描述符

返回值:fsync()调用在将该文件已修改数据全部写入磁盘后才会返回。操作成功返回 0,否则返回-1, 同时设置全局变量 errno 报告具体错误的原因

另外一个函数sync(),功能与fsync()类似,但是sync()是针对整个系统而言的,只有系统中修改过的缓存数据全部写入到磁盘中,才会返回,如果系统修改过的缓存数据量很大,或者有程序正在向磁盘写入数据,sync()要很长时间才能返回

lseek()

lseek()函数可以改变文件的读写位置

Linux文件的数据读写方式,可以分为顺序读写和随机读写两种,普通磁盘文件一般都可以随机读写,可以通过lseek()函数改变文件读写位置,顺序读写只能从头读到尾,管道文件、套接字文件、FIFO都是按顺序读写的,不支持lseek,设备文件是否支持lseek,与具体的设备有关

函数原型:off_t lseek(int fd, off_t offset, int whence);

参数:offset,偏移量,代表相对文件开头的偏移字节数,可以为0;whence,偏移的参照点, 有效值是 SEEK_SET、 SEEK_CUR、 SEEK_END

  1. SEEK_SET 设置新的读写位置为从文件开头算起,偏移 offset 字节
  2. SEEK_CUR 设置新的读写位置为从当前所在的位置算起,偏移 offset 字节,正值表示往文件尾部偏移,负值表示往文件头部偏移
  3. SEEK_END 设置新的读写位置为从文件结尾算起,偏移 offset 字节,正值表示往文件尾部偏移,负值表示往文件头部偏移

返回值:off_t就是long int,操作成功, lseek()返回新的读写位置(相对文件开头的偏移量,非负数,可以为0);否则返回-1 表示操作不成功, 同时设置全局变量 errno 报告具体错误的原因 ,如果对顺序文件进行lseek操作,errno=ESPIPE

文件中的字符串buf的结构

BOF(0) 1 2 3 4 ... n-1 n EOF
H e l l ... d !

cur为0时,实际是指向了第一个字符前的位置,也就是BOF,此时读取三个字节,实际上是Hel被读取,当前cur为3,相对SEEK_SET偏移量为3,相对SEEK_END偏移量为cur - sizeof(buf)

ioctl()

函数原型:int ioctl(int d, int request, ...);

头文件:sys/ioctl.h

参数:d,文件描述符;request,视文件而定

返回值:成功返回0,失败返回-1,errno报告错误原因,有的设备文件可能返回一个正数表示输出参数

文件操作示例

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

int main(int argc, char* argv[])
{
    char rule[] = "{\"app\": \"Raspiber-handler\",\"battery\": 0}";
    char filename[] = "rule.json";
    int fd = -1;
    int res = 0;
    char buf[256] = { 0 };
    
    fd = open(filename, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR
                            |S_IRGRP|S_IWGRP | S_IROTH);
    if (fd < 0)
    {
        printf("打开文件%s失败,errno=%d\n", filename, errno);
        return -1;
    }
    
    res = write(fd, rule, sizeof(rule) - 1); // 之所以sizeof(rule) - 1,是因为要去掉字符串尾部的'\0'
    printf("写入%d字节到%s中\n", res, filename);
    fsync(fd); // 同步文件
    close(fd);

    fd = open(filename, O_RDONLY);
    if (fd < 0)
    {
        printf("打开文件%s失败,errno=%d\n", filename, errno);
        return -1;
    }

    res = read(fd, buf, sizeof(buf));
    buf[res] = '\0';
    printf("从%s文件中读到%d字节,data=%s\n",filename, res, buf);
    close(fd);

    return 0;
}

lseek操作示例

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include "log.h"

int main(int argc, char* argv[])
{
    int fd = -1;
    int res, cur, new_cur, offset = 0;
    char filename[] = "rule.json";
    char buf[128] = { 0 };

    fd = open(filename, O_RDONLY);
    if (fd < 0)
    {
        GW_LOG(LOG_ERROR, "打开文件失败,errno=%d", errno);
        return -1;
    }

    res = read(fd, buf, 6);
    buf[res] = '\0';
    GW_LOG(LOG_DEBUG, "从文件%s中读取%d字节,偏移量:%d,data=%s", filename,\
                                                            res, offset, buf);
    
    cur = lseek(fd, 0, SEEK_CUR);
    offset = 18 - cur;
    new_cur = lseek(fd, offset, SEEK_CUR);
    if (new_cur == -1)
        GW_LOG(LOG_ERROR, "lseek失败,errno=%d", errno);
    else
        GW_LOG(LOG_DEBUG, "SEEK_CUR:上次位置:%d,当前偏移量:%d,当前位置:%d",\
                                                         cur, offset, new_cur);
    
    res = read(fd, buf, sizeof(buf));
    buf[res] = '\0';
    GW_LOG(LOG_DEBUG, "从文件%s中读取%d字节,偏移量:%d,data=%s", filename, res,\
                                                                new_cur, buf);

    cur = lseek(fd, 0, SEEK_END);
    offset = -7; // 从结尾向开头
    new_cur = lseek(fd, offset, SEEK_END); // 移动到自结尾-7处
    if (new_cur == -1)
        GW_LOG(LOG_ERROR, "lseek失败,errno=%d", errno);
    else
        GW_LOG(LOG_DEBUG, "SEEK_CUR:上次位置:%d,当前偏移量:%d,当前位置:%d",\
                                                         cur, offset, new_cur);
    
    res = read(fd, buf, sizeof(buf));
    buf[res] = '\0';
    GW_LOG(LOG_DEBUG, "从文件%s中读取%d字节,偏移量:%d,data=%s", filename,\
                                                            res, new_cur, buf);
    
    lseek(fd, -17, SEEK_END);
    GW_LOG(LOG_DEBUG, "当前相对SEEK_END的偏移量:%d", lseek(fd, -23, SEEK_END));
    res = read(fd, buf, 1);
    buf[res] = '\0';
    GW_LOG(LOG_DEBUG, "读取%d个字节,data=%s",res, buf);
    close(fd);

    return 0;
}

进程

创建进程

pid_t fork( void):这个函数创建一个子进程,将本进程的数据、堆栈和代码,复制一个副本给子进程,两者跑的代码是一样的,fork函数在子进程中返回0,父进程中返回创建的子进程的pid,错误返回-1,失败原因存于errno中,可以使用perror("fork")获取

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

int main(void)
{
    pid_t pid;
    char* message;
    int n;
    printf("只有父进程执行这个printf\n");
    pid = fork();
    printf("父子进程都会执行这个printf\n");
    if(pid < 0)
    {
        perror("fork failed\n");
        exit(1);
    }else if (pid == 0)
    {
        message = "这是子进程\n";
        n = 6;
    }else
    {
        message = "这是父进程\n";
    }
    for (; n > 0; n--)
    {
        printf("%d:\t%s", n, message);
        sleep(1);
    }
    
    return 0;
}

阻塞进程

pid_t wait (int* status)

这个函数可以阻塞当前进程,只有当子进程终止时,本进程才可以继续运行

返回值:成功执行返回子进程PID,错误返回-1,失败原因存于errno中,可以使用perror("wait")获取

pid_t waitpid(pid_t pid, int* status, int options):这个函数可以阻塞当前进程,只有当子进程终止时,本进程才可以继续运行

返回值:返回子进程结束状态值,状态值由status参数返回,子进程的pid也会一并返回

参数:如果不在意结束状态值,参数status可以设为NULL,如果options参数指定WNOHANG,可以使父进程不阻塞而立即返回0

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

int main(void)
{
    printf("Father And Child both runs\n");
    pid_t pid;
    pid = fork();
    if (pid < 0)
    {
        perror("fork failed!\n");
        exit(1); // 结束进程
    }
    else if (pid == 0)
    {
        printf("Child is running\n");
        int i;
        sleep(10);
        exit(1);
    }
    else
    {
        int stat_val;
        printf("Father is waitting...\n");
        waitpid(pid, &stat_val, 0); // 在此处阻塞,等待子进程pid结束
        if (WIFEXITED (stat_val)) // 如果子进程正常中止WIFEXITED取出来的就是非零
        {
            printf("Child exited with code % d\n", WIFEXITED(stat_val));
        }
        else if (WIFEXITED(stat_val))
        printf("Child terminated abnormally, signal % d \n",WIFEXITED(stat_val));
    }
    
    return 0;
}

终止进程

正常终止

  1. 主函数return
  2. 调用void exit(int status);函数,结束当前进程将会刷新(流)缓存区
  3. 调用void _exit(int status);_Exit函数
  4. 进程的最后一个线程在其启动例程中执行返回语句,线程返回值不会使用进程返回值,最后一个线程返回时,进程以终止状态0返回
  5. 进程最后一个线程调用pthread_exit()函数

异常终止

  1. 调用abort
  2. 当进程收到某种信号
  3. 最后一个线程对“取消”(cancellation)请求做出响应

进程终止的处理过程

exit函数关闭一个进程的所有文件描述符,收回所有的代码、数据和堆栈,然后终止进程。进程终止时,会向父进程发送一个SIGCHLD信号,等待它的终止码status被接受,status在0~255之间,等待父进程接收它的终止码的进程叫做“僵尸进程”,父进程执行wait()接受子进程终止码,init进程总会接收其他进程的终止码

int atexit(void (*func)void);

atexit函数可以登记进程终止前调用的函数,最多登记32个,多次登记的会被多次调用;最先登记的最后调用,类似与栈的原理,先进后出,后进先出

/*子进程终止后变为僵尸进程*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
main()
{
    pid_t pid = fork();
    if (pid < 0)
    {
        perror("fork");
        exit();
    }
    if (pid > 0)
    {
        while(1);
    }
}

僵尸进程

子进程先于父进程结束,而父进程没有回收子进程,释放子进程占用的资源,就会产生僵尸进程

父进程先于子进程结束,子进程会被init进程接管,子进程结束后,init进程会回收其占用的资源

忽略子进程的退出的信号,避免产生僵尸进程

signal(SIGCHLD, SIG_IGN); // IGN:ignore

修改当前进程的代码空间

exec 族函数并不创建进程,exec 只是用一个全新的程序替换掉当前进程代码空间里的内容

exec 族函数只有在出错的时候才会返回,如果成功,该函数无返回,否则返回-1。

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);

后缀 p:表示使用 filename 做参数,如果 filename 中包含“/” 则视其为路径名,否则将会在 PATH 环境变量所指定的各个目录中搜索该文件,如 execlp()函数。无后缀 p 则必须使用路径名来指定可执行文件的位置,如 execl()函数。
后缀 e:表示可以传递一个指向环境字符串指针数组的指针,环境数组需要以 NULL 结束,如 execvpe()函数。而无此后缀的函数则将当前进程的 environ 变量复制给新的程序,如execv()函数。
后缀 l: 表示使用 list 形式来传递新程序的参数,所有这些参数均以可变参数的形式在exec 中给出,最后一个参数需要是 NULL 用以表示没有更多的参数了,如 execl()函数。
后缀 v: 表示使用 vector 形式来传递新程序的参数,传给新程序的所有参数放入一个字符串数组中,数组以 NULL 结束以表示结尾,如 execv()函数。

sample3.c

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

extern char **environ; /* 全局环境变量 */

int main(int argc, char *argv[]) 
{
    int i;
    
    printf("argc = %d\n", argc); /* 打印参数个数 */
    printf("args :");
    
    for (i = 0 ; i < argc; i++)
        printf(" %s ", argv[i]); /* 打印参数表 */
    printf("\n");

    i = 0;
    while(environ[i])
        puts(environ[i++]); /* 打印环境变量表 */
    printf("\n");
    
    return 0;
}

exec_sample3.c

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

char *env_init[] = {"USER=git", "HOME=/home/git/", NULL}; /* 为子进程定义环境变量 */

int main(int argc, char *argv[]) 
{
    pid_t pid;

    if ((pid = fork())< 0)
    { 
        perror("fork error");
    } 
    else if (pid == 0)
    {
	/*int execle(const char *path, const char *arg,..., char * const envp[]);*/
        /*子进程装载新程序*/
        /***********************************************************************
           execle:第一个参数是要更换的程序的路径,第二个参数到倒倒数第三个参数,是被
           更换程序的参数列表,必须以NULL结尾,因此倒数第二个参数为(char*)0,最后一
           参数是环境变量
        ***********************************************************************/
        execle("sample3", "sample3", "hello", "world", "addParam", (char *)0, env_init);
        perror("execle error"); /* execle 失败时才执行 */
        exit(-1);
    } 
    else 
    {
    exit(0); /* 父进程退出 */
    }

    return -1;
}

编译

gcc exec_sample3.c -o exec_sample3
gcc sample3.c -o sample3

运行

./exec_sample3

守护进程(Daemon)

守护进程的父进程是 init 进程,因为它真正的父进程在 fork 出该子进程后就先于该子进程 exit 退出了,所以它是一个由 init 领养的孤儿进程。守护进程是非交互式程序,没有控制终端,所以任何输出(无论是向标准输出设备还是标准错误输出设备的输出) 都需要特殊处
理。

守护进程的名字往往结尾有一个d,如sshd

使用daemon()函数创建守护进程

函数原型:int daemon(int nochdir, int noclose);

参数说明:

			1. 参数 nochdir:如果传入 0,则 daemon 函数将调用进程的工作目录设置为根目录,否则保持原有的工作目录不变  
			1. 参数 noclose:如果传入 0,则 daemon 函数会将标准输入、标准输出、标准错误重定向到/dev/null 文件中,否则不改变这些文件描述符。 

返回值:成功返回0,失败返回 -1,并且设置errno

daemond.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>

int main(void)
{
    int fd;

    time_t curtime;
    if(daemon(0,0) == -1) 
    {
        perror("daemon error");
        exit(-1);
    }
    fd = open("/tmp/daemon.log", O_WRONLY | O_CREAT|O_APPEND, 0644);
    if (fd < 0 ) 
    {
        perror("open error");
        exit(-1);
    }
    while(1) 
    {
        // 向/tmp/daemon.log写入时间信息
        curtime = time(0);
        char *timestr = asctime(localtime(&curtime));
        write(fd, timestr, strlen(timestr));
        sleep(60);
    }
    close(fd);
    return 0;
}

信号

信号(signal),又称为软中断信号,用来通知进程发生了异步事件。进程之间可以互相发送信号, 内核也可以因为内部事件而给进程发送信号。信号的作用仅仅是通知进程发生了什么事件,并不向进程传递任何数据。

信号的处理

  1. 使用类似中断的处理程序,对于需要处理的信号,进程可以指定相应的处理函数,由该函数来负责对信号的处理;
  2. 忽略某个信号,对该信号不做任何处理,就像未发生过一样;
  3. 对该信号的处理保留系统默认值,这种缺省操作对于大部分的信号来说就是使得进程被终止。

常用信号

#include <signal.h>
信号名称 信号描述
SIGALRM 在用 alarm()函数设置的计数器超时时,产生此信号
SIGINT 当用户按中断键(Ctrl+c)时,终端产生此信号并发送给前台进程组的进程
SIGKILL 这是两个不能捕捉、忽略的信号之一,它提供了一种可以杀死任一进程的方法
SIGSTOP 这是两个不能捕捉、忽略的信号之一,用于停止一个进程。
SIGPIPE 如果在写管道时读进程已经终止,则产生该信号。
SIGTERM 这是由 kill 命令发送的默认信号
SIGCHLD 在一个进程终止或者结束时,将 SIGCHLD 信号发送给它的父进程。
SIGABRT 调用 abort()函数时产生此信号,进程异常终止
SIGSEGV 该信号指示进程进行了一次无效的内存引用
SIGUSR1,SIGUSR2 这是用户定义的两个信号,可以用于应用程序间通信

可以使用kil命令向指定进程发送信号

kill [-option] PID

kill -l列出所有支持的信号

kill -sSIGSTOP PID # 指定发送的信号为SIGSTOP
kill PID # 如果没有指定信号,默认发送SIGTREM
kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

信号相关函数

sigaction()

函数原型:int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

包含于signal.h

参数:

  1. 参数 signum 指出需要改变处理方法的信号,如 SIGINT 信号, 但 SIGKILL 和 SIGSTOP这两个信号是不可捕捉的

  2. 参数 act 和 oldact 是一个 sigaction 结构体(包含于signal.h)的指针, act 是要设置的对信号的新处理方式,而 oldact 则为原来对信号的处理方式。

    #include <unistd.h>
    #include <stdio.h>
    #include <signal.h>
    
    /* 信号处理函数 */
    void ouch(int sig) 
    { 
        printf("\nOuch! - I got signal %d\n", sig);
    }
    
    int main(int argc, char *argv[]) 
    {
        struct sigaction act;
        act.sa_handler = ouch; // 设置信号处理函数
        
        sigemptyset(&act.sa_mask); // 清空信号屏蔽集
        act.sa_flags = SA_RESETHAND; // 设置信号处理之后恢复默认的处理方式
        sigaction(SIGINT, &act, NULL); // 设置 SIGINT 信号的处理方法
        
        //进入循环等待信号发生
        while (1) 
        { 
            printf("sleeping\n");
            sleep(1);
        }
    
        return 0;
    }
    

返回值:成功返回0,失败返回1

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

/* 信号处理函数 */
void ouch(int sig) 
{ 
    printf("\nOuch! - I got signal %d\n", sig);
}

int main(int argc, char *argv[]) 
{
    struct sigaction act;
    act.sa_handler = ouch; // 设置信号处理函数
    
    sigemptyset(&act.sa_mask); // 清空信号屏蔽集
    act.sa_flags = SA_RESETHAND; // 设置信号处理之后恢复默认的处理方式
    sigaction(SIGINT, &act, NULL); // 设置 SIGINT 信号(整数2)的处理方法
    
    //进入循环等待信号发生
    while (1) 
    { 
        printf("sleeping\n");
        sleep(1);
    }

    return 0;
}
运行结果
gcc sigaction_sample.c -o sigaction_sample
./sigaction_sample 
sleeping
sleeping
sleeping
^C
Ouch! - I got signal 2
sleeping
sleeping
^C

kill()

用于向指定进程发送一个指定的信号

函数原型:int kill(pid_t pid, int sig);

参数:pid为指定进程PID,sig为要发送的信号

返回值:成功返回 0,否则返回-1

头文件:signal.h

kill_sample.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void print_exit_status(int status)
{
    // 输出子进程退出状态
    if (WIFEXITED(status))
    {
        printf("正常退出,退出状态:%d\n", WEXITSTATUS(status));
    }
    else if (WIFSIGNALED(status))
    {
        printf("异常退出,信号代码:%d\n", WTERMSIG(status));
    }
    else
    {
        printf("其他状态\n");
    }
}

int main(int argc, char* argv[])
{
    pid_t pid;
    int status;
    
    if ((pid = fork()) < 0)
    {
        perror("fork error:");
        exit(-1);
    }

    else if (pid == 0) // 子进程代码段
    {
        while(1)
        {
            printf("子进程sleeping\n");
            sleep(1);
        }
        exit(0);
    }
    else //父进程代码段
    {
        sleep(2);
        printf("父进程向子进程发送一个SIGINT\n");
        kill(pid, SIGINT);
        if (wait(&status) != pid) // 获取子进程的退出状态
        {
            perror("wait error:");
            exit(-1);
        }
        print_exit_status(status);
    }

    return 0;
}
运行结果
gcc kill_sample.c -o kill_sample
./kill_sample 
子进程sleeping
子进程sleeping
父进程向子进程发送一个SIGINT
异常退出,信号代码:2

管道

管道允许两个或多个进程彼此发送信息,是进程间的通信机制

管道的读写进程是同时进行的,当管道满时,写进程挂起,管道空,读进程挂起

匿名管道

管道有两个端,读端和写端,每个端都有一个文件描述符,使用完文件描述符后,应该及时调用close()关闭该文件描述符

int pipe(int fd[2]);

返回值:成功返回0,失败返回-1,错误原因存于errno中,

参数:f[0]存读端文件描述符,f[1]存写端文件描述符

读管道

ssize_t read (int fd, void *buf, size_t count);

返回值:实际读取的字节数,错误返回-1,错误代码存入errno

参数:文件描述符,存放读取的数据的缓存区,希望读取的字节数

  1. 使用read()函数读管道
  2. 进程读一个写端关闭的管道,read()返回0,表示输入结束
  3. 进程读一个写端打开的空管道,进程休眠,直到管道有新输入
  4. 进程从管道中读多于现有量的字节,返回当前的所有内容,read()返回实际读取的字节

写管道

ssize_t write (int fd,const void * buf,size_t count);

返回值:实际写入的字节数,错误返回-1,错误代码存入errno

参数:文件描述符,将被写入的字符串,要写入的字符串所占字节

  1. 如果写一个读端关闭的管道,写操作失败,将一个SIGPIPE信号发送给写进程,该信号的缺省操作是终止进程
  2. 如果进程写入管道的字节数少于管道保存到数,write()保证是原子操作,即写进程将完成它的系统调用,不会被其他进程抢占

未命名管道是通过文件描述符来操作的,一般只有创建该管道的父进程和其子进程能够使用

典型序列

  1. 父进程调用pipe()创建管道
  2. 父进程创建子进程
  3. 写进程关闭读端,指定的读进程关闭写端
  4. 进程使用read()write两个系统调用来操作
  5. 进程使用完管道后关闭激活的文件描述
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define READ 0
#define WRITE 1
char* phrase = "Stuff this in your pipe and smoke it";

int main()
{
    int fd[2], bytesRead;
    char message[100];
    pipe(fd); // 创建管道
    if (fork() == 0)
    {
        close(fd[READ]);
        printf("Child procee write pipe. \n");
        write(fd[WRITE], phrase, strlen(phrase) +1); // 写
        printf("[%d]Writed pipe\n", getpid());
        close(fd[WRITE]);
    }
    else
    {
        close (fd[WRITE]);
        bytesRead = read (fd[READ], message, 100);
        printf("Father procee read pipe. \n");
        printf("[%d]Read %d bytes:%s\n", getpid(), bytesRead, message);
        close(fd[READ]);
    }
}

命名管道

  1. 使用命名管道,使用管道文件,可以进行任意进程之间的通信
  2. 打开管道时,可以指定读写方式,通过文件IO进行操作
  3. 管道文件虽然可见,但是文件大小为0,管道的数据实际上是在内存中的
  4. 读端和写端都关闭时,管道内的内容会被自动释放

create_fifo.c

#include<stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include "log.h"

int main(void)
{
	if (mkfifo("myfifo", 0660) < 0)
	{
		perror("mkfifo");
		exit(-1);
	}
	else
	{
		GW_LOG(LOG_DEBUG, "fifo had created:myfifo");
	}
	return 0;
}

write_fifo.c

#include<stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include "log.h"

#define N 32
int main()
{
    char buf[N];
    int pfd;
    if ((pfd = open("myfifo",O_WRONLY)) < 0)
    {
        perror("open");
        exit(-1);
    }
    else
    {
        GW_LOG(LOG_DEBUG, "myfifo is opened");
    }

    while(1)
    {
        fgets(buf, N, stdin);
        if (strcmp(buf, "quit\n") == 0) break;
        write(pfd, buf, N);
    }
    close(pfd);
    
    return 0;
}

read_fifo.c

#include<stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include "log.h"

#define N 32
int main()
{
    char buf[N];
    int pfd;
    if ((pfd = open("myfifo",O_WRONLY)) < 0)
    {
        perror("open");
        exit(-1);
    }
    else
    {
        GW_LOG(LOG_DEBUG, "myfifo is opened");
    }

    while(1)
    {
        fgets(buf, N, stdin);
        if (strcmp(buf, "quit\n") == 0) break;
        write(pfd, buf, N);
    }
    close(pfd);
    
    return 0;
}

编译和使用

编译
gcc create_fifo.c log.c -o create_fifo
gcc read_fifo.c log.c -o read_fifo
gcc write_fifo.c log.c -o write_fifo
使用
./create_fifo
./read_fifo # 先打开读端,不打开写端,会阻塞
./write_fifo # 使用一个单独的终端打开写端
运行结果
写端
[DEBUG] [Jan  7 2023 10:54:02][write_fifo.c: main:19]   myfifo is opened
hello # 用户手动输入
读端
[DEBUG] [Jan  8 2023 03:14:20][read_fifo.c: main:21]    myfifo is opened
[INFO]  [Jan  8 2023 03:14:20][read_fifo.c: main:26]    the length of string is 1

[INFO]  [Jan  8 2023 03:14:20][read_fifo.c: main:27]    hello

共享内存

共享内存允许两个不相关的进程访问同一个逻辑内存的进程间通信方法, 是在两个正在运行的进程之间共享和传递数据的一种方式

不同进程之间的共享内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用 C语言 malloc()分配的内存一样。

使用共享内存区

  1. 指定一个名字参数调用 shm_open, 以创建一个新的共享内存区对象(或打开一个已经存在的共享内存区对象);
  2. 调用 mmap 把这个共享内存区映射到调用进程的地址空间
  3. 调用 munmap() 取消共享内存映射
  4. 调用 shm_unlink()函数删除共享内存段

注意:在编译 POSIX 共享内存应用程序时需要加上-lrt 参数

打开或创建一个共享内存区

int shm_open(const char *name, int oflag, mode_t mode);

参数:

  1. name,创建的共享内存的名称
  2. oflag,为以下列出的标志的或值
    1. O_RDONLY:共享内存以只读方式打开 ;
    2. O_RDWR:共享内存以可读写方式打开 ;
    3. O_CREAT:共享内存不存在才创建 ;
    4. O_EXCL:如果指定了 O_CREAT,但共享内存已经存在时返回错误 ;
    5. O_TRUNC:如果共享内存已经存在则将其大小设置为 0
  3. mode,只有指定了O_CREAT后才有效,用于设定共享内存的权限,与open()函数类似

返回值:成功返回创建或打开的共享内存描述符,与文件描述符作用相同,供后续操作使用,失败则返回-1

头文件:fcntl.h 、sys/mman.h、sys/stat.h

注意:新创建或打开的共享内存大小默认为 0,需要设置大小才能使用

删除共享内存

int shm_unlink(const char *name);

函数成功返回 0,否则返回-1。参数 name 为共享内存的名字

头文件:fcntl.h 、sys/mman.h、sys/stat.h

设置共享内存大小

int ftruncate(int fd, off_t length);

函数成功返回 0,失败返回-1。
参数 fd 为需要调整的共享内存或者文件的描述符, length 为需要调整的大小

头文件:unistd.h、sys/types.h

映射共享内存

void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);

参数:

  1. addr,指向映射存储区的起始地址,通常设为NULL,让系统自己选择起始地址
  2. length,映射的字节数
  3. prot,对映射存储区的保护要求,对指定映射存储区的保护要求不能超过open模式访问权限,可以为以下选项的或值
    1. PROT_READ: 映射区可读
    2. PROT_WRITE:映射区可写
    3. PROT_EXEC:映射区可执行
    4. PROT_NONE: 映射区不可访问
  4. flag,映射标志位,可以为以下标志的或值
    1. MAP_FIXED: 返回值必须等于 addr。 因为这不利于可移植性,所以不鼓励使用此标志;
    2. MAP_SHARED: 多个进程对同一个文件的映射是共享的,一个进程对映射的内存做了修改,另一个进程也会看到这种变化
    3. MAP_PRIVATE: 多个进程对同一个文件的映射不是共享的,一个进程对映射的内存做了修改,另一个进程并不会看到这种变化
  5. fd,被映射的文件描述符或共享内存描述符
  6. offset,要映射在文件中的起始偏移量

返回值:成功,返回映射后指向共享内存的虚拟地址,失败,返回 MAP_FAILED 值

取消共享内存映射

int munmap(void *addr, size_t length);

取消映射后再对映射地址访问的话,会导致调用进程收到SIGSEGV信号

参数:addr为mmap()函数返回的地址,length是映射的字节数

返回值:函数成功返回 0,否则返回-1

sharememReader.c

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

#define SHMSIZE 10 // 共享内存大小, 10 字节
#define SHMNAME "shmtest" // 共享内存名称

int main()
{
    int fd;
    char *ptr; // 指向共享内存区的虚拟地址
    
    // 创建共享内存s
    fd = shm_open(SHMNAME, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
    if (fd < 0) 
    {
        perror("shm_open error");
        exit(-1);
    }
    
    // 映射共享内存
    ptr = mmap(NULL, SHMSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (ptr == MAP_FAILED) 
    {
        perror("mmap error");
        exit(-1);
    }

    ftruncate(fd, SHMSIZE); // 设置共享内存大小

    // 读起始地址,判断值是否为 18 
    while (*ptr != 18) 
    { 
        sleep(1); // 不是 18,继续读取
    }

    printf("ptr : %d\n", *ptr); // 数据是 18,打印显示
    
    munmap(ptr, SHMSIZE); // 取消内存映射
    shm_unlink(SHMNAME); // 删除共享内存

    return 0;
}
编译
gcc sharememReader.c -o sharememReader -lrt

sharemenWriter.c

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

#define SHMSIZE 10 // 共享内存大小, 10 字节
#define SHMNAME "shmtest" // 共享内存名称

int main()
{
    int fd;
    char *ptr; // 指向共享内存区的虚拟地址

    // 创建共享内存
    fd = shm_open(SHMNAME, O_CREAT | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR);
    if (fd<0) 
    {
        perror("shm_open error");
        exit(-1);
    }
    
    // 设置共享内存大小
    ftruncate(fd, SHMSIZE);

    // 映射共享内存
    ptr = mmap(NULL, SHMSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (ptr == MAP_FAILED)
    {
        perror("mmap error");
        exit(-1);
    }

    *ptr = 18; // 往起始地址写入 18
    
    munmap(ptr, SHMSIZE); // 取消映射
    shm_unlink(SHMNAME); // 删除共享内存
    
    return 0;
}
编译
gcc sharememReader.c -o sharememReader -lrt

运行

./sharememReader &
./sharememWriter
运行结果
[git@centos fork.git]$ ./sharememReader &
[1] 11690
[git@centos fork.git]$ ./sharememWriter 
[git@centos fork.git]$ ptr : 18

[1]+  完成                  ./sharememReader

消息队列

消息队列(Message Queue)是消息的链接表,存放在内核中,并由消息队列标识符标识

消息队列提供了从一个进程向另外一个进程发送一块数据的方法,而且每个数据块都被认为有一个类型,接收者进程接收的数据库可以有不同的类型值。

使用消息队列发送消息几乎可以完全回避命名管道同步和阻塞的问题,但是每个数据块最大长度是有上限的,系统上全体队列的最大总长度也有一个上限

消息队列函数

头文件:sys/msg.h、sys/types.h、sys/ipc.h

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

int msgget(key_t key, int msgflg);

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

msgget()创建和访问一个消息队列

函数原型:int msgget(key_t key, int msgflg);

参数:key,消息队列的名字;msgflg:由九个权限标志组成,用法和创建文件时使用的mode模式标志是一样的

信号量

信号量用于解决进程间的同步和互斥问题,是一种进程间通信机制,是一个特殊的变量,变量的值代表对应资源可用数量,0则为目前没有可用资源。

信号量分两种:二值信号量、计数信号量

二值信号量:只有0和1两个值,资源可用为1,不可用为0

计数信号量:信号可以在0~32767之前,代表可用资源个数

信号量只能进行两种原子操作:P、V

P操作:占用一个资源,如果当前没有可用资源,则阻塞当前进程,进入信号量的等待队列,直到系统给当前进程分配资源,唤醒该进程

V操作:如果信号量等待队列中没有阻塞进程等待资源,则释放一个资源,如果等待队列中有阻塞进程在等待资源,则唤醒一个阻塞进程

POSIX提供两种信号量:命名信号量、匿名信号量(基于内存的信号量)

命名信号量:命名信号量可以让不同的进程通过名字获取到信号量

匿名信号量:匿名信号量只能放在进程的共享内存区中

命名信号量操作流程

sem_open()-->sem_wait()-->sem_trywait()-->sem_post()-->sem_getvalue()-->sem_close()-->sem_unlink()

匿名信号量操作流程

sem_init()-->sem_wait()-->sem_trywait()-->sem_post()-->sem_getvalue()-->sem_destroy()

创建或打开命名信号量

函数原型:sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);

参数列表:name为指针,指向信号量的名字

​ oflag可以是O_CREAT如果信号量不存在则创建,必须给出mode和value;O_EXCEL如果信号量存在,而oflag为O_CREAT或 O_EXCEL,则返回错误

​ mode为信号量权限位,同open()函数

​ value为信号量的初始化值

返回值:成功返回指向信号量的指针,失败返回SEM_FAILED,信号量类型为sem_t,表示当前可用资源数

头文件:#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>

关闭命名信号量

函数原型:int sem_close(sem_t *sem);

参数:sem为信号量指针

返回值:成功返回0,失败返回-1

头文件:#include <semaphore.h>

初始化匿名信号量

函数原型: int sem_init(sem_t *sem, int pshared, unsigned int value);

参数列表:sem为信号量指针

​ pshared为0则表示信号量只能在一个进程的线程间使用,且这个信号量应该对所有进程内的线程可见,比如使用全局变量或 堆上动态分配的变量;如果非0,则表示信号量要在多个进程间使用,信号量应该位于共享内存的某个区域中。

注:fork()创建的子进程会继承父进程的内存映射,因此他也可以访问父进程可以访问的信号量

​ value为信号量的初始化值,代表可用资源数

返回值:成功返回0,失败返回-1

头文件:#include <semaphore.h>

P操作

函数原型:int sem_wait(sem_t *sem);

功能:如果信号量值>0,则将信号量-1并且返回,表示获取到资源,如果信号量=0,则对应进程/线程阻塞,直到信号量的值>0再将其-1后返回

参数:sem为信号量指针

返回值:成功返回0,失败返回-1,信号量值不变,设置errno

头文件:#include <semaphore.h>

V操作

函数原型:int sem_post(sem_t *sem);

功能:将指定信号量的资源数+1,唤醒正在等待该信号量的任意的某个进程/线程

参数:sem为信号量指针

返回值:成功返回0,失败返回-1,信号量值不变,设置errno

删除命名信号量

函数原型:int sem_unlink(const char *name);

功能:移除一个命名信号量

参数:指向信号量名的指针

返回值:成功返回0,失败返回-1,设置errno

销毁匿名信号量

函数原型:int sem_destroy(sem_t *sem);

功能:销毁由sem_init()初始化的匿名信号量

参数:sem为信号量指针

返回值:成功返回0,失败返回-1,设置errno

信号量示例

unnamedSem_shm_server.c

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>
#include <errno.h>

#define MAPSIZE 100 // 共享内存区大小为100Bytes

int main(int argc, char* argv[])
{
    int shmid; // 信号量
    char* ptr; // 指向共享内存区的虚拟地址
    sem_t* semid; // 共享内存区

    // 判断参数是否为2个
    if (argc != 2)
    {
        printf("使用%s\n", argv[0]);
        return -1;
    }
    shmid = shm_open(argv[1], O_RDWR|O_CREAT, 0644); // 创建共享内存区
    if (shmid == -1)
    {
        printf("内存区打开出错\n");
        return -1;
    }
    ftruncate(shmid, MAPSIZE); // 设置共享内存区大小
    // 映射共享内存区
    ptr = mmap(NULL, MAPSIZE, PROT_READ|PROT_WRITE, MAP_SHARED, shmid, 0);
    strcpy(ptr, "\0");

    semid = sem_open(argv[1], O_CREAT, 0644, 0); // 创建信号量
    if (semid == SEM_FAILED)
    {
        printf("信号量创建/打开失败\n");
        return -1;
    }

    // 等待共享内存区被其他进程写
    sem_wait(semid); // P操作,等待信号量被释放
    printf("接受到数据=%s", ptr); // 从共享内存区中读
    strcpy(ptr, "\0");

    munmap(ptr, MAPSIZE); // 取消共享内存映射
    close(shmid); // 关闭共享内存
    sem_close(semid); // 关闭信号量

    sem_unlink(argv[1]); // 删除信号量
    shm_unlink(argv[1]); // 删除共享内存

    return 0;

}

Client 端

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>
#include <errno.h>

#define MAPSIZE 100

int main(int argc, char* argv[])
{
    int shmid; // 共享内存区
    char* ptr; // 指向共享内存区的虚拟地址
    sem_t* semid; // 信号量

    if (argc != 2)
    {
        printf("使用%s\n", argv[0]); // argv[1]指定信号量和共享内存区的名字
        return -1;
    }

    shmid = shm_open(argv[1], O_RDWR, 0); // 打开共享内存区
    if (shmid == -1)
    {
        printf("打开共享内存区失败\n");
        return -1;
    }

    ftruncate(shmid, MAPSIZE);

    ptr = mmap(NULL, MAPSIZE, PROT_READ|PROT_WRITE, MAP_SHARED, shmid, 0);

    semid = sem_open(argv[1], 0);
    if (semid == SEM_FAILED)
    {
        printf("打开信号量失败\n");
        return -1;
    }
    
    printf("客户端输入:");
    fgets(ptr, MAPSIZE, stdin); // 从标准输入读取需要写入共享内存的值
    sem_post(semid); // V操作,释放信号量/通知服务端
    
    munmap(ptr, MAPSIZE); // 取消共享内存区映射
    close(shmid);
    sem_close(semid);

    return 0;

}

编译

gcc unnamedSem_shm_server.c -o unnamedSem_shm_server -lrt -lpthread
gcc unnamedSem_shm_client.c -o unnamedSem_shm_client -lrt -lpthread

运行

./unnamedSem_shm_server test &
./unnamedSem_shm_client test

运行结果

[git@centos fork.git]$ gcc unnamedSem_shm_server.c -o unnamedSem_shm_server -lrt -lpthread
[git@centos fork.git]$ gcc unnamedSem_shm_client.c -o unnamedSem_shm_client -lrt -lpthread
[git@centos fork.git]$ ./unnamedSem_shm_server &
[2] 10228
参数不够或太多,应在./unnamedSem_shm_server后添加一个信号量/共享内存区的名字
[2]+  退出 255              ./unnamedSem_shm_server
[git@centos fork.git]$ ./unnamedSem_shm_server test &
[2] 10267
[git@centos fork.git]$ ./unnamedSem_shm_client 
参数不够或太多,应在./unnamedSem_shm_client后添加一个信号量/共享内存区的名字
[git@centos fork.git]$ ./unnamedSem_shm_client test
客户端输入=hi,server,im client! #<--stdin "hi,server,im client!"
接受到数据=hi,server,im client!
[1]-  完成                  ./unnamedSem_shm_server test
[git@centos fork.git]$ 

多线程

线程是进程内部的顺序执行流,是进程的实际运作单位,也是操作系统能够调度的最小单位,一个进程中可以并发多个线程,每个线程可以并行执行不同的任务

进程和线程的关系

  1. 一个线程只能属于一个进程,而进程可以有多个线程,但是至少有一个主线程
  2. 资源分配给进程,进程内部的线程共享进程的所有资源
  3. 进程是资源的基本单位,线程是调度和分配的基本单位
  4. 进程是独立拥有资源的独立单位,线程不拥有系统资源,但是可以访问所属进程的资源
  5. 创建和撤销进程时,由系统进行资源的分配和回收,系统的开销要大于创建和撤销线程的开销

多线程的优点

  1. 通信和数据交换方便:进程间的数据传递只能通过通信的方式,而线程由于共享进程的数据空间,因此同一进程的线程间的数据传递是非常快捷方便的
  2. 高效利用CPU:多线程可以避免单个线程长时间使用CPU,可以让多个线程交替使用CPU

POSIX Thread

POSIX Thread是一个多线程标准接口,该接口根据功能划分可分为线程管理、互斥量、条件变量、同步

编写POSIX Thread的程序时,源码需要包含pthread.h,编译时需要加上-lpthread

线程管理

线程管理包含了常用的线程操作:创建、终止、等待、分离、设置属性

线程ID

线程ID可以视作线程的句柄,用来引用一个线程,类型为pthread_t

获取当前线程ID

函数原型:pthread_t pthread_self(void);

功能:获取当前线程ID

返回值:返回线程ID

比较两个线程ID是否相同

函数原型:int pthread_equal(pthread_t t1, pthread_t t2);

参数列表:被比较的两个线程ID

返回值:如果相同则返回非0,不同返回0

创建线程

函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

功能:创建并运行一个线程

参数列表:thread为指向线程ID的指针;attr是指向线程属性结构体的指针;start_routine为线程执行的函数;arg为start_rountine的参数

返回值:成功返回0,失败返回错误码:EAGAIN表示系统没有创建线程的资源;EINVAL表示attr参数无效;EPERM表示调用程序没有适当 的权限来设定调度策略或 attr 指定的参数

终止线程

当所属进程终止时,所有线程都会终止,主线程会阻塞等待到所有线程终止后终止,或者使用pthread_exit()终止

函数原型:void pthread_exit(void *retval);

功能:终止当前线程

参数:retval可以将线程返回值当做pthread_exit的参数传入,这个值同样被 pthread_join()当作退出状态处理,如果进程的最后一个线程 调用了 pthread_exit(),进程会带着状态返回值 0 退出。

线程的连接与分离

线程可以分为分离(detached)和非分离(joinable)两种

  1. 分离线程是退出后会释放自身资源的线程
  2. 非分离线程退出后不会立刻释放资源,需要另外一个线程为其调用pthread_join()函数或进程退出才会释放资源

只有非分离线程才是可连接的,分离线程退出时不会报告退出状态

线程的分离

函数原型:int pthread_detach(pthread_t thread);

参数:thread为线程ID

返回值:成功返回0,失败返回非0错误码:EINVALthread线程不可分离;ESRCH没有找到ID为thread的线程

线程的连接

非分离线程可以被连接

函数原型:int pthread_join(pthread_t thread, void **retval);

参数:thread为线程ID;retval指向接收线程返回值的变量,无返回值时可以设置为NULL

返回值:成功返回0,失败返回非0错误码:EINVALthread线程不可分离;ESRCH没有找到ID为thread的线程

为了防止内存泄露,长时间运行的程序最终应该为每个线程调用pthread_detach()或pthread_join()

线程属性

线程的基本属性包括:栈大小、调度策略、线程状态

一般先创建属性变量,再将属性通过pthread_create()传递给线程,一个属性对象可以多次被不同线程的pthread_create()使用

线程属性变量的类型为pthread_attr_t

初始化属性变量

函数原型:int pthread_attr_init(pthread_attr_t *attr);

参数:attr是指向属性变量的指针

返回值:成功返回0,失败返回非0错误码

销毁属性变量

函数原型:int pthread_attr_destroy(pthread_attr_t *attr);

参数:attr是指向属性变量的指针

返回值:成功返回0,失败返回非0错误码

线程状态

线程可以有两种状态:分离线程、非分离线程

获取线程状态

函数原型:int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);

参数:attr是指向属性变量的指针;detachstate是指向存放线程状态的变量的指针,分离:PTHREAD_CREATE_DETACHED,非分离:PTHREAD_CREATE_JOINABLE

返回值:成功返回0,失败返回非0错误码

设置线程状态

获取线程状态

函数原型:int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

参数:attr是指向属性变量的指针;detachstate是要设置的值,分离:PTHREAD_CREATE_DETACHED,非分离:PTHREAD_CREATE_JOINABLE

返回值:成功返回0,失败返回非0错误码

线程栈

每个线程都有一个独立的调用栈,线程栈的大小在线程创建时就已经设置,Linux系统线程的默认栈大小为8MB,只有主线程的栈会在运行过程中自动增长,开发人员可以通过属性对象设置和获取栈大小

获取线程栈大小

函数原型:int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);

参数:attr是指向属性变量的指针;stacksize是指向存放栈大小的变量的指针

返回值:成功返回0,失败返回非0错误码

设置线程栈大小

函数原型:int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

参数:attr是指向属性变量的指针;stacksize设置的栈大小

返回值:成功返回0,失败返回非0错误码

线程安全

多个线程同时调用某个函数可能会产生一些错误,那么这个函数被称为“非线程安全函数”;如果多个线程同时调用某个函数,同时执行,不会相互干扰,那么这个函数被称为“线程安全函数”

互斥量

互斥量用来保护临界区,是一种特殊的变量。

互斥量有两种状态:locked、unlocked

当互斥量为locked时,表示有线程持有这个互斥锁,其他试图获取这个互斥锁的线程都会阻塞,并进入这个互斥锁的等待队列

当互斥量为unlocked时,试图获取这个互斥锁的线程会得到它而不被阻塞

每个为互斥量加锁的线程,使用临界资源后,应该立即为其解锁

创建互斥量

静态互斥量初始化

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

动态互斥量初始化

函数原型:int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

参数:mutex是指向将要被初始化互斥量的指针;attr是指向mutex属性变量的指针,可以传递NULL表示使用默认属性

返回值:成功返回0,失败返回非0错误码:EAGAIN系统缺乏初始化互斥量的非内存资源,ENOMEM系统缺乏初始化互斥量所需的内存资源,EPERM调用程序没有适当的优先级

销毁互斥量

函数原型:int pthread_mutex_destroy(pthread_mutex_t *mutex);

参数:mutex是指向将要被销毁互斥量的指针

返回值:成功返回0,失败返回非0错误码

加锁

函数原型:int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_trylock(pthread_mutex_t *mutex);

参数:mutex是指向将要被销毁互斥量的指针

返回值:成功返回0,失败返回非0错误码,如果调用的是pthread_mutex_trylock(),则返回错误码为EBUSY

解锁

函数原型:int pthread_mutex_unlock(pthread_mutex_t *mutex);

参数:mutex是指向将要被销毁互斥量的指针

返回值:成功返回0,失败返回非0错误码

条件变量

条件变量的类型为pthread_cond_t

条件变量静态初始化

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

条件变量动态初始化

函数原型:int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

参数列表:cond为指向需要被初始化的条件变量的指针,attr是指向存放条件变量属性的变量的指针,传递NULL表示使用默认属性

返回值:成功返回0,失败返回非0错误码

销毁条件变量

函数原型:int pthread_cond_destroy(pthread_cond_t *cond);

参数列表:cond为指向将被销毁的条件变量的指针

返回值:成功返回0,失败返回非0错误码

等待

条件变量是与条件测试一起用的,通常线程对一个条件进行测试,如果条件不满足,则会调用条件等待函数来等待条件满足

条件等待函数

函数原型:int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

条件不满足则一直等待。

参数列表:cond为指向条件变量的指针,mutex为指向互斥量的指针,线程在调用这个函数时,应该先拥有这个互斥量,当线程要加入条件变量的等待队列时,等待操作会让线程释放这个互斥量

返回值:成功返回0,失败返回错误码

函数原型:int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);

功能:条件不满足,等待到abstime之前,超时则不等待

参数列表:前两个参数同上,abstime是指向返回时间(绝对时间)的指针,如果在abstime之前条件变量通知没有出现,则会超时退出,不再等待

返回值:成功返回0,失败返回错误码:ETIMEOUT超时错误

通知

当某个线程修改了某个参数导致条件变量等待的条件为真时,应该通知一个或多个在等待队列中的线程

条件通知函数

函数原型:int pthread_cond_broadcast(pthread_cond_t *cond);

唤醒一个在条件变量等待队列等待的线程

参数列表:cond为指向条件变量的指针

返回值:成功返回0,失败返回错误码

函数原型:int pthread_cond_signal(pthread_cond_t *cond);

唤醒所有在条件变量等待队列等待的线程

参数列表:cond为指向条件变量的指针

返回值:成功返回0,失败返回错误码

log

log.h

/*
 * call example:
 *      GW_LOG(LOG_DEBUG, "app start");
 *      GW_LOG(LOG_INFO,"A = %d", a);
 *      GW_INFO(LOG_DEBUG, "path=%s", path);
 *      GW_LOG(LOG_WARN,"WARN");
 * gcc multiThread.c log.c -o multiThread -DOPEN_LOG -lpthread
*/

#ifndef __LOG_H__
#define __LOG_H__

#include <stdio.h>
#include <stdarg.h>

#define OPEN_LOG // comment this to close log
#define WRITE_LOG_FILE
#ifdef WRITE_LOG_FILE
#define LOG_FILE_PATH "./lseek_sample.log"
#endif

#define LOG_LEVEL LOG_DEBUG // level ref: enum:G_LOG_LEVEL

#define GW_LOG(level, fmt,...) gw_log(level, __DATE__, __TIME__, __FILE__, \
                                    __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__)

typedef enum
{
    LOG_DEBUG = 0,
    LOG_INFO,
    LOG_WARN,
    LOG_ERROR
}G_LOG_LEVEL;

char* logLevelGet(const int level);
void gw_log(const int level, const char* date, const char* time, const char* file, 
            const char* fun, const int line,const char* fmt,...);

#endif

log.c

#include "log.h"

char* logLevelGet(const int level)
{
    if (level == LOG_DEBUG)
        return "DEBUG";
    else if (level == LOG_INFO)
        return "INFO";
    else if (level == LOG_WARN)
        return "WARN";
    else if (level == LOG_ERROR)
        return "ERROR";
    else
        return "UNKNOW";
}

void gw_log(const int level, const char* date, const char* time, const char* file, 
            const char* fun, const int line,const char* fmt,...)
{
    #ifdef OPEN_LOG // 如果已经打开了日志开关
        va_list arg;
        
        va_start(arg, fmt);
        char buf[vsnprintf(NULL, 0, fmt, arg) + 1];
        va_end(arg);

        va_start(arg, fmt);
        vsnprintf(buf, sizeof(buf), fmt, arg);
        va_end(arg);
        
        if (level >= LOG_LEVEL)
            printf("[%s]\t[%s %s][%s: %s:%d]\t%s\n",logLevelGet(level), date, time,
                                                file, fun, line, buf);

        #ifdef WRITE_LOG_FILE // 需要写日志文件
            FILE* fp;
            fp = fopen(LOG_FILE_PATH, "a");
            if (fp && level>=LOG_LEVEL) // 文件打开成功并且leve大于等于需要的日志级别
            {
                fprintf(fp, "[%s]\t[%s %s][%s: %s:%d]\t%s\n", logLevelGet(level),
                                                                date, time, file,
                                                                fun, line, buf);
                fclose(fp);
            }
        #endif

    #endif
}
posted @ 2022-12-26 03:11  HelliWrold1  阅读(107)  评论(0编辑  收藏  举报