LINUX系统编程

目录

1.进程相关概念

程序和进程

并发

单道程序设计和多道程序设计

 CPU和MMU

进程控制块PCB

2.进程控制

fork函数

getpid函数

getppid

进程共享

3.exec函数族

execlp函数

execl函数

4.孤儿进程

5.僵尸进程

wait函数

waitpid函数

6.进程间通信

管道

pipe函数

管道的读写行为

FIFO

信号

信号共性

信号的特质

与信号相关的事件状态

产生信号

信号的处理方式

阻塞信号集

未决信号集

信号四要素

kill命令和kill函数

alarm函数

setitimer函数

信号集操作函数

信号捕捉

SIGCHLD信号

共享映射区

文件进程间通信

存储映射I/O

mmap

本地套接字

7.守护进程

定义

创建方式

8.线程

概念

线程共享资源

线程非共享资源

线程优缺点

线程控制原语

线程同步(锁机制)

锁的使用

使用互斥锁mutex的步骤

死锁

读写锁

条件变量

信号量


1.进程相关概念

程序和进程

程序储存在磁盘上,不会占用系统资源,运行程序会产生进程,死的(剧本)

进程是运行起来的程序,会占用内存,cpu,等系统资源,活的(戏)

并发

一个时间段中有多个进程都处于已启动运行到运行完毕之间的状态,但在任一时刻点上仍只有一个进程在运行(分时复用)

单道程序设计和多道程序设计

单道程序设计:每个进程排队执行,串行运行

多道程序设计:同时存放几道相互独立的程序,相互穿插运行,需要有硬件作为基础

时钟中断作为一种强制手段让进程让出cpu资源,对进程来说不可抗拒。由于cpu的计算速度极快,并发实际上是宏观并行,微观串行。

 CPU和MMU

 MMU:虚拟内存映射单元

进程控制块PCB

每个进程在内核当中都有一个进程控制块,本质是一个结构体,重点包含以下几个成员:

1.进程id

2.进程状态:初始态,就绪态,运行态,挂起态与终止态五种

 3.进程切换的一些cpu寄存器

4.描述虚拟内存空间的信息

5.描述控制终端信息

6.当前工作目录

7.umask掩码

8.文件描述符

9.和信号相关的信息

10.用户id和组id

11.会话和进程组

12.进程可以使用的资源上限

2.进程控制

fork函数

用于创建子进程,成功在父进程中返回子进程的pid,在子进程中返回0,失败在父进程中返回-1.子进程未被创建,返回错误值。

getpid函数

获取自身进程id函数

getppid

获取自身的父进程id函数

进程共享

父子进程相同:刚fork后,data段,text段,堆,栈,环境变量,全局变量,宿主目录位置,进程工作目录位置,信号处理方式

父子进程不同:进程id,fork函数返回值,各自的父进程,进程创建时间,闹钟,未决信号集

父子进程共享:读时共享,写时复制----------全局变量   1.文件描述符  2.mmap映射区。

特别注意,fork之后,父进程还是子进程先执行不确定,这取决于操作系统的进程调度算法

3.exec函数族

进程调用exec函数,该进程的用户空间代码和数据完全被新程序替换,从新程序启动例程开始执行,调用exec函数并不创建新的进程,所以调用前后进程的pid并未改变,(换核不换壳)。

execlp函数

int execlp(const *file,const char *arg,...);

加载一个进程,借助PATH环境变量,当PATH中所有目录没有参数1则出错返回-1.注意参数二作为命令行参数argv[0]传入,该函数通常用来调用系统程序,如ls,data,cat等命令。

execl函数

int execl(const char *path,const char *arg,...);

加载一个自己指定路径的程序

回收子进程

4.孤儿进程

父进程先于子进程结束,子进程成为孤儿进程,其父进程变为init进程,成为init进程领养孤儿进程。

5.僵尸进程

子进程终止时,父进程尚未回收,子进程残留资源(PCB)存放于内核当中,变成僵尸进程,注意:僵尸进程无法使用kill命令消除,因为僵尸进程已经终止执行。

wait函数

回收子进程退出资源,

pid_t wait(int *status)函数有以下功能:

1.阻塞等待子进程退出

2.清理子进程残留在内核的pcb资源

3.通过传出参数status得到子进程结束状态

waitpid函数

指定某一个进程进行回收

pid_t waitpid(pid_t pid, int *status,int options);

参数pid表示指定回收的子进程pid:>0表示pid,-1表示任意子进程,0表示同组的子进程;参数options:WNOHANG指定回收方式位非阻塞

