嵌入式---linux进程

1.进程

基本概念

程序:编译后产生的,格式为ELF的,存储于硬盘的文件
进程:程序中的代码和数据,被加载到内存中运行的过程
程序是静态的概念,进程是动态的概念

进程的复刻
除了系统的初始化进程之外,其他的所有进程都是通过 fork() 复刻而来的
一个进程复刻一个子进程的时候,会将自身几乎所有的资源复制一份,详情请查阅教科书:

进程的API

pid_t pid = fork( );
将当前的进程复制一份,然后这两个进程同时从本函数的下一语句开始执行
所有的代码、变量都会复制成两份
返回值:在原先的进程中返回一个大于零的子进程的PID
在新建的进程中返回0
两个进程是并发执行,没有先后次序。
如果要控制进程的进度,要依赖于信号量、互斥锁、条件量

wait( int *status )
主要功能:让父进程阻塞等待子进程直到子进程结束;
回收子进程的资源;
获取子进程的退出状态(包括退出值、终止信号)
wait无法指定回收特定的子进程,哪个子进程先死掉就收哪个。
如果 status 为 NULL, 代表父进程不关心、不获取、不需要子进程的退出状态。
status 用来存放子进程的退出状态,如何解析 status:
WIFEXITED(status) : 判断子进程是否正常退出,在正常退出的情况下,才能获取其退出值。
WEXITSTATUS(status) : 获取子进程的退出值
WIFSIGNALED(status) : 判断子进程是否被信号杀死
WTERMSIG(status) :获取杀死子进程的信号的值

waitpid( pid_t pid , int *status , int option)
主要功能:让父进程回收子进程的资源;获取子进程的退出状态(包括退出值、终止信号)
参数 pid 用来控制究竟想要回收哪个子进程。
如果 status 为 NULL, 代表父进程不关心、不获取、不需要子进程的退出状态。
参数 option 用来控制等待的策略,如果是0,那就是默认的阻塞等待,如果是WNOHANG,这代表不阻塞,指定的子进程如果当时已经是僵尸了那么就立即回收,否则waitpid()立即返回。

int execl(const char *path, const char arg, … / (char *) NULL */);
int execlp(const char *file, const char arg, … / (char *) NULL */);
int execle(const char *path, const char arg, … /, (char *) NULL, 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[]);
功能:给本进程加载指定的程序,如果成功,本进程的整个内存空间都被覆盖
执行指定程序之后,会自动获取原来的进程的环境变量。
各个后缀字母的含义:
l : list 以列表的方式来组织指定程序的参数
v: vector 矢量、数组,以数组的方式来组织指定程序的参数
e: environment 环境变量,执行指定程序前顺便设置环境变量
p: 专指PATH环境变量,这意味着执行程序时不需要全路径
这组函数只是改变了进程的内存空间里面的代码和数据,但并未改变本进程的其他属性,比如PID,比如跟其他进程的父子关系。

僵尸进程

任何进程退出后,都会变成僵尸
变成僵尸的原因,是为了方便其父进程获取其退出状态
父进程通过调用 wait()/waitpid() 来释放某一个子进程的内存资源
僵尸态是进程生命周期的必经阶段,无法避免,但要尽快清理腾出内存资源

2.进程间通信方式

管道

无名管道 pipe:适用于亲缘关系进程间的、一对一的通信
有名管道 fifo :适用于任何进程间的一对一、多对一的通信
套接字 socket:适用于跨网络的进程间通信
信号:异步通信方式
system-V IPC对象
共享内存:效率最高的通信方式
消息队列:相当于带标签的增强版管道
信号量
信号量组:参数复杂,功能强大到臃肿
POSIX有名信号量:适用于多进程,参数简单,接口明晰,老少咸宜
POSIX无名信号量:适用于多线程,参数简单,接口明晰,童叟无欺

