linux开发

主要介绍了linux下的编程包括Makefile,进程编程等

MakeFile

基本结构

格式: 目标:源
test: main.o add.o
gcc -o test main.o add.o

main.o: main.c add.h
gcc -c main.c

add.o: add.c
gcc -c add.c

clean:
rm *.o
rm test

Makefile变量

$(变量)

OBJS = foo.o bar.o
CC = gcc
CFLAGS = -Wall -O -g

$(CC) $(OBJS) -o myprog

$@ 与 $^

test: main.o add.o
gcc -o test main.o add.o
gcc -o test $@ $^

wildcard通配符

SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)

test: $(OBJS)
gcc -o $@ $^

gcc -MM main.c ;;自动扫描依赖文件.depend

Autotools工具

-- linux上的软件开发一般都用autotools来制作Makefile。

-- 开源软件configure生成Makefile
-- Makefile.am
-- ./configure ;; 下载的
-- bin_PROGRAMS = hello
-- hello_SOURCES-hello.c
-- configure.in

进程开发

基础

进程运行状态

就绪、执行、暂停、睡眠(深度、浅度)、僵死。

linux进程描述符:task_struct

包括:thread_info 线程信息;
mm_struct 内存信息;
tty_struct 关联的控制台;
fs_struct 当前的目录;
files_struct 文件描述符;
signal_struct 信号

-- 通过esp(栈寄存器) & 0xfffe0000可得到当前 thread_info,然后 current_thread_info()->task 可得到task_struct。

pid

-- task_struct 双向链表
-- 树表示父子进程关系
-- PID哈希表:快速得到通过PID获取到task_struct
-- 查看系统最大的PID: cat /proc/sys/kernel/pid_max

-- 遍历所有进程

# define for_each_process(p) \
for(p=&init_task; (p=list_entry((p)->tasks.next, \
struct task_struct, tasks) \
)!= &init_task;)

创建 fork/vfork/clone

fork()

父进程返回子进程的PID,子进程返回0.
重新申请一份内存,赋值资源:mm(内存)/fs(当前目录)/files/signal(信号)/tty(控制台)
代码段只读、数据段读写。子进程在起始被fork时,原数据段被映射为只读的,子(父)进程在进行写操作时,会触发中断,重新分配数据段,再次执行原语句data = 20;对应代码training/process/fork/fork_child.c,写时拷贝技术,copy&&write,需要硬件支持mmu系统。

vfork(),在没有mmu系统中

五类资源,vfork()时,不申请mm内存,mm直接指向父进程的mm,在内存方面完全共享。不支持内存分裂。vfork()的子进程与父进程共享内存 vfork.c

clone(), 线程,轻量级线程

五类资源都是指向而不重申请,内核全部使用dofork(),而参数不同。

起点:进程0

-- init pid = 1;
-- 进程0优先级最低,全局变量,通过exec()创建init进程,完成后,变为 while(1) WFI;空闲等中断状态。
-- 在实现最后的进入低功耗态时,存在着不同的实现方式:可能所有的进程都查看一下是否是最后一个进程,若是则进入低功耗。优先级。

-- 可以在proc/下的status中查看父进程

终止

-- 停止进程的所有线程
exit_group() 调用 --》 do_group_exit()

-- 父进程通过wait()函数来清理子进程,子进程的task_struct才被删除,之前处于僵尸态

子进程死而父进程不清理 ps aux

-- 子进程会成为僵尸态,除非父进程被干掉

子进程不死而父进程死掉

-- 父进程被干掉后,子进程变为孤儿进程,托孤过程,变为init的子进程。
-- 为什么不会代替前一级?

线程的类型

-- I/O消耗型:伴随用户体验,I/O消耗型对CPU很敏感
-- CPU消耗型
-- ARM大核配小核,八核上,弱核跑I/O消耗型,big LITTLE

调度目标

-- 快速响应:靠抢占实现
-- 高吞吐率
蜘蛛侠法则,最优的最少 spider norm

调度策略 与 优先级

-- RT: (0 - 99)SCHED_FIFO(同等优先级也不轮转)、 SCHED_RR(同等优先级轮转)
-- Normal: (100 - 139)SCHED_OTHER,劳动人民的善良性,nice值(-20 - +19),nice值越高,优先级越低。
-- 动态的抢占依然发生