返回值:>0表示成功回收,返回回收进程的pid,0表示函数调用时,参数3指定了WNOHANG,并且没有子进程结束,-1表示回收失败。

注意:wait和waitpid一次只能回收一个子进程,要想回收多个子进程需要用循环。

6.进程间通信

进程地址空间相互独立,每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,进程之间想要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,这种机制称为进程间通信(IPC)

常用的通信方式有:

管道

是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递,调用pipe系统函数即可创建一个管道,有如下特质:

1.其本质是一个伪文件(实为内核缓冲区)

2.由两个文件描述符引用,一个表示读端,一个表示写端

3.规定数据从管道的写端流入,从读端流出

内核使用环形队列机制,借助内核缓冲区实现

局限性:

1.数据不能进程自己写,自己读

2.管道数据不可反复读,一旦读走,管道中不再存在

3.采用半双工通信

4.只能作用域有血缘关系的进程间

pipe函数

创建并打开管道

int pipe(int fd[2])

参数:fd[0]表示读端,fd[1]表示写端

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

管道的读写行为

读管道:

1.管道有数据:read返回实际读到的字节数

2.管道无数据:1)无写端,read返回0;2)有写端,read阻塞等待

写管道:

1.无读端:异常终止。(SIGPIPE信号导致)

2.有读端:1)管道已满,阻塞等待;2)管道未满,返回写出的字节个数

FIFO

命名管道,用于无血缘关系的进程间通信。mkfiifo创建一个命名管道,读端用读的方式打开文件,写端用写的方式打开文件

信号

信号共性

简单、不能携带大量信息,满足条件才发送

信号的特质

信号是软件层面上的“中断”,一旦信号产生,无论程序执行到什么位置,必须立即停止运行,处理信号,处理结束再执行后续命令。

所有信号的产生以及处理全部都是由内核完成

与信号相关的事件状态

产生信号

1.按键产生

2.系统调用产生

3.软件条件产生

4.硬件异常产生

5.命令产生

递达:递送并且到达的过程

未决:产生和递达之间的状态,主要由于阻塞导致该状态

信号的处理方式

1.执行默认动作

2.忽略(丢弃)

3.捕捉(调用户处理函数)

阻塞信号集

本质是位图,用来记录信号的屏蔽状态,一旦被屏蔽的信号,在接触屏蔽前,一直处于未决态

未决信号集

用来记录信号的处理状态,该信号集中的信号,表示已经产生,但尚未被处理

信号四要素

信号使用之前,应先确定其四要素,而后再用

信号编号、信号名称、信号对应事件、信号默认处理动作

kill命令和kill函数

int kill(pid_t pid ,int signum)

pid:   >0:发送信号给指定进程

         =0:发送信号给跟调用kill函数的那个进程处于同一进程组的进程

          <1:取绝对值,发送信号给该绝对值所对应的进程组的所有成员

          = -1:发送信号给有权限发送的所有进程

alarm函数

使用自然计时法,定时发送STGALRM给当前进程

unsighed int alarm(unsigned int seconds):

seconds:定时秒数

返回值:上次定时剩余时间,无错误现象

注:time命令查看程序执行时间,实际时间=用户时间+内核时间+等待时间 ----》优化程序瓶颈:IO

setitimer函数

int setitimer(int which,const struct itimerval *new_value, struct itimerval *old_value):

new_value:定时秒数

其中it_interval表示上次任务间隔时间;it_value表示定时时间

old_value:传出参数,上次定时剩余时间

返回值成功:0;失败:-1 error

信号集操作函数

sigset_t set: 自定义信号集

sigemptyset(sigset_t *set):清空信号集

sigfillset(sigset_t *set):全部置1

sigaddset(sigset_t *set, int signum):将一个信号添加到集合中

sigdelset(sigset_t *set, int signum):将一个信号从集合中移除

sigismember(const sigset_t *set,int signum)“判断一个信号是否在集合中,在返回1,不在返回0

设置信号屏蔽字和接触屏蔽

int sigprocmask(int how,const sigset_t *set,sigset_t *oldset):

how: SIG_BLOCK设置阻塞;SIG_UNBLOCK:取消阻塞SIG_SETMASK用自定义集合set替换mask

set:用户自定义集合

oldset:旧有的mask。成功返回0,事变返回-1

查看未决信号集

int sigpending(sigset_t *set):

set:传出的未决信号集

信号捕捉

signal();

sigaction();

信号捕捉特性:

1.捕捉函数执行期间,信号屏蔽字由mask-》sa_mask,捕捉函数执行结束,恢复回mask

2.捕捉函数执行期间,本信号自动被屏蔽(sa_flags = 0)