无名管道
int pipe( int fd[2] )
功能:创建无名管道 pipe
注意:pipe 拥有两个文件描述符,一个专用于读fd[0],一个专用于写fd[1]
创建出来的 pipe 的描述符,只能通过子进程继承的方式传递给别的进程,因此只能用于亲缘进程间的通信,其他非亲缘进程无法获取 pipe 的描述符。
不能有多个进程同时对 pipe 进行写操作,否则数据有可能被覆盖
总结: pipe 适用于一对一的、具有亲缘关系的进程间的通信。

文件重定向
dup2( int fd1, int fd2 )
功能:将文件描述符 fd2 重定向为 fd1 所关联的文件
例如:
int fd1 = open(“a.txt”);
int fd2 = open(“b.txt”);

// 此时,fd1 --> a.txt; fd2 --> b.txt
// 将fd1“覆盖”fd2,即:fd2 变得跟 fd1 一样,也指向 a.txt
dup2(fd1, fd2);

// 此时,fd1 --> a.txt; fd2 --> a.txt
// 此时,不管向fd1还是fd2写入数据,数据都将流向 a.txt

命令 wc 的使用
wc: word counter 字符计数器,可用来计算字符数、单词数或者行数
wc -c : 计算从该键盘输入的字符数
wc -w : 计算从该键盘输入的单词数
wc -l : 计算从该键盘输入的行数
按 ctrl + d 结束输入

管道文件的读写特性:
有写者 没有写者
有数据 无数据 有数据 无数据
读操作 正常读取 阻塞 正常读取 立即返回0

有读者 没有读者
缓冲区未满 缓冲区已满 缓冲区未满 缓冲区已满
写操作 正常写入 阻塞 立即被系统发出的SIGPIPE杀死
注意:
读者 —— 对管道拥有读权限的进程
写者 —— 对管道拥有写权限的进程

文件的阻塞特性
阻塞:操作文件且当资源不可得时,进程进入睡眠(挂起、pending)状态
对于普通文件而言,默认是非阻塞的。
对于管道文件而言,默认是阻塞的。

管道的打开
管道打开时,必须同时有读者和写者,否则 open 会阻塞

Daemon进程(翻译:精灵进程、后台进程、守护进程)的概念:
它们常常在系统引导装入时起动,也可以在任意的时刻由开发人员启动,在系统关闭时终止。因为它们没有控制终端,所以说它们是在后台运行的。 Linux 系统有很多精灵进程,它们执行日常事物活动。可以通过命令 ps -ef 来查看那些名字最后一个字母是d进程。

Daemon进程(翻译:精灵进程、后台进程、守护进程)的创建步骤:
忽略由于终端关闭而产生的 SIGHUP 信号:
signal( SIGHUP, SIG_IGN );

为了能正常调用setsid,产生一个非组长子进程,让子进程去继续。
pid_t a = fork();
if(a > 0)
exit(0);

创建新会话,抛弃原有的带控制终端的会话。
setsid();

为了防止新建会话的进程再次打开控制终端,让其再产生一个孙子进程,那么这个孙子进程就不是该会话的创始人,因此孙子进程无权也无法打开控制终端。
pid_t a = fork();
if(a > 0)
exit(0);

虽然一路创建子孙进程、并创建了新会话,但目前这个孙子进程依然处于原来的进程组中,而进程组可以接收信号,因此这个要升仙的孙子必须脱离原来的进程组,自立门户。
setpgrp();

关闭释放来自祖辈的所有文件资源
int maxfd = sysconf(_SC_OPEN_MAX); // 返回当前进程打开文件的最大描述符
for(int i=0; i<maxfd; i++)
{
close(i); // 关闭所有文件(包括打开了的和未打开的)
}

去除创建文件时影响文件权限的掩码,以便于这位精灵进程创建文件更加灵活
umask(0);

将当前工作路径,切换到一个不可被卸载的分区下,否则一旦工作路径被卸载,神仙也会死。
chdir("/");