动态优先级

-- 动态优先级,task_struc.static_prio反映进程创建时的优先级,nice值
-- 干活变低,睡眠变高优先级,effective_prio()返回一个进程的动态优先级,根据task——struct的sleep_avg对nice进行+-5的调整

-- vruntime = delta * NICE_0_LOAD / se.weight
-- 睡眠型且优先级高【100-139】

linux RT与Normal之间的策略

-- 在RT与Normal中,以1s为周期,95%的时间若都跑RT,内核会切断RT,然后给5%的时间给Normal

相关系统调用

-- nice(), sets a process's nice value
-- sched_setscheduler(), set s process's scheduling policy
-- sched_getscheduler(), get a process's scheduling policy
-- sched_setparam(), set a process's real-time policy
-- sched_getparam(), get a process's real-time policy
-- sched_rr_get_interval() get a process's timeslice value

墨菲定律

-- 软件工程会将小概率事件变为必然事件

多核负载均衡

-- thread_life.cpp
-- time ./a.out ;real,user,sys

RT任务的负载均衡CFS

-- RTC 线程,通过一定的策略来进行负载的均衡,常见的为CFS
-- 以劳动为快乐
-- BFS调度算法,核比较少时,算法更优;核比较多时,CFS更优,适合分布式。

内核线程

-- 只在内核空间来运行,软实时的linux可以通过配置或者运行一个子内核的方式来达到硬实时。

多进程开发

进程类型

-- 交互进程,有shell控制和运行,可在前台、后台运行
-- 批处理进程,该进程不属于某个终端,它被提交到某个队列中以便顺序执行【交互与终端有关】
-- 守护进行, 该进程只有在需要时才被唤醒在后台执行。一般在linux启动时开始执行【驱动】

linux下进程的模式

-- 进程的执行模式划分为用户模式与内核模式。用户进程通过系统调用来调用内核进程。

linux系统调用的实现

系统调用表

 ENTRY(sys_call_table)
 .long sys_restart_syscall
 .long sys_exit
 .long sys_fork
 .long sys_read
 .long sys_write
 .long sys_open    /* 5 */

实现机制

软中断, x86调用 init $0x80指令,从用户空间切换到内核空间(128号异常处理程序,system_call()),系统调用号放入eax,system_call()将系统调用号与NR_syscalls对比,合理调用sys_xxx(),系统调用的返回值也放入eax。

进程创建

fork

-- 根据返回值,判断为父进程(子进程PID)还是子进程(0),子进程通过exit()发送结束信号给父进程,父进程调用wait()函数来清理子进程

写时拷贝

-- 即虽然fork时,子进程申请了内存,父进程与子进程的实际物理内存是在写入操作时,才实际分配的,并且,谁写入的晚,谁使用原来的内存。

exec 函数族

-- exec函数族提供了在一个进程中启动另一个进程的方法。它可以根据指定的文件名或目录名,找到对应可执行文件,,并用它来取代原调用程序的数据段、代码段 和 堆栈段, 在执行完后,元调用进程的内容除了进程号外,其他全部被新的进程替换了。

-- 可执行文件可以是二进程文件,可以是可执行脚本
-- 如果一个进程想执行另一个程序,可以先掉用fork新建一个进程,然后调用任意exec。这样可以看作通过执行应用程序而产生了一个新进程一样。

exit 和 _exit

void exit(int status)
-- status是整型的参数,可以利用这个参数传递进程结束时的状态,一般来说,0表示没有意外的正常结束,其他便是出现问题
-- 在实际编程时,可以用wait系统调用接收子进程的返回值,从而针对不同情况进行处理。
-- _exit()函数的作用:直接使进程停止,清除其内存空间,并销毁在内核中的各种数据结构;
-- exit()函数在滴哦用exit系统调用 之前,做了一些包装,要检查文件的打开情况,把文件缓存区中的内容写回文件等。

wait 和 waitpid

