引出系统编程
=============
1.实际开发中:需要多个程序能够"同时"运行(一心多用)
系统编程:解决多任务的并发操作
不同程序之间的通信(收发数据)
多个程序同时去访问一个共享资源(比如说火车票总数),需要协调一下
int main()
{
show_pic();
show_login();
//先后依次执行,前面的不执行完毕,后面的是没有机会
//学习了系统编程的技术,解决多个任务同时执行的问题
}
系统编程主要知识点
===================
1.多进程
进程相关理论概念
进程创建,回收,退出....的相关接口函数
进程间常用的通信方式
2.多线程
线程相关理论概念
线程创建,回收,退出,设置属性....的相关接口函数
线程的同步与互斥
线程池
多进程(创建多个进程,帮助我实现多个任务的并发操作)
==============
1.进程有关的理论概念:
进程:动态的概念,需要程序运行起来才会有进程,不运行程序,是不会有对应的进程产生,一个正在运行的程序就是一个进程
程序:静态的概念,指的是你用编译器编译好的二进制文件,存放在磁盘的
命令:ps -elf 查看当前系统中进程的信息
其中PID(当前进程的id号) CMD(当前进程的名字)
父进程:程序中调用了fork,那么该程序被称作父进程,fork创建出来的新的进程称作子进程
子进程:
孤儿进程(僵尸进程,僵死进程):父进程优先于子进程退出,导致子进程没有人理会它(孤儿),系统中会有一个init的进程专门负责回收孤儿
进程的组成:数据段,代码段,进程控制块(PCB)
进程控制块:指的就是struct task_struct
struct task_struct用来存放跟进程有关的信息
{
pid_t pid;
unsigned int policy; cpu调度策略
linux用宏定义定义了几个宏,每个宏表示不同的调度策略
*files; //指向存放文件信息数组的一个指针
}
/usr/src/linux-headers-3.5.0-23-generic/include/linux/sched.h 1229行
进程组:多个进程的集合,对比用户组
2.进程创建的相关接口函数
(1)进程的创建
pid_t fork(void);
返回值:(重点,难点)
>0 表示子进程的id号,父进程
==0 表示子进程
<0 失败
注意:常规的函数,返回值只能是多种情况中的一种
fork很特殊,一次调用,两次返回(两次返回的目的在于,fork源码内部帮助你为子进程申请了独立的空间帮助子进程运行,返回两个结果区分谁是父进程,谁是子进程)
(2)进程的退出跟回收
void exit(int status); //return -1 return 0
参数:进程退出时候的状态值 强行退出进程
exit(1); //结束整个进程
return 1; //仅仅只是函数返回一个结果给调用者
void _exit(int status);
区别:exit结束进程的时候帮助你刷新缓冲区
_exit结束进程的时候不刷新缓冲区
#include <sys/wait.h>
pid_t wait(int *stat_loc);
返回值:成功 返回回收到子进程的id号
失败 -1
参数:(重点)
存放进程退出时的状态信息(exit退出值仅仅只是退出状态信息的一部分)
需要通过系统提供的宏函数帮助你分析退出原因,退出值等等
pid_t waitpid(pid_t pid, int *stat_loc, int options);
返回值:成功 返回回收到子进程的id号
失败 -1
参数:pid --》
< -1 例如:waitpid(-2156,status,options)
回收进程组id是2156这个组里面的某个子进程
-1 例如:waitpid(-1,status,options)
回收任意一个子进程
0 例如:waitpid(0,status,options)
回收本进程组里面的任意一个子进程
>0(重点) 例如:waitpid(1234,status,options)
指定回收id是1234的那个子进程
options --》WNOHANG 非阻塞等待回收(等的了就等,等不了就直接退出)
0 阻塞等待回收
(3)打印当前进程的id,已经父进程的id号
pid_t getpid(void);
pid_t getppid(void);
gid_t getgid(void); //获取进程组id
(4)进程中调用执行其他的进程
方法一:system() //执行shell命令或者程序
system("ls -l")
方法二:exec函数族
总结:函数名字中带有l(list),表示参数以列表的形式列举
函数名字中带有p,去环境变量中找命令,程序
函数名字中带有e,可以设置环境变量(实际操作发现设置不了)
函数名字中带有v,参数采用指针数组传递(对比l)
int execl(const char *path, const char *arg, ...);
参数:path --》你要执行的那个进程,命令的路径名
arg --》你要执行的程序,命令参数,参数要求以列表的形式列举出来
例如:执行ls -l
execl("/bin/ls","ls","-l",NULL);
int execlp(const char *file, const char *argument, ...);
execlp("cp","cp","1.txt","/home/gec",NULL)
execlp("11","./11",NULL); //11程序必须存放在环境变量/bin中
int execle(const char *path, const char *arg,
..., char * const envp[]);
例如:
char *envp[]={"PATH=/mnt/hgfs/share",NULL};
execle("/bin/ls","ls","-l",NULL,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[]);
进程间的通信
================
1.解决的问题:进程之间虽然各自有自己的独立空间,但是需要数据的交互
2.通信方式:
管道:无名管道,有名管道
SYSTEM-V ipc通信:共享内存,消息队列,信号量
信号:
3.管道
无名管道
(1)相关的接口函数
无名管道的创建
int pipe(int fildes[2]);
返回值:成功 0 失败 -1
参数:fildes[0] 读端的文件描述符
fildes[1] 写端的文件描述符
总结:如果管道里面没有内容,读的时候会阻塞
只能用于用血缘关系的进程之间(父子进程,兄弟进程)
无名管道没有对应的管道文件
有名管道
(1)相关的接口函数
有名管道的创建
int mkfifo(const char *pathname, mode_t mode);
返回值:成功 0 失败 -1
参数:pathname --》你要创建的有名管道的路径名
mode --》权限 0777 0666
总结:不能在共享里面创建有名管道,原因是windows不支持这种格式的文件
可以用于没有血缘关系的进程之间通信
不区分读端写端
有对应的管道文件
4.信号
讨论的核心问题:给进程发送信号之后,进程做何反应
生活中的例子类比:
红绿灯: 红灯 --》法律规定的默认动作,停下来
怕什么,闯红灯
相关的linux命令:
kill -STOP 进程的id号
kill -19 进程的id号
killall -STOP 进程的名字
killall -19 进程的名字
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
(1)相关的接口函数
#include <signal.h>
int kill(pid_t pid, int sig);
返回值:成功 0 失败 -1
参数:pid --》进程的id
sig --》你要发送的信号
(2)捕捉信号并改变信号的响应动作
正常情况下:发送信号给进程,绝大部分信号都会终止进程
实际需求:我想自主改变信号的响应动作
比如:SIGINT --》正常动作,终止进程
修改成,跳个舞
void (*signal(int sig,void(*func)(int)))(int);
返回值:
参数:sig --》你想要捕捉的信号的序号
void(*func)(int) --》函数指针
void tiaowu(int sig)
{
printf("跳舞咯!\n");
}
signal(SIGINT,tiaowu);
(3)阻塞进程等待信号到来
int pause(void);
(4)另外一组信号的发送和捕捉函数
第一组:kill()和signal()
第二组:sigqueue()和sigaction()
int sigqueue(pid_t pid, int sig,const union sigval value); //买一送一,发送信号的同时携带额外数据
返回值:
参数:value --》 union sigval {
int sival_int; //存放你要发送的某个整数
void *sival_ptr; //存放你要发送的某个指针
};
int sigaction(int sig, const struct sigaction * act,struct sigaction *oact);
返回值:
参数:struct sigaction结构体成员如下:
void(*) (int) sa_handler 跟signal的函数指针一模一样
sigset_t sa_mask 信号阻塞掩码
int sa_flags 选择 SA_SIGINFO表示使用sa_sigaction
选择 0表示使用sa_handler
void(*)(int, siginfo_t *,void *) sa_sigaction 为了配合sigqueue携带额外数据
siginfo_t
{
int si_int;
void *si_ptr;
}
(5)信号的阻塞(屏蔽)
信号的阻塞:进程将信号挂起(把信号挡在外面),暂时不去响应,一旦解除阻塞,立马响应
信号的忽略:signal( SIG_IGN) 进程舍弃掉接收的信号,不去响应
设置信号阻塞的函数
int sigprocmask(int how, const sigset_t *restrict set,sigset_t *restrict oset)
sigset_t:作用是专门存放你想要阻塞的那些信号
称作为信号阻塞掩码集
返回值:
参数:how --》SIG_BLOCK //设置信号阻塞,将set中信号添加到原本oset中
SIG_SETMASK //设置信号阻塞,用set中的信号替换掉(覆盖掉)原本oset中
SIG_UNBLOCK ////设置信号解除阻塞
set --》新设置的阻塞掩码集
oset --》保存原本的阻塞掩码集,不想保存,就设置为NULL
操作信号阻塞掩码集
int sigemptyset(sigset_t *set); //清空集合
int sigfillset(sigset_t *set); //一口气将linux中所有的信号全部加入集合
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum); //判断signum在不在集合中
返回值:1表示是成员 0表示不是成员 -1失败
使用思路:
步骤一:想清楚你要阻塞那些信号
步骤二:定义集合变量,并将这些信号添加到集合中
步骤三:调用sigprocmask让阻塞生效
system-v ipc通信:共享内存,消息队列,信号量
==================
命令:查看共享内存
ipcs -m
删除共享内存
ipcrm -m 共享内存的id号
(1)共享内存
第一个函数:申请共享内存
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
返回值:成功返回共享内存的id 失败 -1
参数:key(重点):键值,本质就是个整数,确保申请的内存唯一性,大家都一样
写法有两种:
第一种:程序员自己随便写个正整数
第二种:使用linux提供的ftok()生成键值
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(char *pathname,int proj_id);
返回值:
参数:pathname--》任意合法有效的路径
proj_id--》任意一个整数
ftok("/home/gec",15);
ftok("/home/gec",10);
size:你打算申请多大的内存,1024的整数倍
shmflg: IPC_CREAT
IPC_EXCL
第二个函数:映射共享内存(得到共享内存的首地址)
void *shmat(int shmid, const void *shmaddr, int shmflg); 返回值:你刚才申请的共享内存的首地址
失败 NULL
参数:shmid--》共享内存id
shmaddr --》一般设置为NULL,表示系统自动分配
shmflg --》一般设置为0 表示共享内存可读可写
第三个函数:解除映射
int shmdt(const void *shmaddr);
返回值:
参数:shmaddr --》共享内存的首地址
第四个函数:多功能函数,删除共享内存,获取,设置共享内存的属性信息
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:cmd --》IPC_STAT 获取共享内存的属性信息
IPC_SET 设置共享内存的属性信息
IPC_RMID 删除,删除的时候需要用到最后一个参数
buf --》 struct shmid_ds 存放你获取,设置的属性信息
{
shm_segsz; //存放大小的
}
信号量
====================
ipcs -s 查看信号量
ipcrm -s 信号量的id 删除信号量
相关的接口函数:
第一个函数:创建信号量(申请积分卡)
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
参数:nsems --》你打算申请多少个信号量
第二个函数:设置信号量的初始值
int semctl(int semid, int semnum, int cmd, ...);
参数:semnum(重点) --》信号量的序号,从0开始,第一个信号量序号是0
例如:semget(7884,2,IPC_CREAT|IPC_EXCL|0777);
semctl(id,0,SETVAL,1); 将第一个信号量(序号是0)值设置为1
semctl(id,1,SETVAL,0); 将第二个信号量(序号是1)值设置为0
int num=semctl(id,0,GETVAL); 获取第一个信号量的值返回保存到num
cmd --》GETVAL 获取当前信号量值(积分值)
SETVAL 设置当前信号量值
IPC_RMID 删除
struct semid_ds mysemds;
semctl(semid,0,IPC_STAT,&mysemds); //获取属性
第三个函数:使用信号量(*****重点)
int semop(int semid, struct sembuf *sops, size_t nsops);
参数:struct sembuf *sops
{
short sem_num 信号量的序号,从0开始
short sem_op pv操作 写负数就表示减法(p操作)
写正数就表示加法(v操作)
short sem_flg 设置为SEM_UNDO宏定义,使用完信号量以后,不删除的情况,程序退出,值恢复成原来的值
}
nsops --》struct sembuf 结构体的个数,一般写成1
总结:信号量的特点
信号量的值是绝对不会出现负数
信号量的值如果是0,你还要进行p操作(减法操作),会导致当前进程阻塞(重点)
只有阻塞了其他进程,我这个进程不就有机会了吗(阻塞别人,成全自己)
信号量进行v操作是绝对不会阻塞
进程的生老病死(纯理论)
==============
进程有如下几种状态:
就绪态:进程已经准备好要执行了,但是还没有获得cpu的使用权
执行态:进程获得了cpu的使用权,开始执行了
睡眠态、挂起态:使用sleep()函数
暂停态:进程收到STOP信号
僵尸态:进程退出了,但是没有人回收
死亡态:进程退出了,有人回收wait() waitpid()
多线程
===========
(1)相关的理论概念:
线程是"轻量级"的"进程"
线程依赖于进程存在,它的资源都是进程给的
孤儿线程:线程退出的时候,主线程没有去回收它
(2)相关的接口函数
第一个函数:线程的创建
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
返回值:成功0 失败 错误码
参数:thread --》存放线程的id
attr --》存放线程的属性(后面会专题讨论),目前设置为NULL,表示使用默认属性
void *(*start_routine)(void *) --》你创建线程需要执行的任务,就通过该指针指向的函数去实现
arg --》传递给start_routine的参数
由于类型是void *,表示可以接收任何类型的指针
Compile and link with -pthread. //编译的时候必须加上线程库
gcc thread.c -o thread -pthread
gcc thread.c -o thread -lpthread 都对
第二个函数:线程的退出和回收
void pthread_exit(void *retval); //退出
参数:retval --》存放线程退出时候的信息
int pthread_join(pthread_t thread, void **retval); //回收线程
参数:thread --》线程的id
retval --》二级指针,存放线程退出信息的(pthread_exit里面参数的内容)
总结:遇到二级指针,复杂--》简化(定义一个一级指针辅助你理解)
第三个函数:获取当前线程的id
pthread_t pthread_self(void);
后续课程
===========
网络编程
shell编程 --》学习过linux中常用shell命令
用shell编程的语法规则将前面学习的这些命令写个一个脚本
音视频
串口编程
C++和QT
线程的属性设置
================
(1)线程属性很多,教大家一般方法
在linux中定义了一个结构体存放线程的所有属性,类型叫做pthread_attr_t
步骤:
第一步:定义线程属性变量并初始化
int pthread_attr_init(pthread_attr_t *attr);
第二步:调用你想要设置的某种属性对应的接口函数
第三步:让你设置属性生效
pthread_create(id,attr,); //此时第二个参数一定不能写成NULL
第四步:用完之后,销毁
int pthread_attr_destroy(pthread_attr_t *attr)
举例:设置线程的分离属性
线程分离:线程退出的时候,主线程不需要回收它,自己回收自己(自生自灭)
线程不可分离:线程退出的时候,主线程必须回收它,不回收就变成孤儿线程
线程默认的属性是不可以分离
(2)设置调度策略
第一种:SCHED_FIFO
静态优先级:0---99来划分优先级的级别
其中优先级为0称之为非实时普通线程
1-99称之为实时线程,数字越大,优先级越高
第二种:SCHED_RR
第三种:SCHED_OTHER 默认的调度策略
静态优先级值必须是0,都是非实时普通线程
动态优先级:取值 -20到19,数字越大,优先级越低
线程的取消和设置取消类型
======================
(1)线程的取消(干掉线程)
int pthread_cancel(pthread_t thread);
(2)设置取消类型和状态
int pthread_setcancelstate(int state, int *oldstate); //设置取消状态(是否能被取消)
参数:state --》
PTHREAD_CANCEL_DISABLE 不能取消
PTHREAD_CANCEL_ENABLE 能被取消(线程默认是能被取消)
分为两种情况:延时取消和立即取消
oldstate --》存放线程原本的取消状态
int pthread_setcanceltype(int type, int *oldtype); //设置取消类型
参数:type --》
PTHREAD_CANCEL_DEFERRED 延时取消
线程被取消的时候会继续往后执行,直到遇到取消点结束
取消点:指的是linux规定的一系列函数
PTHREAD_CANCEL_ASYNCHRONOUS 立即取消(线程立马退出)
线程的同步与互斥(重点)
=================
以下三种技术都是来协调线程的
前面学习进程间信号量:协调多个进程对于临界区资源的访问
临界区资源:就是信号量协调保护的那一部分代码
(1)互斥锁
作用:协调多个线程对于临界区资源(共享资源,例如:全局变量)的访问
学习方法:重点掌握互斥锁的特点(前提条件是,多个线程操作同一把锁)
哪个线程先上锁,后上锁的线程会阻塞,无法上锁
上锁操作要跟解锁操作配合使用,如果只有上锁,没有解锁(这个锁称之为死锁)
死锁:常见的情况有两种
第一种:写代码的时候,有上锁操作,但是没有解锁
第二种:线程刚一上锁就被取消(来不及解锁),导致死锁
相关的接口函数:
第一个函数:互斥锁的初始化
int pthread_mutex_init(pthread_mutex_t *mutex,pthread_mutexattr_t *restrict attr);
参数:mutex --》互斥锁变量
attr --》互斥锁的属性,一般设置为NULL,表示使用默认属性
第二个函数:互斥锁的使用
int pthread_mutex_lock(pthread_mutex_t *mutex); //上锁
int pthread_mutex_trylock(pthread_mutex_t *mutex); //非阻塞上锁
int pthread_mutex_unlock(pthread_mutex_t *mutex); //解锁
第三个函数:互斥锁销毁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
第四个函数:解决线程刚上锁就被取消导致的死锁
void pthread_cleanup_push(void (*routine)(void *),void *arg);
注册线程取消时需要处理的例程
参数:
void (*routine)(void *) 函数指针
void *arg --》
void pthread_cleanup_pop(int execute);
注意:这两个函数是在线程被取消的时候发挥作用了,如果线程是正常退出,那么两个函数不起作用
(2)条件变量
(3)信号量(线程)
条件变量
==============
1.作用:配合互斥锁一起使用,不能单独使用,满足某个条件可以帮助你阻塞线程,同时也提供函数帮助你解除对线程的阻塞
满足某个条件:这个条件是由程序员自己来写的
例如:卖票的代码,遇到票的数量为0,所有的窗口(线程)必须停止卖票
所以我的代码的条件判断就变成了if(当前票数==0){ 阻塞线程 }
特点:pthread_cond_wait()先解锁,然后阻塞当前线程
使用经验分享:
使用条件变量:先思考清楚你的线程满足什么条件需要阻塞
2.条件变量相关的接口函数
第一个函数:条件变量的初始化
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *attr);
参数:pthread_cond_t --》表示条件变量
attr --》设置为NULL,使用默认属性
第二个函数:条件变量的使用
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);//阻塞条件变量
参数:cond --》刚才初始化好的条件变量
mutex --》互斥锁,条件变量需要跟锁配合
int pthread_cond_broadcast(pthread_cond_t *cond); //唤醒条件变量,唤醒所有的线程
int pthread_cond_signal(pthread_cond_t *cond); //唤醒条件变量,只唤醒阻塞在条件变量上的某一个线程(解除条件变量的阻塞)
第三个函数:销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
线程间的信号量
===============
1.原理:跟进程间信号量一模一样
2.相关的接口函数
(1)初始化信号量
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:sem --》信号量
pshared --》设置为0,表示信号量在线程间使用
value --》信号量的值
(2)信号量的pv操作
int sem_wait(sem_t *sem); //p操作 -1
int sem_post(sem_t *sem); //v操作 +1
(3)信号量的销毁
int sem_destroy(sem_t *sem);
线程池
===================
线程池:理解为是一个巨无霸"线程",本质就是多个线程组成的一个集合
原理:说白了线程池的原理就是:
通过创建一个单链表(存函数指针),然后线程池里面的线程死命从单链表的头结点取出任务处理
主函数负责往单链表中死命的添加任务
需要大家封装如下三个核心函数
(1)线程池的初始化
threadpool_init();
(2)任务链表的初始化和往任务链表中添加任务
add_task();
(3)销毁线程池
threadpool_destroy();
网友评论