3.捕捉函数执行期间,被屏蔽信号多次发送,接触屏蔽后只处理一次

内核实现信号捕捉简析

SIGCHLD信号

产生条件:子进程状态发生改变

借助SIGCHLD信号回收子进程:子进程结束运行时,其父进程会收到SIGCHLD信号,该信号的默认动作是忽略,可以捕捉该信号,在捕捉函数中完成子进程状态的回收。’

共享映射区

文件进程间通信

两个无血缘关系的进程分别以读的方式和写的方式打开一个文件,一个向文件中写入内容,一个向文件中读出内容,用法类似于管道,但是没有管道的属性,不能阻塞等待。

存储映射I/O

mmap

1.用于创建映射区的文件大小为0,实际指定非0大小的映射区,出总线错误

2.用于创建映射区的文件大小为0,实际指定0大小的映射区,出无效参数

3.用于创建映射区的文件读写属性为只读,映射区属性为读、写,出无效参数

4.创建映射区需要read权限,默认有一次读操作,mmap的读写权限应该,<=文件的open权限,文件为只写不行。

5.文件描述符fd在mmap创建映射区完成后即可关闭,后续访问文件用地址访问

6.offset必须是4K的整数倍(4096),(MMU映射的最小单位是4K)。

7.对申请的映射区内存,不能越界访问

8.mmap用于释放的地址,必须是mmap申请返回的地址

9.映射区访问权限为“私有”MAP_PRIVATE,对内存所做的所有修改,只在内存有效,不会反应到物理磁盘上

10.映射区访问权限为“私有”MAP_PRIVATE,只需要open文件时,有读权限,用于创建映射区即可

mmap保险的调用方式:

1.open(O_RDWR)

2.mmap(NULL,有效文件大小,PROT_READ|PORT_WRITE,MAP_SHARED,fd,0)

父子进程之间使用mamap通信

父进程先mmap创建映射区,指定share权限,fork子进程,一个进程读,一个进程写。

无血缘关系进程间通信

两个进程打开同一个文件,创建映射区,此时两个进程的映射区指向相同内存空间。指定flags为MAP_SHARED,一个进程写入,另外一个进程读出。

注意:mmap数据可以重复读取,而fifo数据只能读取一次,读取之后就消失。

匿名映射:只能用于血缘关系进程间通信

映射区大小可以随意指定,在MAP_SHARED参数后加|MAP_ANONYMOUS,文件描述符使用-1

本地套接字

7.守护进程

定义

daemon进程,通常运行于操作系统后台,脱离控制终端。一般不与用户直接交互。周期性的等待某个事件发生或周期性执行某一动作,不受用户登录注销影响。通常采用以d结尾的命名方式

创建方式

1.fork子进程,让父进程终止

2.子进程调用setsid()创建会话

3.通常根据需要改变工作目录位置chdir()

4.通常根据需要重设umask文件权限掩码

5.通常根据需要关闭文件描述符(或重定向)

6.守护进程业务逻辑,while()

实例:

#include <iostream>
#include <sstream>
#include <ctime>
#include <stdio.h>
#include <cstdlib>
#include <sys/stat.h>
#include <time.h>
#include <assert.h>
#include <unistd.h>

int main()
{
	// 1.在父进程中执行fork并exit退出;
	pid_t m_pid;
	m_pid = fork();//创建子进程

	if (m_pid < 0)
	{
		exit(1);
	}
	else if (m_pid > 0)
	{
		exit(0);//父进程退出
	}

	// 2.在子进程中调用setsid函数创建新的会话;
	setsid();//若当前进程不是进程组长,创建一个新会话;若当前进程已经是进程组长,返回错误

	// 3.调用getcwd()函数获取当前目录路径,并在子进程中调用chdir函数,让根目录 ”/” 成为子进程的工作目录;
	char m_path[1024];
	if (getcwd(m_path, sizeof(m_path)) == NULL)
	{
		perror("get path error!");
		exit(1);
	}
	printf("get path success !");

	chdir("/");

	// 4.在子进程中调用umask函数,设置进程的umask为0
	umask(0);//设置进程的权限掩码,参数0表示:可以设置任何权限(读、写、执行)

	// 5.在子进程中关闭任何不需要的文件描述符
	for (int i = 0; i < getdtablesize(); i++) {
		close(i);
		printf("文件描述符关闭成功!");
	}
    //也可以利用重定向的方式使文件描述符重定向到文件空洞中

    while(1){
    //守护进程实现代码
    }

    return 0;
}

8.线程

概念

线程就是轻量级的进程,有独立的pcb,但是和进程相比没有独立的进程地址空间,是cpu执行的最小单元,进程是分配资源的最小单位

