一、shell基本命令
notes:简单命令结构框架而不解释详细命令的使用,具体的shell 命令用法可以自行问度娘
软件包管理
dpkg
apt
shell特殊字符使用
notes:
一行多个命令,用;隔开:ls;pwd;cd ..
一行写不下,用‘\’隔开,用于shell脚本
通配符:* ? []
管道:‘|’,前一个命令的输出作为后一个命令的输入
输入/输出重定向:>file 新建模式
>>file 追加模式
2>file 错误信息输出到文件
命令置换:ls `pwd`
notes:esc下面的~键
shell基本系统维护命令
man 联机帮助
passwd su
echo 标准输出
data clear
df 查看文件系统信息
du 查看文件所占磁盘情况
linux用户管理
adduser
usermod 用户属性管理
deluser addgroup
进程管理
ps -elf ps -aux 显示进程状态 pid 进程号
top 动态监视进程状态
pstree 树形显示进程
kill 结束进程
文件系统类型及相关命令
linux文件系统:磁盘文件系统 网络文件系统 专有/虚拟文件系统
file 查看文件类型
ln 文件链接
硬链接文件:通过inode链接,不能跨越文件系统(备份)
软链接:绝对路径链接(快捷方式)
文件压缩和归档:
tar
gzip
shell 脚本编程
#变量
COUNT=1 #变量赋值不允许有空格
echo $COUNT #$是取变量值
DATA=`date` #如果是命令必须要进行命令置换
echo $DATA
unset DATA
echo $DATA #unset 取消变量,也就是不在使用
#位置变量
echo "0-------------$0"
echo "1-------------$1"
echo "2-------------$2"
echo "3-------------$3"
echo "4-------------$4"
echo "4-------------$5"
echo "5-------------$6"
echo "7-------------$7"
echo "8-------------$8"
echo "9-------------$9"
echo "10-------------${10}"
echo "11-------------${11}"
echo "#-------------$#"
echo "\$*-------------$*"
echo "@-------------$@"
echo "?-------------$?"
echo "\$\$-------------$$"
#环境变量
echo $PATH #搜索路径
echo $HOME #用户主目录
echo $IFS #默认为空格 tab或换行符
echo $PS1,PS2 #默认为提示符($)
echo $TERM #终端类型
#功能性语句
#说明性语句----->#注释
#功能性语句----->read expr test等
#结构性语句----->循环语句,判断语句
read -p "input two bumber" v1 v2
echo "$v1 $v2"
#整数运算
num=`expr $v1 + $v2` #注意加空格
echo $num
num=`expr $v1 \* $v2` #注意转义字符
echo $num
#测试命令 test
#字符测试
test "hell0" = "hell" #0表示对,1表示错,注意空格
echo $?
test -n "hello" #判断字符串是否不为0
echo $?
test -z "hello"
echo $?
#整数测试
test 1 -eq 1
echo $?
test 2 -lt 2
echo $?
test 2 -gt 1
echo $?
#文件测试
test -d test.sh
echo $?
test -f test.sh
echo $?
#条件分支判断语句
F=$1
if test -f $F
then
echo "$F is a file"
else
echo "$F is not a file"
fi
read -p "input a number" val
if test $val -eq 0
then
echo "val = 0"
elif [ $val -gt 0 ] #[]在分支语句中也可以表示test,每次if后都需要添加then
then
echo "val >0"
else
echo "val < 0"
fi
#多路分支语句
read -p " input a grade" val
if [ $val -lt 0 ] || [ $val -gt 100 ]
then
echo "input error"
exit 0
fi
grade=`expr $val / 10 `
echo "grade = $grade"
case $grade in
8|9|10)#情况1
echo "A"
;;
6|7)#情况2
echo "B"
;;
*)#*相当于其他情况,default
echo "C"
;;
esac
1
2
3 for val in 1 2 3 4 5
4 #for ((val=1;val<5;val=val + 1))
5 do
6 echo "val=$val"
7 done
8
9
10 for file in `ls`
11 do
12 echo "-----$file"
13 done
14
15 num=0
16 #while [ $num -lt 10 ]
17 while(($num<10))
18 do
19 if [ $num -gt 3 ]
20 then
21 #break
22 continue
23 fi
24
25 echo "num=$num"
26 num=`expr $num + 1`
27 done
~
##函数
fun()
{
local A=$1 #local表示局部作用域,只能在函数内部使用,
#否则就是全局作用域,在函数外也可以使用
B=$2
C=$3
echo "hello world"
echo "A=$A"
echo "B=$B"
echo "C=$C"
}
fun 1 2 3
echo "A=$A"
echo "B=$B"
echo "C=$C"
gcc编译器,gdb调试工具
编译流程图
#gcc基本命令参数
gcc -g 程序可调式
gcc -E test.c -o test.i #预编译处理文件
gcc -S test.i -o test.s #汇编文件
gcc -c test.s -o test.o #目标文件
gcc test.o -o test #可执行文件
-l 链接库
-L
常用:
gcc test.c -o test -l pthread
#gdb 调试工具的使用
#notes:使用gdb之前,必须要用gcc编译器加上-g选项
gdb test 调试test程序
(gdb)l ----->显示10行代码
(gdb)b 12 ----->12行打上断电
(gdb)info b ----->查看断点
(gdb)r ----->运行代码
(gdb)n ----->单步调试
(gdb)p a ----->查看变量a的值
(gdb)s ----->进入到函数中
(gdb)c ----->恢复运行(一直运行,遇到断点或者程序运行结束)
(gdb)q ----->退出
Makefile文件编写
#notes:vsp 命令 set mouse=a crtl+w 切屏 ls -R 查看文件包含关系
#makefile格式
target:dependly_file
<Tab>command
#example
sunq:kang.o yul.o
gcc kang.o yul.o -o sunq
kang.o:kang.c kang.h
gcc -Wall -O -g -c kang.c -o kang.o
yul.o:yul.c
gcc -Wall -O -g -c yul.c -o yul.o
#example
test:f1.o f2.o main.o
gcc f1.o f2.o main.o -o test
f1.o:f1.c
gcc -c f1.c -o f1.o
f2.o:f2.c
gcc -c f2.c -o f2.o
main.o:
gcc -c main.c -o main.o
.PHONY:clean #存在同名文件,则需要添加虚拟文件路径
clean:#make clean 也就是执行下面语句,清除.o中间文件
rm -rf *.o
创建变量:
VAR=var #递归方式创建,不利于拓展
VAR: =var#简单方式创建,可以随意修改,当前赋值是啥就是啥
VAR?=bar#如果var定义过,则什么都不执行,如果没有定义,则将bar的值赋值给VAR
OBJS+=test.o#加一个变量值,现在OBJS就是kang.o yul.o test.o
自动变量:
$@....目标文件
$^....所有依赖文件
$<...第一个依赖文件
参考博客
https://blog.csdn.net/dlf1769/article/details/78997967
#makefile 变量使用
OBJS=kang.o yul.o
CC=gcc
CFLAGs=-Wall -O -g
sunq:$(OBJS)
$(CC) $(CFLAGS) $(OBJS) -o sunq
kang.o:kang.c kang.h
$(CC) $(CFLAGS) -c kang.c -o kang.o
yul.o:yul.c
$(CC) $(CFLAGS) -c yul.c -o yul.o
#sample
test:f1.o f2.o main.o
gcc $^ main.o -o $@
f1.o:f1.c
gcc -c $< -o f1.o
f2.o:f2.c
gcc -c f2.c -o f2.o
main.o:
gcc -c main.c -o main.o
.PHONY:clean #存在同名文件,则需要添加虚拟文件路径
clean:#make clean 也就是执行下面语句,清除.o中间文件
rm -rf *.o
Make的直接使用
make -c
make -f
#隐含规则
gcc命令中,生成.o文件需要依赖.c文件,如果.c文件都存在,那么生成.o的语句可以不用写
#sample2
CC=gcc
CFLAGS=-Wall -O -g
OBJS=f1.o f2.o main.o
f1:f1.o f2.o main.o#生成的目标文件名应是后面的.o文件名的其中之一 也就是执行gcc test.c -o test 系统会自动帮我们链接.c文件
.PHONY:clean
clean:
rm -rf *.o
#sample1
CC=gcc
CFLAGS=-Wall -O -g
OBJS=f1.o f2.o main.o
test:f1.o f2.o main.o
$(CC) $(CFLAGS) $(OBJS) -o test
f1.o:f1.c
f2.o:f2.c
main.o:main.c
.PHONY:clean
clean:
rm -rf *.o
虚文件路径VPATH,多个源文件处于不同文件夹,需要使用虚文件包含
当前目录结构
f1 include main Makefile src1 src2
./include:
head.h
./main:
main.c
./src1:
f1.c
./src2:
f2.c
#sample
CC=gcc
CFLAGS=-Wall -I include #包含文件搜索目录
OBJS=src1/f1.o src2/f2.o main/main.o
VPATH=src1:src2:main
f1:f1.o f2.o main.o
$(CC) $(CFLAGS) $^ -o $@
.PHONY:clean
clean:
find ./ -name "*.o" -exec rm {} \;
make嵌套,嵌套模版,需要记忆
二、文件IO
notes:此章节在文件I/O和标准I/o章节不在详细介绍,只是一些具体函数的使用,如果想要具体查看该小节的详细内容可以参考我的文件i/o博客。链接:
章节概括
@标准I/O主要是介绍标准c库函数对文件的使用
标准I/O接口函数:
文件打开与关闭:fopen/fclose
文件读写(单个字符读写):fgetc/fputc getc/putc getchar/putchar
文件读写(行输入/行输出):gets/puts fgets/fputs
二进制文件读写:fread/fwrite
文件流的刷新和定位(每一个文件都存在文件指针,文件指针随着读取而移动,当文件读完,
文件指针就指向文件末尾,此时想要在文件开头写入字段,就需要刷新文件流指针和定位):
文件流刷新:将缓冲区的数据刷新到文件中
文件定位:long fseek(FILE *stream, long offset, int whence);
格式化输入/输出:scanf/printf sprintf/scanf
@文件I/O主要是介绍系统调用(Linux系统API)对文件的使用:
文件打开与关闭:open/close
读写:read/write
定位:lseek
目录操作和库的使用
打开/关闭目录
打开目录:DIR *opendir(const char *name);
读取目录:struct dirent *readdir(DIR *dirp);
关闭目录:int closedir(DIR *dirp);
#include<dirent.h>
#include<stdio.h>
int main(int argc,char*argv[]){
DIR* fd;
struct dirent* fp;
fd=opendir("/home");
if(fd==NULL){
perror("opendir:");
return -1;
}
while((fp=readdir(fd))!=NULL){
printf("%s\n",fp->d_name);
}
closedir(fd);
return 0;
}
修改文件访问权限
int chmod(const char *path, mode_t mode);
int stat(const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* Inode number */
mode_t st_mode; /* File type and mode */
nlink_t st_nlink; /* Number of hard links */
uid_t st_uid; /* User ID of owner */
gid_t st_gid; /* Group ID of owner */
dev_t st_rdev; /* Device ID (if special file) */
off_t st_size; /* Total size, in bytes */
blksize_t st_blksize; /* Block size for filesystem I/O */
blkcnt_t st_blocks; /* Number of 512B blocks allocated */
/* Since Linux 2.6, the kernel supports nanosecond
precision for the following timestamp fields.
For the details before Linux 2.6, see NOTES. */
struct timespec st_atim; /* Time of last access */
struct timespec st_mtim; /* Time of last modification */
struct timespec st_ctim; /* Time of last status change */
#define st_atime st_atim.tv_sec /* Backward compatibility */
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};
动态库和静态库的使用
静态库:编译时将静态库的代码复制到可执行的文件中,运行时不需要静态库,占用空间大,升级需要重新编译
动态库(共享库):编译时只记录用到库中的哪个符号,不复制共享库中代码,程序运行时需要重新加载库
静态库制作:
1.编写库函数文件 hello.c
2.编译生成.o文件(gcc -c hello.c -o hello.o)
静态库文件命名规则:libxxxx.a
3.ar -rsv libhello.a hello.o
#链接静态库 gcc -o 目标文件 源文件.c -L路径 -l静态库
4.gcc -o test test.c -L. -lhello
动态库制作:
1.编写库函数文件 hello.c
2.编译生成不随位置改变的.o文件 gcc -c -fPIC hello.c
动态库命名规则:libxxx.so
3.gcc -shared -o libhello.so hello.c
#链接动态库
4.gcc -o test test.c -L. -lhello
报错:
./test: error while loading shared libraries: libhello.so: cannot open shared object file: No such file or directory
进行加载动态库:
1.直接将动态库文件放到/usr/lib文件夹中
2.export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./(动态库路径),但是可执行文件换位置就又会报错
将其放入到~/.bashrc文件中
source ~/.bashrc生效
3.ldd test 查看可执行文件所使用的库文件
root@iZbp1928604gkfxejwopqbZ:/home/file_io# ldd test
linux-vdso.so.1 (0x00007fff5651f000)
libhello.so (0x00007ff1976a8000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff1974ad000)
/lib64/ld-linux-x86-64.so.2 (0x00007ff1976b4000)
附图一张(仅供参考)
标准I/O
输入/输出数据流
流的类型:二进制流和文本流
标准I/O流:
stdin:标准输入流 0
stdout:标准输出流 1
stderr:标准错误流 2
系统调用和库函数
系统调用:操作系统提供的接口(API)
C库函数:现存的操作系统多种多样,每一个操作系统提供的系统调用都不一样,因此,为了提高代码的移植性,使用统一的C库函数调用底层的系统调用。
缓冲
全缓冲:系统分配的缓冲区满或无空间,输出
行缓冲:遇到换行符‘\n’,就可以输出
无缓冲:数据直接写入文件当中
文件的打开与关闭
文件打开:
FILE *fopen(const char *pathname, const char *mode);
成功返回FILE*指针;失败返回NULL
mode:
r 只读方式打开!不存在返回NULL
r+ 读写方式打开!不存在返回NULL
w 只写方式打开!不存在则创建,存在则清空内容
w+ 读写方式打开!不存在则创建,存在则清空内容
a 只写方式打开!不存在则创建,存在则将数据追加
a+ 读写方式打开!其他同“a”模式一样
文件关闭:
int fclose(FILE *stream);
success return EOF(0)
failed return errno
标准错误输出:
void perror(const char *s);
char *strerror(int errnum);
三、进程 线程间通信
进程
进程基本概念
#进程概念
进程与程序做区分:程序是存储硬盘上的文件,是静态的;
而进程则是运行一个程序所需要的资源总称,是动态的;
代码段:存放用户代码
数据段:存放初始化的全局变量和静态变量
BSS段:未初始化的全局变量
堆:用户使用malloc分配的动态内存空间
栈:系统分配的内存空间,局部变量,返回值,入参
进程控制块:进程属性(PID),进程状态,优先级,文件描述符表
进程类型:
交互进程:终端交互 前后台都可以运行
批处理进程:与终端无关
守护进程:与终端无关,后台运行
进程状态:
运行态 等待态 停止态 僵尸态
Linux下进程相关命令
ps -elf |grep test 查看进程快照
top []查看进程动态信息
ls /proc
nice -n [] []指定哪个进程以什么优先级运行
nice -n 10 /bin/bash
renice [] [pid] 改变正在运行进程的优先级
renice 5 15637
查看后台进程
jobs
fg [] 恢复到前台运行
bg [] 挂起进程到后台运行
父子进程
创建子进程函数
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
返回值:子进程pid值为0,父进程返回子进程pid,出错<0;
RETURN VALUE
On success, the PID of the child process is returned in the parent, and 0 is returned in the child.
On failure, -1 is returned in the parent, no child process is created, and errno is set appropriately.
父子进程关系:
//创建子进程
#include<stdio.h>
#include<unistd.h>
int main(int argc,char*argv[]){
pid_t pid;
pid=fork();
if(pid>0){
printf("this is father!\n");
}else if(pid==0){
printf("this is child\n");
}else{
perror("fork");
}
printf("public part!\n");
}
结果:注意,子进程和父进程执行的顺序是没有规律的,全靠系统调度
父进程先结束:
子进程变成孤儿进程,由init进程管理,且变为后台进程
子进程先结束:
父进程没有及时回收就会变成僵尸进程
#include<stdio.h>
#include<unistd.h>
int main(int argc,char*argv[]){
pid_t pid;
pid=fork();
if(pid>0){
while(1){
printf("this is father!\n");
sleep(1);
}
}else if(pid==0){
while(1){
printf("this is child\n");
sleep(1);
}
}else{
perror("fork");
}
//printf("public part!\n");
}
验证:
杀死父进程,子进程由init进程管理,且成为后台进程,使用ctrl+c 杀不死,必须使用kill命令
杀死子进程,子进程变成僵尸进程
一个父进程创建多个子进程
#include<stdio.h>
#include<unistd.h>
int main(int argc,char*argv[]){
pid_t pid;
for(int i=0;i<5;i++){
pid=fork();
if(pid>0){
printf("father!\n");
sleep(1);
}else if(pid==0){
printf("child!\n");
//sleep(1);
break;
//如果不在子进程加break,那么子进程每次都会从循环开始,创建子进程,
//因此加上break就可以达到上述题目效果
}else{
perror("fork:");
}
}
while(1){
sleep(1);
}
}
进程结束
#include <stdlib.h>
void exit(int status);
#include <unistd.h>
void _exit(int status);
两个函数区别在于:exit结束进程会刷新流到缓冲,而_exit()不会
status表示以什么状态退出
具体列子可以查看下节
进程回收
#include <sys/wait.h>
pid_t wait(int *stat_loc);
等待任意一个进程退出就回收进程,返回回收进程的pid,并将其状态存放于stat_loc中
pid_t waitpid(pid_t pid, int *stat_loc, int options);
根据输入的pid号回收指定进程(也可以是任意一个进程)
WIFEXITED(stat_val)
Evaluates to a non-zero value if status was returned for a child process that terminated normally.
WEXITSTATUS(stat_val)
If the value of WIFEXITED(stat_val) is non-zero, this macro evaluates to the low-order 8 bits of the status argument that the child process passed to _exit() or exit(), or the value the child process returned from main().
WIFSIGNALED(stat_val)
Evaluates to a non-zero value if status was returned for a child process that terminated due to the receipt of a signal that was not caught (see <signal.h>).
WTERMSIG(stat_val)
If the value of WIFSIGNALED(stat_val) is non-zero, this macro evaluates to the number of the signal that caused the termination of the child process.
WIFSTOPPED(stat_val)
Evaluates to a non-zero value if status was returned for a child process that is currently stopped.
WSTOPSIG(stat_val)
If the value of WIFSTOPPED(stat_val) is non-zero, this macro evaluates to the number of the signal that caused the child process to stop.
WIFCONTINUED(stat_val)
Evaluates to a non-zero value if status was returned for a child process that has continued from a job control stop.
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main(int argc,char*argv[]){
pid_t pid;
pid=fork();
int status;
int rpid;
if(pid>0){
printf("this is father process!\n");
//rpid=wait(&status);status中包含许多参数,因此还需要宏进行进一步解析
//printf("pid=%d status=%d\n",rpid,WEXITSTATUS(status));
rpid=waitpid(-1,&status,0);//-1表示任意进程,
printf("pid=%d status=%d\n",rpid,WEXITSTATUS(status));
}else if(pid==0){
printf("this is child process!\n");
sleep(1);
exit(2);
}else{
perror("fork:");
}
while(1){
sleep(1);
}
}
结果:
未产生僵尸进程,只有父进程
exec和守护进程
exec函数簇
进程调用exec函数簇,进程当前的程序会被指定程序替换
父子进程执行不同程序:
1.父进程创建子进程
2.子进程调用exec函数,父进程不受影响
相关函数
int execl(const char *pathname, const char *arg, ...
/* (char *) NULL */);//最后一个参数必须为NULL
int execlp(const char *file, const char *arg, ...
/* (char *) NULL */);//最后一个参数必须为NULL
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int system(const char *command);
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(int argc,char**argv){
if(execl("/bin/ls","ls","-a","-l","./",NULL)<0){
perror("execl:");
}
return 0;
}
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(int argc,char**argv){
if(execlp("ls","ls","-a","-l","./",NULL)<0){
perror("execl:");
}
return 0;
}
execl和execlp函数唯一不同点在于execl函数输入的程序的绝对路径,而后者是相对路径
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(int argc,char**argv){
char *arg[]={"ls","-a","-l","./",NULL};
if(execv("/bin/ls",arg)<0){
perror("exec:");
}
return 0;
}
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(int argc,char**argv){
char *arg[]={"ls","-a","-l","./",NULL};
if(execvp("ls",arg)<0){
perror("exec:");
}
return 0;
}
execv和execvp函数唯一不同点在于execv函数输入的程序的绝对路径,而后者是相对路径,但是都需要保证最后一个参数为NULL
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(int argc,char**argv){
pid_t pid;
printf("before fork!\n");
pid=fork();
if(pid==0){//子进程用execl函数,父进程与子进程执行不同程序
if(execl("/home/thread/wait_t","0 param","1 param","2 param",NULL)<0){
//0参数一般不使用,但是需要写,第1个输入参数,第2个输入参数,传递给程序wait_t
perror("execl:");
}
}
printf("after execl!\n");
return 0;
}
守护进程:后台运行,独立于终端
会话/终端。进程组概念
守护进程创建过程:
1.父进程创建子进程,父进程退出
2.重新设置会话
setsid(设置为会话组组长) getpid(获取进程号)getsid(获取会话id) getpgid(获取会话组id)
3.更改文件目录(可选,防止程序文件被删除)
chdir("/")
4.更改文件权限掩码(可选)
umask
5.关闭文件标准描述符
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include <sys/stat.h>
int main(int argc,char**argv){
pid_t pid;
pid=fork();
if(pid<0){
perror("fork:");
return 0;
}else if(pid>0){
exit(0);
}
printf("i am deamon!\n");
//重新设置会话
printf("before setsid:sid=%d,pid=%d,pgid=%d\n",getsid(getpid()),getpid(),getpgid(getpid()));
if(setsid()<0){
perror("setsid:");
exit(0);
}
printf("after setsid:sid=%d,pid=%d,pgid=%d\n",getsid(getpid()),getpid(),getpgid(getpid()));
//更改文件目录
chdir("/");
//更改文件掩码
if(umask(0)<0){
perror("umask:");
exit(0);
}
//关闭文件描述符
close(0);close(1);close(2);
sleep(100);
return 0;
}
gdb调试多进程
进入gdb调试
gcc -o -g fork_t fork_t.c
gdb fork_t
start 从main函数开始运行
set follow-fork-mode child 选择跟踪子进程,父进程会自动运行
set detach-on-fork off 分离进程关闭,这样子进程和父进程都不会自动运行
info inferiors 查看所有进程
inferior 1 切换进程调式
补充:killall -9 fork_t 杀死多个同名进程
线程
线程创建及参数传递
线程概念:同一进程中线程共用同一片内存空间
线程公有资源:
静态变量,文件描述符,pid,pgid
私有资源:
.......(后期补充)
函数介绍:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
线程id,线程属性结构体,线程回调函数,线程输入参数
返回值:错误返回EOF,成功>0
pthread_t pthread_self(void); 求线程id
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
int testThread(void*arg){
printf("this is thread tid=%lu,pid=%d,input parmeter=%d\n",pthread_self(),getpid(),*(int *)arg);
//输入参数解释:传入的参数为void*的指针,表示任何数据类型指针,因此现将它强制转换为int *型指针,然后*
//printf("this is thread tid=%lu,pid=%d,input parmeter=%d\n",pthread_self(),getpid(),(int)arg);
pthread_exit(0);
}
int main(int argc,char**argv){
pthread_t tid;
int ret;
int arg=5;
ret=pthread_create(&tid,NULL,(void*)testThread,(void*)&arg);
//ret=pthread_create(&tid,NULL,(void*)testThread,(void*)arg);
//第二种方式就是值传递,int型类型没有超出指针的范围,那么就把值5当做地址传递出去,这样做会有warming
if(ret<0){
perror("pthread_create:");
exit(0);
}
printf("this is main thread,tid=%lu!\n",tid);
sleep(1);//添加休眠函数,避免主线程结束了,子线程函数灭有开始运行就结束
return 0;
}
编译错误原因分析:
pthread_create_t.c:(.text+0x84): undefined reference to `pthread_create'
collect2: error: ld returned 1 exit status
-------------未连接动态库函数,pthread_create是空实现
解决:gcc -o pthread_create_t pthread_create_t.c -lpthread
//创建多个线程传递参数使用第二种值传递方式
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
int testThread(void*arg){
printf("this is thread tid=%lu,pid=%d,input parmeter=%d\n",pthread_self(),getpid(),(int)arg);
pthread_exit(0);
}
int main(int argc,char**argv){
pthread_t tid;
int ret;
int arg=5;
int i;
for(i=0;i<arg;i++){
//ret=pthread_create(&tid,NULL,(void*)testThread,(void*)&arg);
//第一种地址传递,需要每次取地址,但是主线程运行速度较快,i直接来到5,
//所以每次取得地址为5这个位置,因此需要使用sleep函数来延迟主线程运行,以便子线程得以运行
ret=pthread_create(&tid,NULL,(void*)testThread,(void*)i);
if(ret<0){
perror("pthread_create:");
exit(0);
}
printf("this is main thread,tid=%lu!\n",tid);
// sleep(1);
}
sleep(1);
return 0;
}
线程回收
线程回收意义:
如同进程一样,如果线程执行完毕后,主线程没有进行回收,那么这个线程会变成僵尸线程,占用系统用的内存资源,浪费内存空间,因此需要进行回收
线程回收函数:
int pthread_join(pthread_t thread, void **retval);//阻塞函数
线程分离:(线程执行完自动回收)
第一种:int pthread_detach(pthread_t thread);
第二种:设置线程属性
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>
int* testThread(void* arg){
printf("this is child thread,tid=%lu,input parameter=%d\n",pthread_self(),(int)arg);
pthread_exit("child exit");
}
int main(int argc,char**argv){
pthread_t tid[5];
int i;
void*retval;
for(i=0;i<5;i++){
pthread_create(&tid[i],NULL,(void*)testThread,(void*)i);
}
//回收线程
for(i=0;i<5;i++){
pthread_join(tid[i],&retval);
//pthread_join是一个阻塞的函数,如果线程0没有结束,那么即使后面的线程结束了,
//该函数还会一直阻塞在这里,因此为了解决这个问题,提出了线程分离
printf("%d thread %s\n",i,(char*)retval);
}
while(1){
sleep(1);
}
return 0;
}
查看线程信息命令:ps -eLf | grep pthread_join_t
动态查看进程占用内存信息:top -p pid
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>
int* testThread(void* arg){
pthread_detach(pthread_self());
printf("this is child thread,tid=%lu,input parameter=%d\n",pthread_self(),(int)arg);
pthread_exit("child exit");
}
int main(int argc,char**argv){
pthread_t tid[5];
int i;
void*retval;
for(i=0;i<5;i++){
pthread_create(&tid[i],NULL,(void*)testThread,(void*)i);
}
while(1){
sleep(1);
}
return 0;
}
线程取消
线程取消,也就是杀死线程
int pthread_cancel(pthread_t thread);
此函数的使用必须要有取消点也就是阻塞系统调用
如果我们不知道是有程序是否有取消点,则我们可以自己设置取消点
void pthread_testcancel(void);
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
int* testThread(void*arg){
printf("i am child thread!\n");
while(1){
sleep(1);
// pthread_testcancel();
}
pthread_exit("exit!\n");
}
int main(int argc,char** argv){
pthread_t tid;
void* retval;
pthread_create(&tid,NULL,(void*)testThread,NULL);
sleep(5);
pthread_cancel(tid);
pthread_join(tid,&retval);
while(1){
sleep(1);
}
return 0;
}
如果我们想要线程的一段程序必须执行,之后可以取消,则可以设置取消点状态
int pthread_setcancelstate(int state, int *oldstate);
设置取消的模式,遇到取消点取消还是立即取消
int pthread_setcanceltype(int type, int *oldtype);
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
int* testThread(void*arg){
printf("i am child thread!\n");
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
// while(1)
{
sleep(5);
pthread_testcancel();
}
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
while(1){
sleep(1);
}
pthread_exit("exit!\n");
}
int main(int argc,char** argv){
pthread_t tid;
void* retval;
pthread_create(&tid,NULL,(void*)testThread,NULL);
sleep(1);
pthread_cancel(tid);
pthread_join(tid,&retval);
while(1){
sleep(1);
}
return 0;
}
线程清理
线程中申请了内存,遇到exit,线程退出,内存没有free,会造成内存泄露,因此需要线程进行清理
void pthread_cleanup_push(void (*routine)(void *), void *arg);
void pthread_cleanup_pop(int execute);
这两个函数成对使用
routine函数执行情况:
1.遇到pthread_cancel函数执行
2.遇到pthread_exit函数执行
3.pthread_cleanup_pop非零参数也执行
//此函数只描述了遇到取消点执行routine函数情况,其余情况大家可以自行尝试
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
void cleanup(void* arg){
printf("this is clean fun,arg=%s\n",(char*)arg);
}
int* testThread(void*arg){
printf("i am child thread!\n");
pthread_cleanup_push(cleanup,"abc");
while(1)
{
sleep(5);
}
pthread_exit("exit!\n");
pthread_cleanup_pop(0);
}
int main(int argc,char** argv){
pthread_t tid;
void* retval;
pthread_create(&tid,NULL,(void*)testThread,NULL);
sleep(1);
pthread_cancel(tid);
pthread_join(tid,&retval);
while(1){
sleep(1);
}
return 0;
}
线程互斥
互斥锁
临界资源:只允许一个程序访问的资源
临界区:
互斥锁:
创建互斥锁:
动态方式:
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
静态方式:
pthread_mutex_t mutex=PTHREAD_MUTEX_INITALIZER
销毁互斥锁:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
锁的使用:
上锁:
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
解锁:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
FILE* fp;
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;//静态创建锁
void* func1(void*arg){
pthread_detach(pthread_self());
printf("this is func1 thread!\n");
char str[]="func1 write file!\n";
int i=0;
char c;
while(1){
pthread_mutex_lock(&mutex);//上锁
for(i=0;i<strlen(str);i++){
c=str[i];
fputc(c,fp);
usleep(10);
}
pthread_mutex_unlock(&mutex);//解锁
usleep(1);
}
pthread_exit(0);
}
void* func2(void*arg){
pthread_detach(pthread_self());
printf("this is func1 thread!\n");
char str[]="func2 read file!\n";
int i=0;
char c;
while(1){
pthread_mutex_lock(&mutex);
for(i=0;i<strlen(str);i++){
c=str[i];
fputc(c,fp);
usleep(10);
}
pthread_mutex_unlock(&mutex);
usleep(1);
}
pthread_exit(0);
}
int main(int argc,char** argv){
pthread_t tid1,tid2;
fp=fopen("1.txt","a+");
if(fp==NULL){
perror("fopen:");
return 0;
}
pthread_create(&tid1,NULL,func1,NULL);
pthread_create(&tid2,NULL,func2,NULL);
while(1){
sleep(1);
}
fclose(fp);
return 0;
}
读写锁
读写锁:
特征:
写锁:没有写者,没有读者,才能获得写锁
读锁:没有写者。可以获得读锁
写锁只能有一个,而读锁可以有多个
相关函数:
创建读写锁:
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
获得读锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
获得写锁:
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
解锁:
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
FILE* fp;
pthread_rwlock_t rwlock;
void* func1(void*arg){
pthread_detach(pthread_self());
printf("this is func1 thread!\n");
char str[]="func1 write file!\n";
int i=0;
char c;
while(1){
pthread_rwlock_wrlock(&rwlock);
for(i=0;i<strlen(str);i++){
c=str[i];
fputc(c,fp);
usleep(10);
}
pthread_rwlock_unlock(&rwlock);
usleep(1000);
}
pthread_exit(0);
}
void* func2(void*arg){
pthread_detach(pthread_self());
printf("this is func1 thread!\n");
char str[]="func2 read file!\n";
int i=0;
char c;
while(1){
pthread_rwlock_wrlock(&rwlock);
for(i=0;i<strlen(str);i++){
c=str[i];
fputc(c,fp);
usleep(10);
}
pthread_rwlock_unlock(&rwlock);
usleep(1000);
}
pthread_exit(0);
}
void*read_func(void*arg){
pthread_detach(pthread_self());
char buf[32]={0};
while(1){
rewind(fp);
pthread_rwlock_rdlock(&rwlock);
while(fgets(buf,32,fp)!=NULL){
printf("%d thread read=%s",(int)arg,buf);
usleep(1);
}
pthread_rwlock_unlock(&rwlock);
usleep(1000);
}
pthread_exit(0);
}
int main(int argc,char** argv){
pthread_t tid1,tid2,tid3,tid4;
fp=fopen("1.txt","a+");
if(fp==NULL){
perror("fopen:");
return 0;
}
pthread_rwlock_init(&rwlock,NULL);
pthread_create(&tid3,NULL,read_func,3);
pthread_create(&tid4,NULL,read_func,4);
sleep(1);
pthread_create(&tid1,NULL,func1,NULL);
pthread_create(&tid2,NULL,func2,NULL);
while(1){
sleep(1);
}
fclose(fp);
return 0;
}
死锁
死锁一般发生在两把锁以上的情况,在已经拥有一把锁的情况下想要访问另外一个资源,而这个资源已经被其他第二个线程获得,导致程序卡死
解决:
1.线程有先后
2.同时获得两把锁,同时释放两把锁
条件变量和线程池
条件变量
生产消费者模型:线程间同步
一个线程生产出资源之后才能被其他线程消费
相关函数:创建条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
生产出条件变量:
int pthread_cond_signal(pthread_cond_t *cond);
等待条件变量:
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
这个函数(阻塞的函数,只有一个线程能够接受到这个变量)相当于:
先判断条件变量是否有 若无:mutex_unlock
若有:mutex_lock
notes:条件变量和互斥量需要一起使用
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
typedef struct TAX{
struct TAX* next;
int num;
}Tax;
Tax *head;
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
void*product(void*arg){
pthread_detach(pthread_self());
printf("this is consume thread!\n");
Tax *tx;
int i=1;
while(1){
pthread_mutex_lock(&mutex);
tx=malloc(sizeof(Tax));
tx->num=i++;
tx->next=head;
head=tx;
printf("tax %d is arrived!\n",tx->num);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
sleep(1);
}
pthread_exit(0);
}
void*consume(void*arg){
pthread_detach(pthread_self());
printf("this is consume thread!\n");
Tax* tx;
while(1){
pthread_mutex_lock(&mutex);
tx=malloc(sizeof(Tax));
while(head==NULL){//惊群效应问题解决,防止段错误
pthread_cond_wait(&cond,&mutex);
}
tx=head;
head=tx->next;
printf("passage take %d taxi\n",tx->num);
pthread_mutex_unlock(&mutex);
sleep(1);
}
pthread_exit(0);
}
int main(int argc,char**argv){
pthread_t tid1,tid2;
head=malloc(sizeof(Tax));
head->num=0;
head->next=NULL;
pthread_create(&tid1,NULL,product,NULL);
pthread_create(&tid2,NULL,consume,NULL);
while(1){
sleep(1);
}
return 0;
}
丢失信号量问题
bordcast发送条件变量产生惊群效应(多个线程争抢一个资源,造成段错误)
线程池
线程池:
简单来说就是一个放满线程的池子,也就是创建一个数据结构,里面的成员主要是线程,
使用线程池可以极大的节省线程的创建和销毁的时间
线程池使用:
任务队列+线程池
过程:
1.创建任务队列、线程池数据结构
2.线程池初始化
3.任务添加到线程池
4.工作线程实现
5.销毁线程池
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
//定义线程池结构体:任务对列和线程池结构体
//任务队列是临界资源
typedef struct Task{
void*(*func)(void*arg);//工作函数
void* arg;
struct Task* next;//任务链表
}Task;
#define POOL_NUM 10
typedef struct ThreadPool{
pthread_mutex_t mutex;
pthread_cond_t newTask;
pthread_t tid[POOL_NUM];
Task*taskQueue;
int workThread_num;
}Pool;
Pool *pool;
void*realWork(void* arg){
printf("%d func work finshed\n",(int)arg);
}
void addTaskToPool(void*arg){
while(1){
pthread_mutex_lock(&pool->mutex);
while(pool->workThread_num>=POOL_NUM){
pthread_mutex_unlock(&pool->mutex);
sleep(1);
pthread_mutex_lock(&pool->mutex);
}
pthread_mutex_unlock(&pool->mutex);
//将新任务添加到任务队列中
Task* newTask=malloc(sizeof(Task));
newTask->func=realWork;
newTask->arg=arg;
pthread_mutex_lock(&pool->mutex);
Task* member=pool->taskQueue;
if(member==NULL)member=newTask;
else{
member=member->next;
}
member->next=newTask;
}
pool->workThread_num++;
pthread_cond_signal(&pool->newTask);
pthread_mutex_unlock(&pool->mutex);
}
}
void* workThread(void*arg){
while(1){
//等待任务的到来
pthread_mutex_lock(&pool->mutex);
pthread_cond_wait(&pool->newTask,&pool->mutex);
//取出任务
//Task* newTask=malloc(sizeof(Task));
Task* newTask=pool->taskQueue;
pool->taskQueue=pool->taskQueue->next;
pthread_mutex_unlock(&pool->mutex);
//执行任务
newTask->func(newTask->arg);
pool->workThread_num--;
}
}
void pool_init(){
//申请内存池内存空间
pool=malloc(sizeof(Pool));
if(pool==NULL){
perror("malloc pool:");
return;
}
pthread_mutex_init(&pool->mutex,NULL);
pthread_cond_init(&pool->newTask,NULL);
for(int i=0;i<POOL_NUM;i++){
pthread_create(&pool->tid[i],NULL,workThread,NULL);
}
pool->workThread_num=0;
pool->taskQueue=NULL;
return ;
}
void* poolDestory(void){
//清空任务队列
Task* task;
while(pool->taskQueue!=NULL){
task=pool->taskQueue;
pool->taskQueue=pool->taskQueue->next;
free(task);
}
//销毁条件变量和互斥锁
pthread_mutex_destroy(&pool->mutex);
pthread_cond_destroy(&pool->newTask);
//销毁线程池结构体
free(pool);
return;
}
int main(){
//线程初始化
pool_init();
sleep(1);
//任务添加
int i=0;
for(i=0;i<20;i++){
addTaskToPool(i);
}
sleep(5);
//销毁
poolDestory();
return 0;
}
线程的GDB调试
1.显示线程
info thread
2.切换线程
thread XXX
bt
3.为特定的线程添加断点
break location thread id
4.GDB设置线程锁
set schedur-locking on/off
进程间通讯
进程间通讯的几种方:
1.无名管道(pipe)
2.有名管道(fifo)
3.信号(signal)
4.共享内存(mmap)
5.套接字(socket)
6.信号量(PV操作)
7.消息队列
无名管道
无名管道在内核中开辟一片内存,也就是管道,通过管道来传递数据
无名管道特点:
1.只适用于具有亲缘关系的进程(父子进程,兄弟进程)
2.单工通信
3.每次创建会产生两个文件描述符,读端和写端,但是每个进程只能使用一个
4,线程不能通过管道自己读写数据
int pipe(int pipefd[2]);
pipefd[0] refers to the read end of the pipe.
pipefd[1] refers to the write end of the pipe.
#include <stdio.h>
#include<unistd.h>
#include<string.h>
int main(int argc,char**argv){
int pfd[2];
pid_t pid;
char wbuf[32]={0};
char rbuf[32]={0};
int re;
re=pipe(pfd);
if(re<0){
perror("pipe:");
return 0;
}
pid=fork();
if(pid<0){
perror("fork");
return 0;
}else if(pid==0){
while(1){
strcpy(wbuf,"abcdefg");
write(pfd[1],wbuf,strlen(wbuf));
sleep(1);
}
}else{
while(1){
re=read(pfd[0],rbuf,sizeof(rbuf));
printf("read data=%s\n",rbuf);
sleep(1);
}
}
return 0;
}
利用无名管道实现:两个进程写,一个进程读
#include <stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
int main(int argc,char**argv){
int pfd[2];
pid_t pid;
char wbuf[32]={0};
char rbuf[64]={0};
int re;
int i=0;
re=pipe(pfd);
if(re<0){
perror("pipe:");
return 0;
}
for(i=0;i<2;i++){
pid=fork();
if(pid<0){
perror("fork");
return 0;
}else if(pid==0){
break;
}
}
if(i==0){
while(1){
strcpy(wbuf,"this is 1 process");
write(pfd[1],wbuf,strlen(wbuf));
sleep(1);
}
}
if(i==1){
while(1){
strcpy(wbuf,"this is 2 process");
write(pfd[1],wbuf,strlen(wbuf));
sleep(1);
}
}
if(i==2){
while(1){
re=read(pfd[0],rbuf,sizeof(rbuf));
if(re>0){
printf("read data=%s\n",rbuf);
}
memset(rbuf,0,sizeof(rbuf));
}
}
return 0;
}
无名管道一些注意事项:
1.读端
读端有数据,返回值返回为读取的字节数
读端无数据:
管道的写端被全部关闭,则read返回为0
管道写端未关闭,则阻塞等待数据
2.写端
写端完全关闭,程序异常停止
写端不完全关闭
写端数据满,write阻塞
写端数据未满,则write写入
有名管道
有名管道:
适用于非亲缘关系的进程,通过操作路径名(管道文件,管道文件存放于内存当中),使用文件I/O的方式实现进程间通信,不支持leek,也是单工通信
相关函数:
int mkfifo(const char *pathname, mode_t mode);
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include <fcntl.h> /* Definition of AT_* constants */
#include <sys/stat.h>
#include <fcntl.h>
#include<string.h>
//写进程
int main(int argc,char**argv){
int ret;
int fd;
char buf[32]={0};
ret=mkfifo("/myfifo",0666);
if(ret<0){
perror("mkfifo:");
}
fd=open("/myfifo",O_WRONLY);
if(fd<0){
perror("open:");
return 0;
}
while(1){
fgets(buf,sizeof(buf),stdin);
write(fd,buf,strlen(buf));
}
return 0;
}
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include <fcntl.h> /* Definition of AT_* constants */
#include <sys/stat.h>
#include <fcntl.h>
#include<string.h>
//读进程
int main(int argc,char**argv){
int ret;
int fd;
char buf[32]={0};
/*
ret=mkfifo("myfifo",0666);
if(ret<0){
perror("mkfifo:");
}
*/
fd=open("/myfifo",O_RDONLY);
if(fd<0){
perror("open:");
return 0;
}
while(1){
//fgets(buf,sizeof(buf),stdin);
ret=read(fd,buf,strlen(buf));
if(ret>0){
printf("read data=%s\n",buf);
}
}
return 0;
}
共享内存
共享内存就是通过mmap函数将将文件映射到系统内存,通过访问内存实现进程间通信!
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
返回值:
On success, mmap() returns a pointer to the mapped area
On error, the value MAP_FAILED (that is, (void *) -1) is returned
参数描述:
If addr is NULL, then the kernel chooses the (page-aligned) address at which to create the mapping。一般为NULL,让系统自动选择内存地址
length:申请的文件大小
port:文件映射的权限
PROT_EXEC:可执行
PROT_READ:可读
PROT_WRITE:可写
flags:文件对于其他线程的权限
MAP_SHARED:用于多进程通信,共享
MAP_PRIVATE:单个进程,私有
MAP_ANONYMOUS:匿名映射,适用于亲缘关系进程
fd:文件描述符。若是匿名映射,则为-1
offset:文件内容偏移量
注意事项:
1.文件映射权限<=文件打开权限,否则会报:权限拒绝错误
2.文件大小必须>0,否则会报总线错误
3.文件偏移量参数必须为0或者4K的整数倍
4.文件申请的length参数必须>0
5.文件映射成功之后,可以关闭文件描述符
6.文件映射内存的申请是以页(4K)为单位的
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define RD 0
int main(int argc,char** argv){
int fd;
void* maddr;
fd=open("test",O_RDWR);
if(fd<0){
perror("open:");
return 0;
}
maddr=mmap(NULL,128,PROT_WRITE|PROT_READ,MAP_SHARED,fd,0);
if(maddr==MAP_FAILED){
perror("mmap:");
return 0;
}
close(fd);//文件映射成功之后可以关闭文件描述符
//写进程使用
int i=0;
for(i=0;i<128;i++){
memcpy((maddr+i),"a",1);
sleep(1);
}
#ifdef RD
//读进程使用
while(1){
printf("read data=%s\n",(char*)maddr);
sleep(1);
}
#endif
munmap(maddr,128);//释放映射文件内存
return 0;
}
信号
信号相关概念
信号机制:信号机制其实是对软件中断的一种模拟,异步通信,产生信号通知进程
信号产生的方式:按键,发送命令(kill),系统调用函数,硬件异常
进程对对信号不同的反应方式:
缺省:按照信号默认的方式
忽略:忽略该信号,不做任何反应
捕捉:修改该信号的行为
常用信号(具体解释可以自行百度):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
发送信号函数:
int kill(pid_t pid, int sig);//指定进程发送
int raise(int sig);//自己给自己发送
int pause(void);阻塞函数
定时器函数:
unsigned int alarm(unsigned int seconds);
useconds_t ualarm(useconds_t usecs, useconds_t interval);
int setitimer(int which, const struct itimerval *new_value,
struct itimerval *old_value);
struct itimerval {
struct timeval it_interval; /* Interval for periodic timer */
struct timeval it_value; /* Time until next expiration */
};
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
捕捉信号
捕捉信号函数:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
//由于uinx系统分类不一致,所以一般建议使用sigaction函数
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
#include<stdio.h>
#include<unistd.h>
#include <signal.h>
#include <sys/time.h>
typedef void (*sighandler_t)(int);
sighandler_t oldAct;
void handle(int sig){
if(sig==SIGINT){
printf("catch SIGINT!\n");
//signal(SIGINT,oldAct);//捕捉一次INT信号后复原默认处理函数
}else if(sig==SIGALRM){
printf("timer!\n");
//alarm(1);使用alarm循环捕捉SIGALRM
}
}
int main(int argc,char** argv){
//oldAct=signal(SIGINT,handle);
struct sigaction act;
act.sa_handler=handle;
act.sa_flags=SA_SIGINFO;
// sigaction(SIGINT,&act,NULL);
sigaction(SIGALRM,&act,NULL);
// alarm(1);
/*时间结构体参数的设置*/
struct itimerval cur_value;
//间隔
cur_value.it_interval.tv_sec=1;
cur_value.it_interval.tv_usec=0;
//延迟
cur_value.it_value.tv_sec=5;
cur_value.it_value.tv_usec=0;
setitimer(ITIMER_REAL,&cur_value,NULL);
while(1){
// sleep(1);
}
return 0;
}
利用SIGCHLD信号,实现主进程回收(wait)子进程阻塞问题
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/wait.h>
void handle(int sig){
printf("sig=%d\n",sig);
wait(NULL);//阻塞函数
}
int main(int argc,char** argv){
pid_t pid;
struct sigaction act;
act.sa_handler=handle;
sigemptyset(&act.sa_mask);
act.sa_flags=SA_SIGINFO;
pid=fork();
if(pid<0){
perror("fork");
return 0;
}else if(pid>0){
sigaction(SIGCHLD,&act,NULL);
while(1){
printf("main process\n");
sleep(1);
}
}else{
sleep(5);
printf("child proces exit!\n");
exit(0);
}
}
信号阻塞
信号阻塞:有些时候我们我们想要接受到信号,但不立即运行该信号的回调函数(此时系统正在处理更加紧急的任务),但是不能忽略该信号。而是过一段时间之后在开始执行,这就是信号的阻塞
信号状态:
信号递达(delivery):接受到信号并执行其回调函数
信号未决(peding):信号产生到递达之间的状态
信号集:
信号的集合,也就是存放信号的一种数据结构
未决信号集:
存放系统未决信号状态的信号集,该数据集无法改变
信号屏蔽字:
可以通过函数改变,决定屏蔽哪个信号(修改bit位(0表示不屏蔽,1表示屏蔽)来确定屏蔽哪个信号)
相关函数介绍:
int sigemptyset(sigset_t *set);//清空信号集,不屏蔽所有信号
int sigfillset(sigset_t *set);//填充信号集,屏蔽所有信号
int sigaddset(sigset_t *set, int signum);//将信号添加到信号屏蔽字
int sigdelset(sigset_t *set, int signum);//将信号从信号屏蔽字中删除
int sigismember(const sigset_t *set, int signum);//判断信号是否存在在信号屏蔽字中
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);//真正的信号屏蔽函数
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void handle(int sig){
printf("get sig=%d\n",sig);
}
int main(int argc,char** argv){
struct sigaction act;
act.sa_handler=handle;
sigemptyset(&act.sa_mask);
act.sa_flags=0;
sigset_t set;
sigemptyset(&set);
sigaddset(&set,SIGINT);
sigprocmask(SIG_BLOCK,&set,NULL);
sigaction(SIGINT,&act,NULL);
sleep(5);//睡眠5秒后不屏蔽
sigprocmask(SIG_UNBLOCK,&set,NULL);
while(1){
sleep(1);
}
}
信号驱动任务执行
适用情况:接受到信号开始执行程序,执行完之后阻塞继续等待信号
int pause(void);等待信号阻塞函数
int sigsuspend(const sigset_t *mask);
将屏蔽的信号用新的信号集代替,并阻塞等待信号的到来
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
void handler(int sig){
printf("get sig =%d\n",sig);
}
void mytask(){
printf("task start!\n");
sleep(5);
printf("task end\n");
}
int main(int argc,char** argv){
struct sigaction act;
act.sa_handler=handler;
sigemptyset(&act.sa_mask);
act.sa_flags=0;
//添加信号屏蔽函数,保证在任务执行期间不被INT信号打断
sigset_t set;
sigemptyset(&set);
sigaddset(&set,SIGINT);
sigset_t mask;
sigemptyset(&mask);
sigaction(SIGINT,&act,NULL);
pause();
while(1){
sigprocmask(SIG_BLOCK,&set,NULL);
mytask();
/*下面两句代码存在的情况是在任务执行期间确实不会被int信号中断,但是任务执行完毕之后,对于在任务执行中接收到的信号不会再次启动任务*/
// sigprocmask(SIG_UNBLOCK,&set,NULL);
// pause();
// 为了保证接受到的信号量都会响应,使用sigsuspend函数
sigsuspend(&mask);
}
}
消息队列及systemV通信机制
该章节使用的ipc通信机制已经落后,一般不再采用,因此不过多详细介绍
消息队列
信号量
信号量表示资源的数量,通过P/V操作来实现
P操作(申请):
if(信号量的值>0){
申请资源任务继续运行
信号量值-1
}else {
申请资源任务阻塞
}
V操作(释放资源):
信号量值+1
if(有任务等待资源){
唤醒等待任务,继续运行
}
信号灯种类:
有名信号量
无名信号量(用于linux线程同步)
systemV信号量
有名信号量相关函数:
创建
sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);
name:信号量名字
oflag:打开方式,常用O_CREAT
mode:文件权限,0666
value:信号量的值。二元信号量值为1
信号量文件位置:/dev/shm
销毁:
int sem_close(sem_t *sem);
int sem_unlink(const char *name);
P操作:
int sem_wait(sem_t *sem);
v操作:
int sem_post(sem_t *sem);
无名信号量:
创建:int sem_init(sem_t *sem, int pshared, unsigned int value);
销毁:int sem_destroy(sem_t *sem);
P操作:int sem_wait(sem_t *sem);
v操作:int sem_post(sem_t *sem);
四、网络编程
网络采取分层结构:
1.每一层实现不同的功能,对数据进行透明传输
2.网络每一层向上层提供服务,同时享受下一层的服务
模型图:
各层典型协议:
1.网络物理接口层:
MAC地址:48位地址,全球唯一,网络身份的标识
ARP/RARP:
ARP:IP地址------->MAC地址
RARP:MAC地址---------->IP地址
PPP协议:拨号协议(GPS/3G/4G)
2.网络层协议
IP (IPv4 · IPv6) (Internet Protocol) 网络之间互连的协议
ICMP (Internet Control Message Protocol )Internet 控制报文协议。它是TCP/IP 协议族的一个子协议,用于在IP 主机、路由器之间传递控制消息。举例:ping命令
3.传输层
TCP (Transmission Control Protocol )传输控制协议提供可靠的面向连接的服务,传输数据前须先建立连接,结束后释放。可靠的全双工信道。可靠、有序、无丢失、不重复。
UDP (User Datagram Protocol )用户数据报协议发送数据前无需建立连接,不使用拥塞控制,不保证可靠交付,最大努力交付。
4.应用层
FTP (File Transfer Protocol )文件传输协议<端口号21>减少或消除不同操作系统下处理文件的不兼容性。
HTTP (Hypertext Transfer Protocol )超文本传输协议 <端口号 80>, 面向事务的应用层协议。
SSH (Secure Shell )安全外壳协议
嵌入式相关:
NTP网络时钟管理
SNMP:简单网络管理(网络设备集中管理)
RTP/RTSP:传输音视屏协议(安防监控)
网络的分装和拆包
网络编程预备知识
1.socket
是一个编程接口
是一种特殊的文件描述符(可以使用read,write等文件io函数),一种网络资源
socket类型
流式套接字(SOCK_STREAM):唯一对应TCP
提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。内设置流量控制,避免数据流淹没慢的接收方。数据被看作是字节流,无长度限制。
数据报套接字(SOCK_DGRAM):唯一对应UDP
提供无连接服务。数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。
原始套接字(SOCK_RAW):对应多种协议,使用ping可以直接穿透传输层
可以对较低层次协议如IP、ICMP直接访问。
2.IP地址
Internet中的主机要与别的机器通信必须具有一个IP地址
IP地址为32位****(IPv4)或者128位(IPv6)
每个数据包都必须携带目的IP地址和源IP地址,路由器依靠此信息为数据包选择路由
特殊IP:
局域网:192.XXX.XXX.XXX 10.XXX.XXX.XXX
广播:XXX.XXX.XXX.255 255.255.255.255(全网广播)
组播:224.XXX.XXX.XXX ~239.XXX.XXX.XXX
3.端口号
ip地址确认哪台机器,端口号确认哪个进程或者线程(数据传输给哪个任务)
保留端口:FTP 21, SSH 22 ,HTTP 80, HTTPS 469
1025-5000端口不建议使用
可以使用:5000~65535
TCP 和UDP端口是相互独立的
4.字节序
1.cpu访问多字节数据时存在字节序的问题(string字符串则不存在这个问题)
大端字节序:数据高位对应cpu内存地址高位
小端字节序:数据低位对应vpu内存地址高位
2.字节序转换相关函数
(1)主机字节序到网络字节序
u_long htonl (u_long hostlong);
u_short htons (u_short short);
(2)网络字节序到主机字节序
u_long ntohl (u_long hostlong);
u_short ntohs (u_short short);
3.IP地址转换
(1)将strptr所指的字符串转换成32位的网络字节序二进制值
int_addr_t inet_addr(const char *strptr);
(2)将IPV4/IPV6的地址转换成binary格式
int inet_pton(int af, const char *src, void *dst);
逆过程:inet_ntop()
参数:
af:AF_INET(IPV4)AF_INET6(IPV6)
src:IPV4点分十进制
dst:转换后的结果
TCP编程
socket函数
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
参数:
1.domain:
AF_INET IPv4 Internet protocols ip(7)
AF_INET6 IPv6 Internet protocols ipv6(7)
AF_UNIX Local communication unix(7)
AF_LOCAL Synonym for AF_UNIX
AF_NETLINK Kernel user interface device netlink(7)
AF_PACKET Low-level packet interface packet(7)
2.type
SOCK_STREAM:流式套接字,唯一对应TCP
SOCK_DGRAM:数据报套接字,唯一对应UDP
SOCK_RAM:原始套接字
3.protocol:一般填0,使用原始套接字需要填充
返回值:
RETURN VALUE
On success, a file descriptor for the new socket is returned. On error, -1 is returned, and errno is set appropriately.
bind函数
SYNOPSIS
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
sockfd:通过socket拿到的文件描述符
addr:结构体变量的地址
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
};
具体要我们填充的数据结构体
struct sockaddr_in {
sa_family_t sin_family;   /* address family: AF_INET */
in_port_t sin_port;    /* port in network byte order */
struct in_addr sin_addr;    /* internet address */
}
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
addrlen:地址长度
listen()函数
SYNOPSIS
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
参数:
backlog:表示三次握手阶段最多允许几个客户端连接,一般设置为5
return:
-1表示失败,0表示成功
accept()函数
**SYNOPSIS**
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
addr:存放保存连接客户端的地址
addlen:存放保存连接客户端的地址长度
返回值:
On success, these system calls return a nonnegative integer that is a file descriptor for the accepted socket. On error, -1 is returned, errno is set appropriately, and addrlen is left unchanged.
connect()函数
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
与bind相类似
client.c
1 #include "net.h"
2
3 int main(){
4 int fd=-1;
5 fd=socket(AF_INET,SOCK_STREAM,0);
6 if(fd<0){
7 perror("socket");
8 exit(1);
9 }
10
11 struct sockaddr_in sin;
12 sin.sin_family=AF_INET;
13 sin.sin_port=htons(SERVE_POET);
14 #if 0
15 sin.sin_addr.s_addr=inet_addr(SERVE_ADDR);
16 #else
17 if((inet_pton(AF_INET,SERVE_ADDR,&sin.sin_addr.s_addr)!=1)){
18 perror("inet_pton");
19 exit(1);
20 }
21 #endif
22
23 if((connect(fd,(struct sockaddr*)&sin,sizeof(sin))<0)){
24 perror("connect");
25 exit(1);
26 }
27 char buf[BUFSIZE];
28 while(1){
29 bzero(buf,strlen(buf));
30 printf(">");
31 if((fgets(buf,32,stdin)==NULL)){
32 perror("fgets");
33 continue;
34 }
35 if((strncasecmp(STR_QUIT,buf,strlen(STR_QUIT)))==0){
35 if((strncasecmp(STR_QUIT,buf,strlen(STR_QUIT)))==0){
36 break;
37 }
38 write(fd,buf,strlen(buf));
39 }
40
41 close(fd);
42
43
44 }
server.c
1 #include "net.h"
2 int main(){
3
4 /*1.创建socket套接字(文件描述符)*/
5 int fd=-1;
6 if((fd=socket(AF_INET,SOCK_STREAM,0))<0){
7 perror("socket");
8 exit(-1);
9 }
10 /*2.绑定bind*/
11 /*2.1填充结构体数据*/
12 struct sockaddr_in sin;
13 bzero(&sin,sizeof(sin));
14 sin.sin_family=AF_INET;
15 sin.sin_port=htons(SERVE_POET);
16 #if 1
17 // sin.sin_addr.s_addr=inet_addr(SERVE_ADDR);
18 /*优化1:能绑定任意服务器*/
19 sin.sin_addr.s_addr=htonl(INADDR_ANY);
20 #else
21
22 if((inet_pton(AF_INET,SERVE_ADDR,(void*)&sin.sin_addr.s_addr)!=1)){
23 perror("inet_pton");
24 exit(1);
25 }
26 #endif
27 /*2.2 bind*/
28 if(( bind(fd,(struct sockaddr*)&sin,sizeof(sin))<0)){
29 perror("bind");
30 exit(1);
31
32 }
33 /*3.利用listen函数将主动套接字变为被动套结字*/
34 if(listen(fd,BACKLOG)<0){
35 perror("listen");
36 exit(1);
37 }
38 /*4.阻塞等待客户端连接*/
39 #if 0
40 int newfd=-1;
41 newfd=accept(fd,NULL,NULL);
42 if(newfd<0){
43 perror("accept");
44 exit(1);
45 }
46 #else
47 //优化2:获取连接客户端信息
48 int newfd=-1;
49 struct sockaddr_in cin;
50 socklen_t addrlen=sizeof(cin);
51 char clientaddr[16];
52 newfd=accept(fd,(struct sockaddr*)&cin,&addrlen);//无法实现多个客户端连接,因此需要多线程来解决
53 if(newfd<0){
54 perror("accept");
55 exit(1);
56 }
57 /*网络字序转换*/
58 if(!(inet_ntop(AF_INET,(void*)&cin.sin_addr.s_addr,clientaddr,sizeof(cin)))){
59 perror("inet_ntop");
60 exit(1);
61 }
62 printf("client addr:%s port:%d\n",clientaddr,ntohs(cin.sin_port));
63
64
65 #endif
66
67 /*5.读写*/
68 char buf[BUFSIZE];
69 int ret=-1;
70 while(1){
71 bzero(buf,sizeof(buf));
72 do{
73 ret=read(newfd,buf,BUFSIZE-1);
74
75 }while(ret<0 && EINTR==errno);
76
77 if(ret<0){
78 perror("read");
79 exit(1);
80 }
81
82 printf("receive %s",buf);
83
84 if(!strncasecmp(STR_QUIT,buf,strlen(STR_QUIT))){
85 printf("Client is exiting\n!");
86 break;
87 }
88 }
89 close(newfd);
90 close(fd);
91 return 0;
92 }
net.h
1 #ifndef _NET_H_
2 #define _NET_H_
3 #include<stdio.h>
4 #include<stdlib.h>
5 #include<unistd.h>
6 #include<string.h>
7 #include<strings.h>
8 #include <sys/types.h> /* See NOTES */
9 #include <sys/socket.h>
10 #include <netinet/in.h>
11 #include <netinet/ip.h> /* superset of previous */
12 #include <arpa/inet.h>
13 #include<errno.h>
14
15
16
17 #define SERVE_POET 5001
18 #define SERVE_ADDR "127.0.0.1"
19 #define BACKLOG 5
20 #define BUFSIZE 128
21 #define STR_QUIT "quit"
22
23
24
25
26
27 #endif
28
TCP并发服务器多线程/多进程
多线程服务器
server.c
1 #include<pthread.h>
2 #include "net.h"
3 void cli_data_handler(void* arg);
4 int main(){
5
6 /*1.创建socket套接字(文件描述符)*/
7 int fd=-1;
8 if((fd=socket(AF_INET,SOCK_STREAM,0))<0){
9 perror("socket");
10 exit(-1);
11 }
12 /*2.绑定bind*/
13 /*2.1填充结构体数据*/
14 struct sockaddr_in sin;
15 bzero(&sin,sizeof(sin));
16 sin.sin_family=AF_INET;
17 sin.sin_port=htons(SERVE_POET);
18 #if 1
19 // sin.sin_addr.s_addr=inet_addr(SERVE_ADDR);
20 /*优化1:能绑定任意服务器*/
21 sin.sin_addr.s_addr=htonl(INADDR_ANY);
22 #else
23
24 if((inet_pton(AF_INET,SERVE_ADDR,(void*)&sin.sin_addr.s_addr)!=1)){
25 perror("inet_pton");
26 exit(1);
27 }
28 #endif
29 /*2.2 bind*/
30 if(( bind(fd,(struct sockaddr*)&sin,sizeof(sin))<0)){
31 perror("bind");
32 exit(1);
33
34 }
35 /*3.利用listen函数将主动套接字变为被动套结字*/
36 if(listen(fd,BACKLOG)<0){
37 perror("listen");
38 exit(1);
39 }
40
41 int newfd=-1;
42 while(1){
43 pthread_t pid;
44 struct sockaddr_in cin;
45 socklen_t addrlen=sizeof(cin);
46 char cli_addr[16];
47 newfd=accept(fd,(struct sockaddr*)&cin,&addrlen);
48 if(newfd<0){
49 perror("accept");
50 exit(1);
51 }
52
53 if(!inet_ntop(AF_INET,(void*)&cin.sin_addr.s_addr,cli_addr,addrlen)){
54 perror("inet_ntop");
55 exit(1);
56 }
57 printf("client addr=%s, port=%d\n",cli_addr,(int)ntohs(cin.sin_port));
58 pthread_create(&pid,NULL,(void*)cli_data_handler,(void*)&newfd);
59
60 }
61 close(fd);
62 return 0;
63 }
64
65 void cli_data_handler(void* arg){
66 int newfd=*(int*)arg;
67 printf("newfd=%d\n",newfd);
68 int ret=-1;
69 char buf[BUFSIZ];
70 while(1){
71 do{
72 bzero(buf,BUFSIZE);
73 ret=read(newfd,buf,BUFSIZE);
74
75 }while(ret<0 && EINTR==errno);
76 if(ret<0){
77 perror("read");
78 exit(1);
79 }
80 printf("%s",buf);
81
82 if(!strncasecmp(STR_QUIT,buf,strlen(STR_QUIT))){
83 printf("client is exiting\n");
84 break;
85 }
86
87 }
88 close(newfd);
89 }
client.c
1 #include "net.h"
2 void usage(char* s){
3 printf("\n%s server_ip,server_port",s);
4 printf("\n server_ip:seriver ip address");
5 printf("\n server_port:server port(>5000)\n\n");
6
7 }
8 int main(int argc,void* argv[]){
9 int fd=-1;
10 if(argc<3){
11 usage(argv[0]);
12 exit(1);
13 }
14 short port;
15 fd=socket(AF_INET,SOCK_STREAM,0);
16 if(fd<0){
17 perror("socket");
18 exit(1);
19 }
20
21 port=atoi(argv[2]);
22 if(port<5000){
23 usage(argv[0]);
24 exit(1);
25 }
26
27 struct sockaddr_in sin;
28 sin.sin_family=AF_INET;
29 sin.sin_port=htons(port);
30 #if 0
31 sin.sin_addr.s_addr=inet_addr(SERVE_ADDR);
32 #else
33 if((inet_pton(AF_INET,argv[1],&sin.sin_addr.s_addr)!=1)){
34 perror("inet_pton");
35 exit(1);
36 }
37 #endif
38
39 if((connect(fd,(struct sockaddr*)&sin,sizeof(sin))<0)){
40 perror("connect");
41 exit(1);
42 }
43 char buf[BUFSIZE];
44 while(1){
45 bzero(buf,strlen(buf));
46 printf(">");
47 if((fgets(buf,32,stdin)==NULL)){
48 perror("fgets");
49 continue;
50 }
51 if((strncasecmp(STR_QUIT,buf,strlen(STR_QUIT)))==0){
52 break;
53 }
54 write(fd,buf,strlen(buf));
55 }
56
57 close(fd);
58
59
60 }
TCP多进程
server.c
1 #include<signal.h>
2 #include"net.h"
3 #include <sys/wait.h>
4 void waitact(int sig){
5 if(sig==SIGCHLD){
6 waitpid(-1,NULL,WNOHANG);
7 }
8
9 }
10 int main(){
11 int fd=-1;
12 fd=socket(AF_INET,SOCK_STREAM,0);
13 if(fd<0){
14 perror("socket");
15 exit(0);
16 }
17 //信号机制。回收进程
18 sigaction(SIGCHLD,(void*)&waitact,NULL);
19
20 struct sockaddr_in sin;
21 sin.sin_port=htons(SERVE_POET);
22 sin.sin_family=AF_INET;
23 #if 0
24 if((inet_pton(AF_INET,SERVE_ADDR,&sin.sin_addr.s_addr))<0){
25 perror("inet_pton");
26 exit(0);
27 }
28 #else
29 sin.sin_addr.s_addr=htonl(INADDR_ANY);
30 #endif
31 if((bind(fd,(struct sockaddr*)&sin,sizeof(sin)))<0){
32 perror("bind");
33 exit(1);
34 }
35
36 if((listen(fd,BACKLOG))<0){
37 perror("lsiten");
38 exit(1);
39 }
40 int newfd=-1;
41 struct sockaddr_in cin;
42 socklen_t addrlen=sizeof(cin);
43 char clientaddr[16];
44 while(1){
45 bzero(clientaddr,sizeof(clientaddr));
46 newfd=accept(fd,(struct sockaddr*)&cin.sin_addr.s_addr,&addrlen);
47 if(newfd<0){
48 perror("accept");
49 exit(1);
50 }
51 pid_t pid;
52 pid=fork();
53 if(pid<0){
54 perror("fork");
55 exit(1);
56 }else if(pid==0){
57 close(fd);
58 if((inet_ntop(AF_INET,(struct sockaddr*)&sin.sin_addr.s_addr,clientaddr,addrlen))<0){
59 perror("inet_ntop");
60 exit(1);
61 }
62 printf("client addr=%s,port=%d\n",clientaddr,ntohs(sin.sin_port));
63 char buf[BUFSIZE];
64 int ret=-1;
65 while(1){
66 do{
67 bzero(buf,BUFSIZE);
68 ret=read(newfd,buf,BUFSIZE);
69 }while(ret<0 && EINTR==errno);
70 if(ret<0){
71 perror("read");
72 exit(1);
73 }
74 printf("receive data=%s",buf);
75 if(!strncasecmp(buf,STR_QUIT,strlen(STR_QUIT))){
76 printf("client is exiting!\n");
77 break;
78 }
79 }
80 close(newfd);
81 }else{//主线程
82 close(newfd);
83 }
84 }
85 close(fd);
86
87
88
89
90 }
UDP编程
network拓展API:
send()/write():
SYNOPSIS
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
参数:flag:
为0.则和write()函数一样
MSG_DONTWAIT:不阻塞
MSG_OOB:(out-of-band data)外带函数,tcp类型
recv()/read()
SYNOPSIS
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
参数: falgs
为0则和read一样
MSG_DONTWAIT:不阻塞
MSG_OOB:(out-of-band data)外带函数,tcp类型
MSG_PEEK:读取数据都是从头开始,而不移动读取位置
server.c
1 #include "net.h"
2
3 int main(){
4 int fd=-1;
5 fd=socket(AF_INET,SOCK_DGRAM,0);
6 if(fd<0){
7 perror("socket");
8 exit(0);
9
10 }
11
12 int b_reuse = 1;
13 setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int));
14
15
16 struct sockaddr_in sin;
17 sin.sin_family=AF_INET;
18 sin.sin_port=htons(SERVE_POET);
19 sin.sin_addr.s_addr=htonl(INADDR_ANY);
20
21 if(bind(fd,(struct sockaddr*)&sin,sizeof(sin))<0){
22 perror("bind");
23 exit(0);
24 }
25
26 struct sockaddr_in cin;
27 char buf[BUFSIZE];
28 socklen_t addrlen=sizeof(cin);
29 char cli_addr[16];
30 printf("UDPserver is starting ......\n");
31 while(1){
32 bzero(buf,BUFSIZE);
33 if(recvfrom(fd,buf,BUFSIZE-1,0,(struct sockaddr *)&cin,&addrlen)<0){
34 perror("recvfrom");
35 continue;
36 }
37
38 if(inet_ntop(AF_INET,(struct sockaddr*)&cin.sin_addr.s_addr,cli_addr,addrlen)<0){
39 perror("inet_ntop");
40 continue;
41 }
42 printf("client(%s:%d):receive data=%s\n",cli_addr,ntohs(cin.sin_port),buf);
43
44 if(!strncasecmp(buf,STR_QUIT,strlen(STR_QUIT))){
45 printf("client(%s:%d) is exit!\n",cli_addr,ntohs(cin.sin_port));
46 }
47
48 }
49 close(fd);
50 return 0;
51
52 }
client .c
1 #include "net.h"
2
3 void usage(char* s){
4 printf("\n%s:server_ip,server_port:\n",s);
5 printf("server_ip:server ipaddress\n");
6 printf("server_port:sever port(>5000)\n");
7
8 }
9 int main(int argc,void* argv[]){
10 int fd=-1;
11 fd=socket(AF_INET,SOCK_DGRAM,0);
12 if(fd<0){
13 perror("socket");
14 exit(1);
15 }
16 if(argc<3){
17 usage(argv[0]);
18 exit(0);
19 }
20 int port;
21 port=atoi(argv[2]);
22 if(port<5000){
23 usage(argv[0]);
24 exit(1);
25 }
26
27 char buf[BUFSIZE];
28 struct sockaddr_in sin;
29 sin.sin_port=htons(port);
30 sin.sin_family=AF_INET;
31 if(inet_pton(AF_INET,argv[1],(void*)&sin.sin_addr.s_addr)<0){
32 perror("inet_pton");
33 exit(1);
34 }
35
36 while(1){
37 bzero(buf,sizeof(buf));
38 printf(">");
39 if(fgets(buf,BUFSIZE,stdin)==NULL){
40 perror("fgets");
41 continue;
42 }
43
44 if(sendto(fd,buf,sizeof(buf),0,(struct sockaddr*)&sin,sizeof(sin))<0){
45 perror("sendto");
46 continue;
47 }
48 if(!strncasecmp(buf,STR_QUIT,strlen(STR_QUIT))){
49 printf("client is exited!\n");
50 break;
51 }
52
53 }
54 close(fd);
55
56
57
58 return 0;
59 }
多路复用(select函数使用,解决阻塞的问题)
步骤:
1.fd_se集合,将关心的文件描述符加入到fd_set集合中
2.select函数阻塞监控
3.1个或者多个文件描述符有数据exit
4.判断哪个文件描述符有数据
5.依次对数据进行处理
相关函数介绍
1、 fd_set
void FD_CLR(int fd, fd_set *set);//将fd从集合中清除
int FD_ISSET(int fd, fd_set *set);//判断fd是否在集合中
void FD_SET(int fd, fd_set *set);//将fd加入到集合中
void FD_ZERO(fd_set *set);//集合初始化
2.select
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
nfds:maxfd+1
readfds:读集合
writerfds:写集合,一般为NULL
expectfds:异常处理集合(带外数据),一般为NULL
timerout:阻塞超时时间
notes:select函数前后,集合性质发生了变化,select是将关心的fd加入集合,select是有数据的fd保留了下来,因此需要用FD_ISSET判断进行相应的操作。
client.c
1 #include"net.h"
2 void usage(char*s){
3 printf("\n%s: server_addr,server_port(>5000)\n",s);
4 }
5 int main(int argc,void *argv[]){
6 int fd=-1;
7 fd=socket(AF_INET,SOCK_STREAM,0);
8 if(fd<0){
9 perror("socket");
10 exit(1);
11 }
12 if(argc<3){
13 usage(argv[0]);
14 exit(1);
15 }
16 int port=atoi(argv[2]);
17 if(port<5000){
18 usage(argv[0]);
19 exit(1);
20 }
21
22 struct sockaddr_in sin;
23 sin.sin_family=AF_INET;
24 sin.sin_port=htons(port);
25 if(inet_pton(AF_INET,argv[1],&sin.sin_addr.s_addr)<0){
26 perror("inet_pton");
27 exit(1);
28 }
29
30 if(connect(fd,(struct sockaddr*)&sin,sizeof(sin))<0){
31 perror("connect");
32 exit(1);
33 }
34
35 fd_set rset;
36 int maxfd=-1;
37 struct timeval timeout;
38 timeout.tv_sec=5;
39 timeout.tv_usec=0;
40 int ret=-1;
41 char buf[BUFSIZE];
42 while(1){
43 bzero(buf,BUFSIZE);
44 FD_ZERO(&rset);
45 FD_SET(0,&rset);
46 FD_SET(fd,&rset);
47 maxfd=fd;
48 select(maxfd+1,&rset,NULL,NULL,&timeout);
49
50 if(FD_ISSET(0,&rset)){//从标准输入当中读取字符
51 do{
52 ret=read(0,buf,BUFSIZE-1);
53 }while(ret<0 && EINTR==errno);
54
55 if(ret<0){
56 perror("read");
57 continue;
58 }
59 if(!ret)continue;//没有字符输入
60
61 write(fd,buf,BUFSIZE);
62 if(!strncasecmp(buf,STR_QUIT,strlen(STR_QUIT))){
63 printf("client is exited!\n");
64 break;
65 }
66 }
67 if(FD_ISSET(fd,&rset)){
68 bzero(buf,BUFSIZE);
69 do{
70 ret=read(fd,buf,BUFSIZE);
71
72 }while(ret<0 && EINTR==errno);
73 if(ret<0){
74 perror("read");
75 continue;
76 }
77 if(!ret)break;//服务器关闭
78 printf("sever data:%s\n",buf);
79 if(!strncasecmp(buf,STR_QUIT,strlen(STR_QUIT))){
80 printf("server is closed\n");
81 break;
82 }
83
84 }
85
86 }
87 close(fd);
88
89
90 }
TCP IP协议
以太网头
IP头
tcp头
tcp握手过程分析
网络编程扩展
网络信息检索
域名解析相关函数
SYNOPSIS
#include <netdb.h>
extern int h_errno;
struct hostent *gethostbyname(const char *name);//域名解析
#include <sys/socket.h> /* for AF_INET */
struct hostent *gethostbyaddr(const void *addr,
socklen_t len, int type);
void herror(const char *s);//报错提示
const char *hstrerror(int err);
/* System V/POSIX extension */
struct hostent *gethostent(void);
struct hostent {
char *h_name; /* official name of host */
char **h_aliases; /* alias list */
int h_addrtype; /* host address type */
int h_length; /* length of address */
char **h_addr_list; /* list of addresses */
}
#define h_addr h_addr_list[0] /* for backward compatibility */
RETURN VALUE
The gethostbyname() and gethostbyaddr() functions return the hostent structure or a null pointer if an error occurs.
On error, the h_errno variable holds an error number. When non-NULL, the return value may point at static data, see the notes below.
1 #include<netdb.h>
2 #include "net.h"
3 void usage(char* s){
4 printf("\n%s server_name,server_port",s);
5 printf("\n server_name:seriver ip address");
6 printf("\n server_port:server port(>5000)\n\n");
7
8 }
9 int main(int argc,void* argv[]){
10 int fd=-1;
11 if(argc<3){
12 usage(argv[0]);
13 exit(1);
14 }
15 short port;
16 fd=socket(AF_INET,SOCK_STREAM,0);
17 if(fd<0){
18 perror("socket");
19 exit(1);
20 }
21
22 port=atoi(argv[2]);
23 if(port<5000){
24 usage(argv[0]);
25 exit(1);
26 }
27 //域名处理
28 struct hostent *hs;
29 if((hs=gethostbyname(argv[1]))==NULL){
30 herror("gethostname");
31 exit(1);
32 }
33 struct sockaddr_in sin;
34 sin.sin_family=AF_INET;
35 sin.sin_port=htons(port);
36 #if 1
37 sin.sin_addr.s_addr=*(uint32_t *)hs->h_addr;//域名转换
38 #else
39 if((inet_pton(AF_INET,argv[1],&sin.sin_addr.s_addr)!=1)){
40 perror("inet_pton");
41 exit(1);
42 }
43 #endif
44
45 if((connect(fd,(struct sockaddr*)&sin,sizeof(sin))<0)){
46 perror("connect");
47 exit(1);
48 }
49 char buf[BUFSIZE];
50 while(1){
51 bzero(buf,strlen(buf));
52 printf(">");
53 if((fgets(buf,32,stdin)==NULL)){
54 perror("fgets");
55 continue;
56 }
57 if((strncasecmp(STR_QUIT,buf,strlen(STR_QUIT)))==0){
58 break;
59 }
60 write(fd,buf,strlen(buf));
61 }
62
63 close(fd);
64
65
66 }
网络属性设置
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
level指定控制套接字的层次.可以取三种值:
1)SOL_SOCKET:通用套接字选项. (应用层)
2)IPPROTO_IP:IP选项. (网络层)
3)IPPROTO_TCP:TCP选项. (传输层)
optname指定控制的方式(选项的名称),我们下面详细解释
举例:
允许地址快速重用
int reuse=1;
setsockopt(fd,SOL_SOCKET,SO_RESUSERADDR,&reuse,sizeof(int))
网络超时优化
方法一:
设置socket的属性 SO_RCVTIMEO
参考代码如下
struct timeval tv;
tv.tv_sec = 5; // 设置5秒时间
tv.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv,
sizeof(tv)); // 设置接收超时
recv() / recvfrom() // 从socket读取数据
方法二:
用select检测socket是否’ready’
参考代码如下
struct fd_set rdfs;
struct timeval tv = {5 , 0}; // 设置5秒时间
FD_ZERO(&rdfs);
FD_SET(sockfd, &rdfs);
if (select(sockfd+1, &rdfs, NULL, NULL, &tv) > 0) // socket就绪
{
recv() / recvfrom() // 从socket读取数据
}
方法三:
设置定时器(timer), 捕捉SIGALRM信号
参考代码如下
void handler(int signo) { return; }
struct sigaction act;
sigaction(SIGALRM, NULL, &act);
act.sa_handler = handler;
act.sa_flags &= ~SA_RESTART;
sigaction(SIGALRM, &act, NULL);
alarm(5);
if (recv(,,,) < 0) ……
广播发送
sender:UDP客户端
receiver:UDP服务端
广播发送:只需要修改客户端即可
加上如下代码:
int so_br=1;
setsockopt(fd,SOL_SOCKET,SO_BROADCAST,&so_br,sizeof(int));
多播
组播接收
1.创建用户数据报套接字
2.加入多播组
3.绑定本机IP地址和端口
4.绑定的端口必须和发送方指定的端口相同
5.等待接收数据
加入多播组
struct ip_mreq mreq;
bzero(&mreq, sizeof(mreq));
mreq.imr_multiaddr.s_addr = inet_addr(“235.10.10.3”);
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
struct ip_mreq
{
struct in_addr imr_multiaddr;
struct in_addr imr_interface;
};
UINX域套接字(本地进程通信)
1.进程间通信方式:
管道,消息队列,共享内存,信号,unix域套接字
易用性:消息队列>unix套接字>管道>共享内存
效率性:共享内存>unix套接字>管道>消息队列
2.异步通信
信号
2.同步和互斥
信号量
unix域套接字
和普通tcp和udp套接字流程是一样的,唯一区别就是套接字类型不同,适用于本地通信
socket(AF_UNIX/AF_LOACL,SOCK_STRAM,0);
bind地址结构的需要改变:
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[108]; /* Pathname */
};
还需要利用**access()函数**判断文件是否存在?
五、数据库开发
六、ARM体系结构
ARM体系结构导学
操作系统:向上提供接口(API),向下管理硬件
Linux系统组成:
1.进程管理:进程创建调度
2.内存管理:内存的申请释放
3.文件系统:访问磁盘文件管理
4.设备管理:硬件设备及驱动管理
5.网络协议:网络协议栈通信
计算机基础:
冯诺依曼计算机体系:
CPU(控制器,运算器),输入设备,输出设备,存储器
三级存储结构:cache(高速缓存),主存储器(内存),辅助存储器(硬盘),CPU只能访问cache和内存,不能直接访问硬盘
地址空间:CPU通过地址总线访问内存的空间,地址空间是有限的,取决于地址总线的数量,如:32位操作系统,他的地址空间一般为2^32=4G,因此32位操作系统内存不能超过4G,
CPU工作原理:
取指:
指令计数器存取想要执行指令的地址,将其发送给内存,内存将其地址中的内容返回给cpu的指令寄存器IR
译码:
译码器将指令解析成具体的运算
执行:
控制器控制相应的运算单元运算,运算结果存入寄存器中
ARM处理器架构
RISC(精简指令集)指令集和CISC(复杂指令集)指令集
ARM寄存器
ARM处理器拥有8种工作模式:
用户模式(USR):正常程序执行模式,不能直接切换到其他模式
系统模式(SYS):运行操作系统的特权任务,与用户模式类似,但具有可以直接切换到其他模式等特权
快中断模式(FIQ):支持高速数据传输及通道处理,FIQ异常响应时进入此模式
中断模式(IRQ):用于通用中断处理,IRQ异常响应时进入此模式
管理模式(SVC):操作系统保护模式,系统复位和软件中断响应时进入此模式(由系统调用执行软中断SWI命令触发)
中止模式(ABT):用于支持虚拟内存和/或存储器保护,在ARM7TDMI没有大用处
未定义模式(UND):支持硬件协处理器的软件仿真,未定义指令异常响应时进入此模式
ARM寄存器分为通用寄存器、专用寄存器、控制寄存器
R15,PC,程序计数器,存放执行指令的地址
R14,LR,链接寄存器
执行跳转指令(如:从main函数执行func函数,跳转到func函数实现),LR自动保存跳转指令下一条指令的地址,
使得执行完func函数后,将LR的值赋值给PC,重新返回main函数执行
产生异常(中断),LR自动保存跳转指令下一条指令的地址,异常处理结束后将LR的值赋值给PC,跳转到异常开始的地方继续执行
R13,栈指针,指向栈底的位置
CPSR,控制寄存器,32位,注意前四位和后7位代表的含义
ARM异常处理
异常概念:异常指的是处理器在正常执行程序的过程中遇到的不正常事件。异常发生时,处理器会暂停当前程序转而去处理异常事件,异常事件处理完成之后再回到之前被打断的地方继续执行程序。
异常源
异常模式
七、系统移植
开发板启动过程
1.开发板上电,pc=0,从IROM开始运行,执行BL0(bootload)程序,软硬件初始化,读取拨码开关的值,选择启动方式(以SD卡启动为例),将SD卡中uboot程序加载到RAM中
2.uboot程序运行,将linux程序,dtb(设备树),rootfs(根文件系统)加载到RAM中
3.运行linux程序,初始化环境,挂载rootfs
交叉编译环境配置
1.网络环境配置 开发板与ubuntu使用有线网线连接
2.tftp服务器配置 Ubuntu与开发板使用tftp协议进行文件传输
3.nfs服务器配置 nfs网络文件系统,能够在不同的计算机之间使用网络进行文件共享,开发板能够访问Ubuntu的文件
uboot
bootload:
操作系统运行之前的一段代码,用于将软硬件环境初始化到一个合适的状态,为操作系统的加载和运行做准备
bootload基本功能:
初始化软硬件环境
引导加载linux内核
给linux内核传参
执行用户命令
notes:bootLoad是引导加载程序的统称,在嵌入式linux经常使用的bootload是uboot
SD卡制作uboot启动盘:
uboot使用:
将开发板用串口线与电脑连接,打开secureCRT软件,即可进行交互操作:
常用命令:
bootcmd 上电启动命令
setenv 设置环境变量
printenv 答应环境变量
saveenv 保存环境变量
go 跳转到内存哪里开始运行
linux安装与加载
从tftp服务器将linux内核加载到内存
1.配置Ubuntu的tftp服务器及ip
2.将liunx内核(uimage),dtb设备数文件(exynos -4412,dtb),rootfs根文件(ramdisk)放到tftp服务器目录,修改权限为777
du -mh 查看文件大小
3.连接开发板,上电,进入uboot交互模式,设置自启动环境变量
setenv bootcmd tftp 0x41000000 uimage\;tftp 0x42000000 exynos4412.dtb\;tftp 0x43000000 ramdisk.img\;bootm 0x41000000 0x43000000 0x42000000
saveenv
解释:地址为什么从0x41000000开始?
查看芯片手册得知,exynos4412他的拓展内存是从0x40000000开始,且大小为1G,0x40000000到0x41000000之间需要存放uboot文件,因此从41000000开始
bootm 专用于启动linux系统命令
EMMC加载linux内核(产品交付常用模式):
1.将内存中的linux内核文件,设备树文件,根文件系统文件写入到外存(EMMC)中
使用tftp命令从tftp服务器中下载程序到内存,使用EMMC write 将文件从内存写入都EMMC中
tftp->内存->EMMC
2.设置启动环境变量
setenv bootcmd mmc read
3.saveenv
tftp加载linux内核,nfs挂载头rootfs(开发常用模式):
EMMC加载uboot(u-boot-fs4412.bin):
从tftp服务器下载uboot加载到内存,使用mmc命令打开引导分区,之后将镜像文件写入EMMC中
交叉编译
编译的原理:将高级语言转换成cpu能够识别的机器语言(二进制)
由于cpu架构的不同(X86,ARM架构),他们底层的指令(二进制命令)是不一样的,因此为了移植的方便
,只需要更改编译器就可以,而不需要修改代码
gcc编译的四个过程:
预处理:将源文件中宏定义和头文件展开,生成.
编译:编译为汇编代码,输出汇编文件
汇编:将汇编文件(.s)输出位目标文件(.o)
链接:将目标文件与附加目标文件(动态库 静态库 标准C库)链接起来,生成可执行文件
交叉工具链:arm-gcc适用于ARM架构的编译器
ELF文件:linux系统下可执行文件的格式