-- waith函数使父进程阻塞,知道收到子进程结束信号。
-- waitpid,有若干选项,wait只是调用waitpid的阻塞形式,它可以提供非阻塞的wait功能。
-- waitpid(pid_t pid, int *status, int options);在opitons处包括阻塞(0),非阻塞WNOHANG(子进程无信号则返回值为0), WUNTRACED(若支持作业控制,则由pid指定的子进程已暂停, 且其状态自暂停以来还为报告够,则返回其状态)。

linux守护进程

作用

-- Daemon进程,是linux中的后台服务进程。它通常独立与控制终端,并且周期性的执行某种任务或等待处理某些发生的事情。
-- 守护进程一般在系统引导装入时启动,在系统关闭时终止。大多数服务都是守护进程实现的。

特点

-- 每个从终端开始的进程都依赖与此终端,终端关闭时,相应的进程都会关闭,而守护进程能够突破这种线程,它从执行开始运转,知道整个系统退出才会关闭。

命令

-- 查看常用的系统守护进程 ps -axj

编写

-- 1、创建子进程、父进程退出。 【孤儿进程升级为init子进程】
pid = fork();
if(pid > 0) exit(0);

-- 2、在子进程中创建新会话
进程组是一个或多个进程的集合。进程组有进程组ID来唯一标志PGID。每个进程组都有一个组长进程,组长进程的进程号等于进程组ID。
会话期,会话期是一个或多个进程组的集合,通常一个会话开始与用户登录,终止与用户退出,在此期间该用户运行的所有进程都属于这个会话组。
-- setsid函数
用于创建一个新的会话,并且任该会话组的组长,让进程摆脱原会话、进程组、终端的控制

-- 3、改变当前目录为根目录。 fs
使用fork创建的子进程继承了父进程的当前目录,由于在进程运行过程中,当前目录所在的文件系统是不能卸载的,这会对以后使用的会话造成诸多的麻烦。所以改变当前目录为/目录。chdir("/")

-- 4、改变文件权限掩码。 files
使用父进程的文件权限掩码会给子进程使用文件带来诸多的麻烦,因此把文件权限掩码设置为0,可以大大增加该守护进程的灵活性。 umask(0)

-- 5、关闭文件描述符。 files
从父进程那继承的已经打开的文件,这些文件子进程keying从来不会读写,不仅消耗了资源,而且导致所在的文件系统无法卸载。由于重写设置了会话,因此从终端输入的字符不可能达到守护进程,守护进程用常规的(printf)输出的字符也不可能在终端上显示出来。所以文件描述符为0,1,2的三个文件 (输入、输出错标志错误三个文件)已经失去了存在的价值,也应该被关闭。
for(i=0; i<MAXFILE; i++) close(i);

进程间通信

概述

单计算机内通信 From AT&T贝尔实验室

  • 管道(pipe)及有名管道(named pipe)
    Pile在父子进程间通信,named pipe可以在无关系进程间的通信

  • 信号(signal)
    软件模拟中断

  • 消息队列
    消息链表,解决前两种信号量有限的缺点,有写权限的进程可以写,有读权限的进程可以读。

  • 共享内存
    最有用的进程间通信方式,多个进程可以访问同一块内存,不同进程可以即时看到共享内存的修改,依靠互斥锁或信号量来同步。

  • 信号量
    主要作为不同进程、线程间的同步手段。

socket From BSD伯克利

可以用于不同机器间的进程通信。

管道

特点

-- 只用于父子进程间
-- 固定的读写端
-- 可以看作为一种特殊的文件,可用read、write等函数。

创建与关闭

-- 管道基于文件描述符的通信方式,当一个管道创建时,它创建2个文件描述符,fds[0] fds[1]
其中 fds[0]固定用于读、 fds[1]固定用于写。构成一个半双工的通道
-- int pipe(int fd[2]),成功返回0,失败返回-1

FIFO命名管道

特点

  • 有命名管道可以在不相关的两个进程实现彼此通信,该通道在文件系统中可见,通过路径名来进程通信。
  • FIFO, 先进先出原则

创建

  • int mkfifo(const char *filename, mode_t mode); filename要创建的管道,mode包括读管道、写管道、读写管道;从功能上为非阻塞、创建。

消息队列

特点

  • 消息队列比FIFO多随机查询,消息在内核中,由消息ID来标识