信号
概念:系统中用来在进程间异步发送的数据(异步:未经预先协商好的情况,比如门铃)
一般怎么处理信号:在信号来之前,尽早地做好准备
响应方式:
啥也不干,等待信号的默认处理。(man 7 signal 查阅信号细节)
忽略,或称丢弃。 代码实现: signal( SIGXXX, SIG_IGN );
屏蔽,或称阻塞,即延迟响应。代码实现比较复杂。
捕获。即指定一个关联函数,称为信号响应函数,信号来了就去执行函数。
信号快捷键:
SIGINT : ctrl + c
SIGQUIT : ctrl +
SIGSTOP : ctrl + z (fg 可以将被暂停的 vi 程序放回前台运行)

特殊信号:
SIGSTOP / SIGKILL
这两个信号,不能被忽略,也不能被屏蔽,也不能被捕获,只能按默认处理。

跟信号相关的命令
kill -l:查看所有的信号的编号
man 7 signal:查看信号的详细信息
kill -3 PID:向进程号为 PID 的进程发送3号信号
kill -s SIGQUIT:向进程号为 PID 的进程发送3号信号
killall xxx:向名字为xxx的所有进程发送信号,一般用在你不知道xxx的 PID 或者系统中有很多xxx进程,你懒得一个一个发信号。killall默认发送的 15号 SIGTERM 信号
killall -s SIGXXX xxx:向名字为xxx的所有进程发送信号SIGXXX

API
pause()
功能:暂停当前进程,直到收到一个信号。

int sigemptyset(sigset_t *set); 清空信号集
int sigfillset(sigset_t *set); 将所有的信号填入信号集
int sigaddset(sigset_t *set, int signum); 将指定信号 signum 填入信号集
int sigdelset(sigset_t *set, int signum); 将指定信号 signul 从信号集中删除
int sigismember(const sigset_t *set, int signum); 判定指定信号 signum 是否存在于信号集中
功能:处理信号集

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能:设置屏蔽信号,或者取消屏蔽信号
参数:
how:操作模式
SIG_BLOCK: 继续阻塞原有的信号,并加上指定信号集的信号统统阻塞
SIG_UNBLOCK:解除指定的信号集的信号
SIG_SETMASK:解除原有的正在阻塞的信号,而阻塞现在指定的信号集中的信号
set :指定要操作的信号集
oldset : 存放原有的阻塞信号集

int kill( pid_t pid, int sig );
功能:给指定的进程,发送一个指定的信号
参数:
pid:接收信号的进程的PID
sig:发送的信号的序号
注意:
两个参数不能搞乱,搞乱编译的时候不报错,但是运行的时候会报错。

int fcntl(int fd, int cmd, …);
功能:这是一个多功能函数,其功能取决于 cmd
F_GETFL :获取指定文件 fd 的读写模式(比如只读、可读写、阻塞……)
F_SETFL :设置指定文件 fd 的读写模式

一般做法:
先获取文件原有的读写模式
再在原有的模式的基础上,增加我们需要的或者去除我们不想要的模式,然后再设置回去。
比如:
// 取得文件fd的读写模式,放入mode中
int mode = fcntl(fd, F_GETFL);

// 在原有的读写模式mode中,加入非阻塞模式
mode |= O_NONBLOCK; mode > 110101111010??
O_NONBLOCK
> 00000000000011

// 将设置好的读写模式mode,设置回文件fd中
fcntl(fd, F_SETFL, mode);

// 再次取得文件fd的读写模式,放入mode中
int mode = fcntl(fd, F_GETFL);

// 在此读写模式mode中,去掉非阻塞模式
mode &= ~O_NONBLOCK;

// 将设置好的读写模式mode,设置回文件fd中
fcntl(fd, F_SETFL, mode);