ps -Lf 进程id ----> 线程号(非线程id)

线程共享资源

1.文件描述符表

2.每种信号的处理方式

3.当前工作目录

4.用户ID和组ID

5.内存地址空间(./text/data/bss/heap/共享库)

线程非共享资源

1.线程id

2.处理器现场和栈指针(内核栈)

3.独立的栈空间(用户空间栈)

4.error变量

5.信号屏蔽字

6.调度优先级

线程优缺点

优点:1.提高程序并发性 2.开销小  3.数据通信,共享数据方便

缺点:1.库函数,不稳定  2.调试编写困难,gdb不支持  3.对信号支持不好

线程控制原语

1.pthread _t pthread_self(void)   获取线程id,线程id是在进程地址空间内部,用来标识线程身份的id号

返回本线程id

2.int pthread_create(pthread_t *tid,const pthread_attr_t *attr, void *(*start_rountn)(void *),void *arg)    参数1:传出参数,表示新创建的子线程id。参数2:线程属性,传NULL表示使用默认属性。参数3:子线程回调函数,线程会执行这个回调函数。

循环创建n个子线程

for循环,将int类型i,强转成void *,传参

3.void pthread_exit(void *retval):退出当前线程;retval:退出值。无退出值时,NULL

4.int pthread_join(pthread_t thread, void **retval); thread:待回收的线程id,retval;传出参数,回收的那个线程退出值,成功返回0;

5.pthread_detach(pthread_t thread):设置线程分离,thread:待分离的线程id

6.int pthread_cancel(pthread_t thread)杀死一个线程,需要到达取消点(保存点), thread:待杀死的线程id,返回值:成功返回0。如果线程没有到达取消点,那么pthread_cancel无效,可以使用pthread_testcancel();成功被其杀死的线程返回-1,使用pthread_join回收

线程同步(锁机制)

协同步调,对公共区域数据按序访问,防止数据混乱,产生与时间有关的错误

锁的使用

建议锁,对公共数据进行保护,所有线程(应该)在访问公共数据前先拿锁再访问,但锁本身不具备强制性

使用互斥锁mutex的步骤

1.pthread_mutex_t lock 创建锁

2.pthread_mutex_init 初始化

3.pthread_mutex_lock 加锁

4.访问共享数据(stdout)

5.pthread_mutex_unlock 解锁

6.pthread_mutex-destroy 销毁锁

注意:尽量保证锁的粒度越小越好(访问共享数据前加锁,访问结束后立即解锁);互斥锁本质是一个结构体,但我们可以将其看作成一个整数,经过pthread_mutex_init函数初始化为1,加锁对其--,解锁对其++

restrict

用来限定指针变量,被该关键字限定的指针变量所指向的内存操作,必须由本指针完成。

死锁

是使用锁不恰当导致的现象,有以下两种可能:

1.对同一个锁反复lock

2.两个线程各自拥有一把锁,互相请求另一把

读写锁

读写锁只有一把,读模式下加锁叫读锁,写模式下加锁叫写锁,遵循读时共享,写时独占原则

1.读写锁是写模式加锁时,解锁前,所有对该锁加锁的线程都会被阻塞。

2.读写锁是读模式加锁时,解锁前,如果线程以读模式对其加锁,会成功,如果线程以写模式加锁会被阻塞

3.读写锁是读模式加锁时,既有试图以写模式加锁的线程,也有试图以读模式加锁的线程,那么读写锁会阻塞随后的读模式锁请求,优先满足写模式锁,读锁,注意:写锁并行阻塞,写锁优先级高

读写锁非常适合于对数据结构读的次数远大于写的情况

条件变量

条件变量本身不是锁,但是通常结合锁来使用(mutex)

pthread_cond_t cond初始化条件变量:

1.pthread_cond_init(&cond,NULL);动态初始化

2.pthread_cond_t cond = PTHREAD_COND_INITIALIZER;静态初始化

pthread_cond_signal();唤醒阻塞在条件变量上的(至少)一个线程

pthread_cond_broadcast(),唤醒阻塞在条件变量上的所有线程

信号量

相当于初始化值为N的互斥量

函数:

sem_t sem:定义类型

int sem_init(se_t *sem,int pthread,unsigned int value):

参数:sem:信号量;pshared:0用于线程同步,1用于进程同步吧;value:N值(指定同时访问的线程数)

sem_destroy()

sem_wait(),一次调用,做一次--操作,当信号量为0时,再--就会阻塞(对比pthread_mutex_lock)

sem_post(),一次调用,做一次++操作,当信号量为N时,再次++就会阻塞(对比pthread_mutex_unlock)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值