创建

  • 操作包括创建或打开消息队列、添加消息、读取消息、控制消息队列。
  • 创建消息队列 -- msgget,受系统消息队列数量的限制
  • 添加消息 -- msgsend
  • 读取消息 -- msgrcv,与FIFO不同,可以指定取走某条消息
  • 控制消息 -- msgctl ,可以完成多项功能。

共享内存

特点

  • 最为高效的进程间通信方式,进程间可以直接读写,不需要数据的拷贝,大大提高效率。
  • 内核中留出一个内存区,可以由访问的进程将其映射到自己的私有地址空间。
  • 多个进程间访问,需要同步机制。 互斥锁与信号量。

实现

  • 创建共享内存shmget
    int shmget(key_t key, int size, int shmflg);;
    key:IPC_PRIVATE; size:大小 shmflg:Usr/Grp/Oth 的读写执行
    成功:返回共享内存段标志符

  • 映射共享内存shmat
    char shmat(int shmid, const void shmaddr, int shmflg);
    shmid:要映射的共享内存标志符
    shmaddr:将共享内存映射到指定位置,若为0则表示把该共享内存映射到调用进程的地址空间
    shmflg:SHM_RDONLY:共享内存只读。默认0:共享内存读写
    成功:被映射的段地址

  • 现在就可以使用共享内存了,也就是用不带缓存的I/O读写命令对其进行操作

  • 撤销映射的操作shmdt
    int shmdt(const void *shmaddr);
    shmaddr:被映射的共享内存段地址

信号量

简介

  • 二值信号量:值为0或1,与互斥锁类似。
  • 计数信号量:值在0-n之间,用来统计资源,其值代表可用资源数
  • 等待操作时等待信号量的值变为大于0,然后将其减1;而挂出操作则相反,将信号量—+1,会唤醒等待资源的任何进程或线程

操作

  • int semget(key_t key, int nsems, int oflag);
    key: ftok的返回值或IPC_PRIVATE
    nsems:信号量的值
    oflag:一般为IPC_CREAT|IPC_EXCL|0600 自己拥有读写操作

  • int semop(int semid, struct sembuf *opsptr, size_t nops);
    nops为结构体中元素的个数
    opsptr指向结构体:
    struct sembuf{
    short sem_num; // 指定的某个信号量
    short sem_op; // 信号量操作 1: semval+1--释放资源; -1:semval-1 --获取资源
    short sem_flg; // 0, IPC_NOWAIT,SEM_UNDO
    }

  • int semctl(int semid, int semnum, int cmd, ……);
    semnum:要修改的信号量ID?
    cmd: SETVAL GETVAL 设置或获取semval的值
    IPC_RMID:在系统中删除semid标识的sem

信号(软中断)

简介

  • 信号是软件层次上对中断机制的一种模拟,是一种异步通信方式

  • 信号可以直接进行用户空间进程和内核进程之间的交互,内核进程可以利用它来通知用户用间进程发生哪些系统事件。它可以在任何使用发给某一进程,而无需知道该进程的状态。若用户进程未处于执行态,该信号会由内核保存起来,知道该进程处于执行状态再传递给它。如果一个信号被进程设置为阻塞,则该信号的传递被延迟,知道其阻塞被取消才传递给进程。

  • 用户进程对信号的相应方式
    忽略信号,即对信号不做任何处理,两个信号不能忽略 SIGKILL与SIGSTOP
    捕捉信号,定义信号处理函数,当信号发生时,执行相应的处理函数
    缺省操作,linux对每种信号都定义了缺省操作。

操作

  • kill()
    kill函数可以发生信号给进程或进程组,它不仅可以中止进程,也可以发生其他信号
    int kill(pid_t pid, int sig);
    pid:正数表示要发送信号的进程号; 0:信号发送给所有和pid进程在同一进程组的进程; -1:所有进程(除进程号最大的进程外)??
    sig:信号

  • raise()
    raise()函数允许进程向自身发送信号
    int raise(int sig);
    sig:信号

  • alarm()
    闹钟信号,定时器,时间一到就发送SIGALARM信号。一个进程中只能有一个闹钟信号,新值代替旧值。
    unsigned int alarm(unsigned int seconds);
    seconds:秒数
    返回成功:如果此前已经调用过alarm()则返回上一个闹钟时间的剩余值,否则返回0

  • pause()
    将调用进程挂起,直到捕捉到信号为止。
    int pause(void);