信号们:
名称 值 默认行为 备注
SIGHUP 1 Term 当关闭控制终端(登陆窗口)时触发该信号
SIGINT 2 Term 按下 ctrl + c 时触发该信号
SIGQUIT 3 Core 按下 ctrl + \ 时触发该信号
SIGILL 4 Core 遇到CPU无法执行的非法指令时触发该信号
SIGABRT 6 Core 当调用函数 abort() 时触发该信号
SIGFPE 8 Core 当发生除零操作时触发该信号
SIGKILL 9 Term 系统安全的保障,因为它只能被默认处理
SIGSEGV 11 Core 当进程访问了非法的内存时触发该信号
SIGPIPE 13 Term 当对一个没有读者的管道进行写操作时触发该信号
SIGALRM 14 Term 定时器到点了,触发该信号
SIGTERM 15 Term 终止信号,是命令 killall 的默认信号
SIGUSR1 30,10,16 Term 自定义信号1
SIGUSR2 31,12,17 Term 自定义信号2
SIGCHLD 20,17,18 Ign 当子进程暂停或者退出时触发该信号
SIGCONT 19,18,25 Cont 让目标进程继续运行
SIGSTOP 17,19,23 Stop 让目标进程暂停
SIGTSTP 18,20,24 Stop 让目标进程暂停
SIGTTIN 21,21,26 Stop 让目标进程暂停
SIGTTOU 22,22,27 Stop 让目标进程暂停
……

几个跟 system-V IPC 对象相关的命令:

ipcs -a:查看当前系统中存在的所有的 IPC 对象。
ipcs -q:查看当前系统中存在的 消息队列。
ipcs -m:查看当前系统中存在的 共享内存。
ipcs -s:查看当前系统中存在的 信号量。
删除 IPC 对象
ipcrm -Q key : 删除指定的消息队列
ipcrm -q id : 删除指定的消息队列

ipcrm -M key : 删除指定的共享内存
ipcrm -m id: 删除指定的共享内存

ipcrm -S key : 删除指定的信号量
ipcrm -s id: 删除指定的信号量

API
key_t ftok( const char *path, int n );
功能:产生一个用于标记 IPC 对象的 key
参数:
path:当前进程所在的路径,一般最好写绝对路径。
n:指定路径下,产生的key的序号,用来区分相同路径下的不同的 key

消息队列

int msgget( key_t key, int msgflg );
功能:创建或者打开一个消息队列
参数:
key:消息队列的唯一标识
msgflg:权限选项组合
IPC_CREAT:试图创建消息队列。
IPC_EXCL:当创建失败时,报错。
八进制权限,他们以位或的形式组合起来:
IPC_CREAT | 0666: 代表不存在就创建且设置权限为666,存在就打开
IPC_CREAT | IPC_EXCL | 0666: 代表存在就报错,不存在就创建并打开

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:向指定的消息队列,发送一个消息
参数:
msqid:指定的消息队列的 ID。
msgp:消息结构体指针。
msgsz:消息正文的长度。
msgflg:可选项。
0:以默认的模式进行发送数据,即:当缓冲区满了继续发会阻塞。
IPC_NOWAIT:以非阻塞模式进行发送数据。即:当消息队列的缓冲区满了之后,继续发送不会阻塞,而是会立即出错返回。

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
功能:从指定的消息队列,接收一个消息
参数:
msqid:指定的消息队列的 ID。
msgp:消息结构体指针。
msgsz:消息结构体中存放正文的内存空间的最大值。
msgtyp:指定接收消息的标签(类型)。
msgflg:可选项。
0:以默认的模式进行接收数据,即:当消息队列中没有消息时会阻塞。
IPC_NOWAIT:以非阻塞模式进行接收数据。即:当消息队列没有消息时,继续收不会阻塞,而是会立即出错返回。

共享内存

int shmget(key_t key, size_t size, int shmflg);
功能:创建或者打开一个共享内存对象
参数:
key:由 ftok( ) 产生的唯一标识。
size:共享内存的大小,要求是偶数。
shmflg:可选项
IPC_CREAT:创建
IPC_EXCL:如果存在就报错
八进制权限,他们以位或的形式组合起来。
返回值:代表该共享内存的ID
注意:创建的共享内存是物理内存,必须要映射到本进程的内存空间才能使用。