信号的处理

  • 一个进程可以决定在该进程中需要对哪些信号进行什么样的处理
  • 两种方式:signal()函数,使用信号集函数组
    signal(),用于前32种非实时信号的处理,不支持信号传递信息,简单易用
    void (* signal(int signum, void( *handler)(int)))(int)
    signum:指定信号
    handler: SIG_IGN,SIG_DFL,自定义的信号处理函数指针。

总结

1、管道、消息队列、共享内存,用于传递数据
2、信号,用于同步动作,像是回调函数
3、信号量、互斥锁,用于对资源的保护

多线程开发

线程创建

-- lpthread 库 --> clone()

信号量 -- 生产者消费者问题

-- sem_wait()阻塞; sem_post()唤醒阻塞
-- 生产者--消费者问题,用信号量来解决
-- a++ :ldr inc str; 共同访问时,会出现两次a++结果为1的情况
-- 关于临界区:

互斥锁 -- 互斥

-- pthread_mutex_t mutex;
-- pthread_mutex_init(&mutex, NULL);

-- pthread_mutex_lock(&mutex);
-- pthread_mutex_unlock(&mutex);

关于注释

-- 自注释
-- 表达现实语义的根据,或结果

-- 2B:对本身代码语音的解说; 正常:对现实语义的根据,函数要干嘛; 或结果; 文艺:抒情与原因

关于锁

-- 同一把锁
-- 语义整体 :原子,全部发生或全部不发生;
-- 密度最小

早死早超生

-- 小写字母转大写字母; 使用断言来做输入检查,而不是用返回

关于自愈

-- 主辅

条件变量

-- 用全局变量来做线程同步时不正确的。

条件变量 -- 同步

-- case失效:命中失效
-- 命名:架构相关

-- 编译乱序
-- 执行乱序
== == ==
== == ==
== == ==
命中失效,会往下执行,导致执行上的乱序。

带着对计算机本身的理解去编写。

-- pthread_cond_wait(),先解锁,再sleep等待唤醒,再拿锁。
-- pthread_cond_signal():点到点唤醒
-- pthread_cond_cast():全部唤醒
电源管理线程唤醒其他线程。

线程级全局变量

-- 逻辑上是不能访问,但直到地址后,也可以访问,以进程为单位。

优先级反转

-- 低的优先级拿到锁,高的优先级来拿锁造成等待,而中的优先级而抢了低优先级,而造成高优先级等待更长时间。火星上发生了什么。

-- 优先级继承,提高低优先级的优先级到高的程度。 pi.c

可重入与线程安全

-- 可重入 -- 线程安全、信号处理函数中调用的异步安全。
-- 信号与线程问题,变为线程与线程问题:异步信号的同步化
-- 只使用临时变量,内部来维护外部变量,才可以满世界调用。

线程不安全

异步信号同步化

线程栈与栈溢出

-- 线程栈私有而共享
-- gcc stack_overflow.c -lpthread

多线程编程模型

原则

-- 单核情况下完成I/O与CPU并发

  • H.GOMMA原则

  • I/O原则
    -- I/O操作单独线程处理

  • 大量运算原则
    -- 数据处理方面放在单独线程

  • 优先级原则
    -- 不同优先级放在不同线程

  • 功能耦合原则
    -- 过程,有相同的行为

  • 周期性原则
    -- 周期性,心跳线程

  • 偶然耦合原则
    -- 比如时间点相同

流水线模型

-- 读取 --> 解码 --> 处理 --> 渲染
每个流程都独自一个线程,来完成。
-- 去IOE运行

工作组模型

-- 适合多核分布下工作

UML图

-- seguer

amdahl's law

-- 性能很大程度上是软件

c10K, c10M

线程池技术

-- 将线程的创建与销毁,变为线程的唤醒与睡眠
-- man 3 sleep
-- g++ thread_pool.c thread_pool_test.c

系统I/O与服务器模型

linux网络编程

linux性能剖析手段

-- oprofile,分析效率
-- gprof ,每个函数占用的时间

# Linux 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×