void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:将指定的共享内存,映射到本进程内存空间
参数:
shmid:指定的共享内存的ID
shmaddr:指定映射后的地址,因为是虚拟地址,分配的原则要兼顾诸如段对齐、权限分配等问题,因此用户进程是无法指定的,只能由系统自动分配,因此此参数必须为NULL,表示交由系统来自动分配。
shmflg:可选项
0:默认,代表共享内存可读可写。
SHM_RDONLY:代表共享内存只读。
返回值:共享内存映射后的虚拟地址入口。

int shmdt(const void *shmaddr);
功能:将指定的共享内存,从本进程内存空间解除映射
参数:
shmaddr:共享内存的虚拟地址入口。

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:对指定的共享内存进行某个操作
参数:
shmid:指定的共享内存的ID
cmd:一些命令字
IPC_STAT:获取共享内存 的一些信息,放入shmid_ds{ }中
IPC_SET:将 buf 中指定的信息,设置到本共享内存中
IPC_RMID:删除指定的共享内存,此时第三个参数 buf 将被忽略
buf:用来存放共享内存信息的结构体

信号量组

int semget(key_t key, int nsems, int semflg);
功能:创建或者打开一个信号量组对象
参数:
key:由 ftok( ) 产生的唯一标识。
nsems:信号量组里面包含的信号量元素个数
semflg:可选项。
IPC_CREAT:创建
IPC_EXCL:如果存在就报错
八进制权限,他们以位或的形式组合起来。
返回值:代表这个信号量组的ID
注意
必须要严格区分是否是新建的信号量组,如果是新建的必须初始化。
刚新建的信号量组里面的每一个元素的值都是随机值,必须初始化。

int semctl(int semid, int semnum, int cmd, …);
功能:对指定的信号量组进行某个操作
参数:
semid:指定的信号量组的ID
semnum:指定的组里面的元素的序号(从0开始)
cmd:具体操作的命令字
IPC_STAT:获取信号量组的一些信息,放入semid_ds{ }中
IPC_SET:将 semid_ds{ } 中指定的信息,设置到信号量组中
IPC_RMID:删除指定的信号量组。
GETALL:获取所有的信号量元素的值。
SETALL:设置所有的信号量元素的值。
GETVAL:获取第semnum个信号量元素的值。
SETVAL:设置第semnum个信号量元素的值。
以上命令字需要的参数不同,具体如下:
union semun
{
int val; /* Value for SETVAL */
struct semid_ds buf; / Buffer for IPC_STAT, IPC_SET */
unsigned short array; / Array for GETALL, SETALL */
struct seminfo __buf; / Buffer for IPC_INFO
};

struct semid_ds
{
struct ipc_perm sem_perm; /* Ownership and permissions /
time_t sem_otime; /
Last semop time /
time_t sem_ctime; /
Last change time /
unsigned long sem_nsems; /
No. of semaphores in set */
};

int semop(int semid, struct sembuf sops, size_t nsops);
功能:对指定信号量组中的若干个元素进行P/V操作
参数:
semid:指定的信号量组的ID
sops:对某个元素进行操作的细节。
nsops:本次操作的元素的总数。
相关数据结构体:
对某一个元素进行的操作的所有细节,统统由如下结构体决定:
struct sembuf
{
unsigned short sem_num; /
信号量元素的序号(从0开始) /
short sem_op; /
P/V操作的数值,负代表P操作,正代表V操作 /
short sem_flg; /
可选项 */
};

举例:假设有一个包含了3个元素的信号量组,要对其中的第0、1、2号元素分别进行P、P、V操作:
struct sembuf op[3];
op[0].sem_num = 0;
op[0].sem_op = -1;
op[0].sem_flg = 0;

op[1].sem_num = 1;
op[1].sem_op = -1;
op[1].sem_flg = 0;

op[2].sem_num = 2;
op[2].sem_op = +1;
op[2].sem_flg = 0;

semop(semid, op, 3);

P / V操作
P操作:即申请资源,即减操作,由于是申请使用资源,因此在资源不可得的时候会阻塞。
V操作:即释放资源,或称增加资源,此操作永不阻塞。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值