linux系统编程学习笔记

IO

当系统调用io与标准io都能完成相同功能时,优先使用标准io
因为不同操作系统提供的系统调用不同,但标准io是之上的封装,不会随着系统的不同改变
另外标准io可以合并系统调用,加速
如标准io如fopen,在linux下依赖open,在windows下依赖openfile

标准IO与系统IO区别
一个吞吐量大(即先缓存合并,再一次交由系统处理)
一个响应速度快

标准IO stdio

FILE一个结构体,包含打开的文件的信息

FILE* fopen(const char* path, const char* mode)
如果失败会返回空指针,并将错误信息放入全局变量errno中,使用函数perror或strerror会将errno转化为对应的报错信息
r从开始读
r+从开始读写
w创建或清空已有文件并从开始写
w+创建或清空已有文件并从开始读写
a追加写
a+创建或清空已有文件并追加写,或从开始读
有可以加一个字母b。因为windows中有字符流和二进制流两种状态,而POSIX环境可以忽略,因为只有stream流一种概念

int fclose(FILE* fp)

int fgetc(FILE* stream)
将读入的无符号字符转为int,失败返回EOF
其实getchar就是getc就是fgetc

int fputc(int c, FILE* stream)
其实putchar就是putc就是fputc

char* fgets(char* s, int size, FILE* stream);
读字符串到s中,成功返回s本身,失败返回null

int fputs(char* s, FILE* stream);

不推荐
size_t fread(void* ptr, size_t size, size_t nmemb, FILE* stream);从stream读nmemb个对象到ptr中,对象的大小为size,返回读取的对象个数
size_t fwrite(const void* ptr, size_t size, size_t nmemb, FILE* stream);

printf 还有fprintf sprintf snprintf等
scanf 还有fscanf sscanf等

上述函数必须指定读取的大小,getline不用
ssize_t getline(char** lineptr, size_t* n, FILE* stream)
读取一行,成功返回读取的大小,失败返回-1
lineptr为读取的内容存放的地址,n为这处地址的大小,大小是动态分配的

int fseek(FILE* stream, long offset, int whence)
whence表示相对文件首SEEK_SET,文件尾,还是当前位置

long ftell(FILE* stream)
文件当前所在位置

有改进上两个的函数,但是只适用于POSIX等标准,不是c99等标准
int fseeko(FILE* stream, off_t offset, int whence)
off_t ftello(FILE* stream)
off_t需要使用define _FILE_OFFSET_BITS 64定义

void rewind(FILE* stream)
回到文件头

fflush 缓冲区刷新

文件权限
linux系统会使用umask(一般为002),来给文件权限,值越大,给的权限越低
给权限的计算公式为666 & ~umask

临时文件
FILE* tmpfile(void)
创建一个临时文件,并以w+b模式打开

缓冲区

行缓冲:换行时刷新,满了时刷新(标准输出是行缓冲)
全缓冲:满了时刷新(除了标准输出)
无缓冲:(stderr)

系统调用IO

文件描述符:本质是一个数组下标即整型数,这个数组存放表示文件信息的结构体的地址(通过结构体的信息可以找到文件的inode节点,进而找到真实文件数据),这个数组是每个进程都独自创建的,并且会首先打开三个流stdin stdout stderr对应文件描述符0 1 2
文件描述符优先使用可用范围内最小的

标准调用实际就是调用如下系统调用

int open(const char* pathname, int flags)
int open(const char* pathname, int flags, mode_t mode)
flags表示一个权限的位图,有O_CREAT表示创建文件,O_EXCL表示必须打开一个新的,O_TRUNC表示截断,O_NOBLOCK表示非阻塞打开
mode表示创建文件的权限(经过mask后)

int close(int fd)
0和-1表示是否成功

ssize_t read(int fd, void* buf, size_t count)
成功则返回读到字节数,失败返回-1

ssize_t write(int fd, const void* buf, size_t count)
成功则返回写入字节数,失败返回-1

off_t lseek(int fd, off_t offset, int whence)
成功则返回文件开始处到此处偏移

重定向

int dup(int oldfd)
将文件描述符fd复制到新的fd

int dup2(int oldfd, int newfd)
将文件描述符fd复制到指定的的fd,若指定的fd有指向的文件会先关闭

同步
void sync(void)
将内核中的buffer缓存刷新到硬盘

int fsync(int fd)
同步文件的数据

int fcntl(int fd, int cmd, ...)
管理文件描述符

int ioctl(int d, int request, ...)
d指设备,request指进行的操作
设备相关的操作

/dev/fd/目录,是一个虚目录,显示当前进程的文件描述符信

文件系统

int stat(const char* path, struct stat* buf)
int fstat(int fd, struct stat* buf)
int lstat(const char* path, struct stat* buf)
系统调用,获取文件属性信息,将信息放在buf中

unix环境下文件系统的中文件的size仅是一个属性,不一定是文件的实际大小(空洞文件)

int chmod(const char* path, mode_t mode)
int fchmod(int fd, mode_t mode)
改变文件权限

int link(const char* oldpath, const char* newpath)
int unlink(const char* pathname)
unlink是系统调用,删除文件
int remove(const char* pathname)同样是删除文件,是标准调用
int rename(cosnt char* old-ath, const char* newpsth)
改变文件的路径位置,路径相同则相当于改名

int mkdir(const char* pathname, mode_t mode)
创建目录
int rmdir(const char* pathname)
删除一个空目录

int chdir(const char* path)
int fchadir(int fd)
更改当前工作目录
long getcwd(char* buf, unsigned long size)
获取当前工作目录

int glob(const char* pattern, int flags, int (*errfunc)(const char* epath, int eerrno), glob_t* pglob);
找出匹配模式的路径名,结果放在pglob中
void globfree(glob_t* pglob);
释放glob的空间

glob相当于以下函数组合
opendir
closedir
readdir
rewenddir
seekdir
telldir

/etc/passwd
保存用户信息
/etc/shadow
保存用户加密信息
/etc/group
保存组信息
这些文件不同操作系统文件不同,其中字段也不同。所以可以通过函数得到上述信息
struct passwd* getpwname(const char* name)
通过名字得到用户信息
struct passwd* getpwuid(uid_t uid)
根据uid得到用户信息
struct group* getgrgid(gid_t gid)
struct group* getgrgrnam(const char* name)

struct spwd* getspnam(const char* name)
得到用户的加密信息
char* crypt(const char* key, const char* salt)
哈希,key为口令,salt是一个以哈希方式和盐值组成的串

time_t time(time_t t)
从内核中得到大整数类型的时间
struct tm
localtime(const time_t* timep)
struct tm* gmtime(const time_t* timep)
将大整数类型的时间变为表示时间的结构体
ctime
将内核中的时间变为时间字符串
asctime
size_t strftime(char* s, size_t max, const char* format, const struct tm* tm)
将结构类型的时间转为时间字符串
max指字符串最大长度
time_t mktime(struct tm* tm)
将时间结构体变为大整数类型时间

硬链接与符号链接
符号链接即软连接,相当windows中的快捷方式
硬链接相当于目录项,不能给目录建立,符号链接可以给目录建立

并发

进程终止

正常终止:
从main函数返回
调用exit
调用_exit或_Exit(不会执行钩子函数和IO清零)
最后一个线程从其启动例程返回
最后一个线程调用pthread_exit函数
异常终止:
调用abort
接到一个信号并终止
最后一个线程对其取消请求作出响应

钩子函数
int atexit(void (*function)(void))
进程正常终止时将会被调用,在exit前调用

exit与_exit区别:
exit会调用钩子函数,终止处理程序,关闭文件,刷新缓存,再调用_exit终止进程
_exit会直接终止进程

命令行参数分析
int getopt(int argc, char* const argv[], const char* optstring)
会改变一个全局变量optint来对输入的参数扫描,表示现在指向的位置
如果成功返回找到的字符,不成功返回-1
如果成功,找到的字符在optstring中,返回
如果成功,找到的字符不在optstring中,返回?
如果optstring中加了:会更改一个全局变量char* optarg,它会指向后一个位置
如果optstring中前加了-会返回值1
getopt_long()

char ** environ
全局变量,保存当前环境变量内容
char* getenv(const char* name)
获取环境变量的值
int setenv(const char* name, const char* value, int overwrite)
改变环境变量的值(不存在则添加)
int unsetenv(const char* name)
删除环境变量


动态库
静态库
手工装载库
void* dlopen(const char* filename, int flag)

函数跳转
int setjmp(jmp_bug env)
设置跳转点
设置了跳转点,返回0,如果从别的函数longjmp跳回来,返回非0
void longjmp(jmp_buf env, int val)
可以实现安全的跨函数的跳转

资源的获取及控制
getrlimit(int resource, struct rlimit* rlim)
setrlimt(int resource, const struct rlimit* rlim)
rlimit中有
{
rlim_t rlim_cur //对软限制可以增减,但不能超过硬限制
rlim_t rlim_max //普通用户对硬限制可以减
}

进程

进程表示符pid,类型pid_t,一般16位有符号整型
getpid
获得当前进程进程号
getppid
获得当前进程父进程进程号

pid_t fork(void)
产生一个新进程,通过复制父进程的方式
fork后父子进程的区别:fork的返回值不同,pid不同,ppd不同,未决信号和文件锁不继承,资源利用率清零
init进程是所有进程的祖先进程
fork前尽量fflush刷新缓冲区

pid_t wait(int* status)
成功返回子进程pid,退出时状态放在status中
pid_t waitpid(pid_t pid, int* status, int options)
pid可以为0,指任何同组的子进程,可以为-1,指任何子进程,可以为小于-1,指任何组号为其绝对值的子进程
waitid()

当调用fork()时,操作系统会创建当前进程的一个副本,即新的子进程。子进程复制父进程的地址空间、文件描述符和其他资源。在fork()调用之后,父进程和子进程将同时执行,但是它们在不同的进程上下文中运行。父进程中的fork()调用会返回子进程的进程ID(PID),而在子进程中,fork()调用会返回0。父子进程之间的主要区别在于,它们的返回值不同。通过检查返回值,可以确定当前代码是在父进程还是子进程中执行。
一个进程结束了,但是其父进程没有等待它,那么它将变成一个僵尸进程。僵尸进程是一个早已死亡的进程,但在进程表中仍占据一个位置。但是如果进程的父进程已经先结束,那么该进程就不会变成僵尸进程。因为会由init进程来接管他,成为他的父进程,从而保证每个进程都会有一个父进程。而init进程会自动wait其子进程,因此被Init接管的所有进程都不会变成僵尸进程

exec函数族
execl
execlp
execle
execv
execvp
用新的进程映象替换现有的
也要注意fflush先使用

用户权限与组权限
如果user权限中有s,表示当执行时会以该文件的user角色执行(g+s同理)

uid_t getuid(void)
返回用户实际uid
uid_t getuid(void)
返回用户effective uid

解释器
shell看到#!标记后会将其后的内容当做解释器装载,然后用解释器执行所有内容

int system(const char* command)
通过/bin/sh -c command调用

系统日志
void openlog(const char* ident, int option, int facility)
void syslog(int priority, const char* format, ...)
void closelog(void)
ident指任意的名字,facility指消息的来源

守护进程
守护进程脱离控制终端所以其TTY控制终端号没有
会话标识sid
pid_t setsid(void)
如果调用的进程不是组leader,创建一个会话并成为新进程组的组leader

守护进程与后台进程区别
守护进程是被最初始的进程收养的孤儿进程,后台进程的父进程仍是终端,随着终端关闭而退出

pid_t getpgid(pid_t pid)
返回当前进程所在进程组的组id

守护进程示例:
父进程创建子进程后退出
子进程创建新会话setid
子进程重定向标准输入输出至空文件,并关闭不需要的文件
子进程更改工作目录至根目录chadir
子进程设置umask

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<syslog.h>
#include<string.h>
#include<errno.h>

# define FNAME "/tmp/out"

static int daemonize()
{
    pid_t pid = fork();
    if(pid < 0)
    {
        return -1;
    }
    if(pid > 0)
    {
        exit(0);
    }
    int fd = open("/dev/null", O_RDWR);
    if(fd < 0)
    {
        return -1;
    }

    dup2(fd,0);
    dup2(fd,1);
    dup2(fd,2);
    if(fd >2)
    {
        close(fd);
    }
    setsid();
    chdir("/");
    return 0;
}
int main()
{
    FILE *fp;
    openlog("mydaemon", LOG_PID, LOG_DAEMON);
    if(daemonize())
    {
        syslog(LOG_ERR, "daemonize()失败");
	    exit(1);
    }
    else
    {
	    syslog(LOG_INFO, "daemonize() success");
    }

    fp = fopen(FNAME, "w");
    if(fp == NULL)
    {
	    syslog(LOG_ERR, "fopen():%s", strerror(errno));
        exit(1);
    }
    else
    {
	    syslog(LOG_INFO, "%s open", FNAME);
    }
    for(int i = 0; ;++i)
    {
        fprintf(fp, "%d\n", i);
        fflush(fp);
	    syslog(LOG_INFO, "%d is print", i);
        sleep(1);
    }
    fclose(fp);
    closelog();
    exit(0);
}

单实例守护进程,通过锁文件实现/var/run/name.pid
开机自启动脚本文件位于/etc/rc/...

信号

并发实现的两种方式:信号和线程

信号是软件中断,信号的响应依赖于中断,信号会打断阻塞的系统调用

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
给信号定义一个新行为,返回旧行为

信号行为不可靠
当调用信号处理函数时,由内核设置执行上下文。如果又有相同的信号需要处理,可能覆盖原有的执行环境。
如正调用malloc,突然又来了信号,而信号处理函数中也要malloc,这就导致错误
解决:可重入函数
可重入函数也叫异步安全信号函数
所有的系统调用都是可重入的,一部分标准库函数也是可重入的如memcpy(可重入函数版本一般后缀有_r)

信号响应过程
进程保存有两个位图mask(屏蔽哪个信号) pending(记录出现了哪个信号),当中断恢复从内核态向用户态变化时,会与这两个位图,查看是否有信号,如果有信号,会转而处理信号,然后将位图还原,之后再继续执行原程序

所以信号有延时
所以信号可能丢失(因为位图保存)

int kill(pid_t pid, int sig)
给一个进程发信号

int raise(int sig)
给当前进程或线程发送一个信号

unsigned int alarm(unsigned int seconds)
计时器,当倒计时为0,给当前进程返回SIGALRM信号(该信号默认动作是杀掉当前进程)

int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value)
which设置哪个时钟, new_value设置新时钟周期,old_value要保存的旧时钟周期
相对alarm更灵活,误差不累计,优先使用

int pause(void)
等待一个信号

abort()自己给自己发送abort信号,制造一个异常

sleep()
不建议使用,一些平台由alarm+pause封装,重复使用alarm会出错
推荐nanosleep() usleep() select()

令牌桶

#ifndef MYTBF_H__
#define MYTBF_H__

#define MYTBF_MAX 1024
typedef void mytbf_t;

mytbf_t* mytbf_init(int cps, int burst);
int mytbf_destroy(mytbf_t*);

int mytbf_fetchtoken(mytbf_t*, int);
int mytbf_returntoken(mytbf_t*, int);
#endif
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<errno.h>

#include"mytbf.h"

typedef void (*sighandler_t)(int);

static struct mytbf_st* job[MYTBF_MAX];
static int inited = 0;
static sighandler_t alrm_handler_save;

struct mytbf_st
{
    int cps;
    int burst;
    int token;
    int pos;
};

static void alrm_handler(int s)
{
    alarm(1);

    for(int i = 0; i < MYTBF_MAX; ++i)
    {
        if(job[i] != NULL)
        {
            job[i]->token += job[i]->cps;
            if(job[i]->token > job[i]->burst)
            {
                job[i]->token = job[i]->burst;
            }
        }
    }
}

static void module_load(void)
{
    alrm_handler_save = signal(SIGALRM, alrm_handler);
    alarm(1);

    atexit(module_unload);
}
static void module_unload(void)
{
    signal(SIGALRM, alrm_handler_save);
    alarm(0);
    for(int i = 0; i < MYTBF_MAX; ++i)
    {
        free(job[i]);
    }
}

static int get_free_pos(void)
{
    for(int i = 0; i < MYTBF_MAX; ++i)
    {
        if(job[i] == NULL)
        {
            return i;
        }
    }
    return -1;
}
mytbf_t* mytbf_init(int cps, int burst)
{
    struct mytbf_st* me;

    if(!inited)
    {
        module_load();
        inited = 1;
    }

    pos = get_free_pos();
    if(pos < 0)
    {
        return NULL;
    }

    me = malloc(sizeof(*me));
    if(me == NULL)
    {
        return NULL;
    }
    me->token = 0;
    me->cps = cps;
    me->burst = burst;
    me->pos = pos;

    job[pos] = me;

    return me;
}
int mytbf_destroy(mytbf_t* ptr)
{
    struct mytbf_st* me = ptr;
    job[me->pos] = NULL;
    free(ptr);
    return 0;
}
static int min(int a, int b)
{
    if(a < b)
    {
        return a;
    }
    return b;
}
int mytbf_fetchtoken(mytbf_t* ptr, int size)
{
    struct mytbf_st* me = ptr;
    if(size <= 0)
    {
        return -EINVAL;
    }
    while(me->token <= 0)
    {
        pause();
    }
    int n = min(me->token, size);
    me->token -= n;
}
int mytbf_returntoken(mytbf_t* ptr, int size)
{
    struct mytbf_st* me = ptr;
    if(size <= 0)
    {
        return -EINVAL;
    }
    me->token += size;
    if(me->token > me->burst)
    {
        me->token = me->burst;
    }
    return size;
}

信号集

信号集的操作函数
sigemptyset
sigfillset
sigaddset
sigdelset
sigismember

信号屏蔽字pending集处理
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
对信号集set中执行操作how

sigsuspend()
与pause有相似功能,常用在信号驱动程序
解除信号集阻塞后马上进入等待

sigaction
响应某个信号的同时阻塞其它指定的信号

标准信号与实时信号
标准信号可能有信号丢失。实时信号会建立信号队列,让每个信号都得到处理。标准信号编号区间为1-31,实时信号编号区间为32-64

线程

int pthread_equal(pthread_t t1, pthread_t t2) 比较两个线程ID
pthread_self 返回当前线程标识
int pthread_create(pthread_t *thread, const pthread_attr *attr, void *(*start_routine)(void *), void *arg)
创建线程
thread保存创建的线程的ID, attr指定创建线程的属性,start_routine创建线程跑的函数,arg函数传的参数

线程终止
可以调用pthread_exit()函数终止,好处是可以再调用钩子函数对线程清理

线程收尸
pthread_join
安排线程退出时需要调用的函数
必须成对使用
pthread_cleanup_push
pthread_cleanup_pop

线程取消
pthread_cancel
线程取消状态分为两种:允许和不允许
允许又分为:异步取消和推迟取消(默认),推迟至取消点
取消点:POSIX定义的取消点,指可能引发阻塞的系统调用
pthread_setcancelstate设置是否允许取消
pthread_setcanceltype设置取消方式
pthread_testcancel什么都不做,就是一个取消点

线程分离
pthread_detach

互斥量实现线程同步
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr)
动态互斥量初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
静态互斥量初始化,使用了默认属性
pthread_mutex_lock
pthread_mutex_unlock
加锁解锁

条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER
pthread_cond_init()
pthread_cond_destroy()
唤醒所有等待线程
pthread_cond_broadcast()
唤醒一个等待线程
pthread_cond_signal()
等待条件变量
pthread_cond_wait()
pthread_cond_timedwait()

信号量PV

高级IO

基于非阻塞IO(O_NONBLOCK )和有限状态机编程的中继引擎示例
两个进程A与B,同时可以读对方也可以写对方,如果阻塞IO,B没数据,A读时阻塞,影响A写数据

#ifndef RELAYER_H__
#define RELAYER_H__

#define REL_JOBMAX 10000
enum{STATE_RUNING = 1, STATE_CANCELED, STATE_OVER};
struct rel_stat_st
{
	int state;
	int fd1;
	int fd2;
	int64_t count12, count21;
};
// return >= 0 success
//        == -EINVAL failed
//        == -ENOSPC array fill
//        == -ENOMEM memory error
int rel_addjob(int fd1, int fd2);

int rel_canceljob(int fd);

int rel_waitjob(int id, struct rel_stat_st *);
int rel_statjob(int id, struct rel_stat_st *);
#endif
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<pthread.h>
#include<string.h>
#include "relayer.h"

#define BUFFSIZE 1024
enum { STATE_R = 1, STATE_W, STATE_Ex, STATE_T };
struct rel_fsm_st
{
	int state;
	int sfd;
	int dfd;
	char buf[BUFFSIZE];
	int len;
	char *errstr;
	int pos;
	int64_t count;
};
struct rel_job_st
{
	int job_state;
	int fd1;
	int fd2;
	struct rel_fsm_st fsm12, fsm21;
	int fd1_save, fd2_save;
};
static struct rel_job_st* rel_job[REL_JOBMAX];
static pthread_mutex_t mut_rel_job = PTHREAD_MUTEX_INITIALIZER;
static pthread_once_t init_once = PTHREAD_ONCE_INIT;
static void fsm_driver(struct rel_fsm_st *fsm)
{
	int ret;
	switch(fsm->state)
	{
		case STATE_R:
			fsm->len = read(fsm->sfd, fsm->buf, BUFFSIZE);
			if(fsm->len ==0)
				fsm->state = STATE_T;
			else if(fsm->len < 0)
			{
				if(errno == EAGAIN)
					fsm->state = STATE_R;
				else
				{
					fsm->errstr = "read()";
					fsm->state = STATE_Ex;
				}
			}
			else
			{
				fsm->pos = 0;
				fsm->state = STATE_W;
			}
		break;
		case STATE_W:
			ret = write(fsm->dfd, fsm->buf + fsm->pos, fsm->len);
			if(ret < 0)
			{
				if(errno == EAGAIN)
					fsm->state = STATE_W;
				else
				{
					fsm->errstr = "write()";
					fsm->state = STATE_Ex;
				}
			}
			else
			{
				fsm->pos += ret;
				fsm->len -= ret;
				if(fsm->len == 0)
					fsm->state = STATE_R;
				else
					fsm->state = STATE_W;
			}
		break;
		case STATE_Ex:
			perror(fsm->errstr);
			fsm->state = STATE_T;
			break;
		case STATE_T:
			break;
		default:
			abort();
			break;
	}
}
static void *thr_relayer(void *p)
{
	while(1)
	{
	pthread_mutex_lock(&mut_rel_job);
	for(int i =0; i < REL_JOBMAX; ++i)
	{
		if(rel_job[i] != NULL)
		{
			if(rel_job[i]->job_state == STATE_RUNING)
			{
				fsm_driver(&rel_job[i]->fsm12);
				fsm_driver(&rel_job[i]->fsm21);
				if(rel_job[i]->fsm12.state == STATE_T && rel_job[i]->fsm21.state == STATE_T)
				{
					rel_job[i]->job_state = STATE_OVER;
				}	
			}			
		}
	}
	pthread_mutex_unlock(&mut_rel_job);
	}
}
static void module_load(void)
{
	pthread_t tid_relayer;
	int err = pthread_create(&tid_relayer,NULL,thr_relayer,NULL);
	if(err)
	{
		fprintf(stderr,"pthread_create():%s\n",strerror(err));
		exit(1);
	}
}
static int get_free_pos_unlocked()
{
	for(int i = 0; i < REL_JOBMAX; ++i)
	{
		if(rel_job[i] == NULL)
			return i;
	}
	return -1;
}

int rel_addjob(int fd1, int fd2)
{
	struct rel_job_st *me;
	pthread_once(&init_once, module_load);
	me = malloc(sizeof(*me));
	if(me == NULL)
	{
		return -ENOMEM;
	}
	me->fd1 = fd1;
	me->fd2 = fd2;
	me->job_state = STATE_RUNING;
	me->fd1_save = fcntl(me->fd1, F_GETFL);
	fcntl(me->fd1, F_SETFL, me->fd1_save | O_NONBLOCK);
	me->fd2_save = fcntl(me->fd2, F_GETFL);
	fcntl(me->fd2, F_SETFL, me->fd2_save | O_NONBLOCK);

	me->fsm12.sfd = me->fd1;
	me->fsm12.dfd = me->fd2;
	me->fsm12.state = STATE_R;
	me->fsm21.sfd = me->fd2;
	me->fsm21.dfd = me->fd1;
	me->fsm21.state = STATE_R;
	
	pthread_mutex_lock(&mut_rel_job);
	int pos = get_free_pos_unlocked();
	if(pos < 0)
	{
		pthread_mutex_unlock(&mut_rel_job);
		fcntl(me->fd1, F_SETFL, me->fd1_save);
		fcntl(me->fd2, F_SETFL, me->fd2_save);
		free(me);
		return -ENOSPC;
	}
	rel_job[pos] = me;
	pthread_mutex_unlock(&mut_rel_job);
	return pos;
}

int rel_canceljob(int fd);

int rel_waitjob(int id, struct rel_stat_st *);
int rel_statjob(int id, struct rel_stat_st *);
#include <stdio.h>
#include<unistd.h>
#include <stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<errno.h>
#include "relayer.h"

#define TTY1 "/dev/tty11"
#define TTY2 "/dev/tty12"

int main()
{
	int fd1 =open(TTY1, O_RDWR);
	if(fd1 < 0)
	{
		perror("open()");
		exit(1);
	}
	write(fd1, "TTY1\n", 5);

	int fd2 = open(TTY2, O_RDWR | O_NONBLOCK);
	if(fd2 < 0)
	{
		perror("open()");
		exit(1);
	}
	write(fd2, "TTY2\n", 5);

	int job1 = rel_addjob(fd1, fd2);
	if(job1 < 0)
	{
		fprintf(stderr, "rel_addjob():%s\n", strerror(-job1));
		exit(1);
	}
	while(1)
	{
		pause();
	}
	close(fd2);
	close(fd1);
	exit(0);
}

IO多路转接

即对文件描述符监视
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)移植性好,旧函数,以事件为单位组织文件描述符
nfds指监视的文件描述符中最大的+1
返回给定的文件描述符集合中发生满足事件的文件描述符的个数,并保存在readfds等集合中

int poll(struct pollfd *fds, nfds_t nfds, int timeout)以文件描述符组织事件
nfds文件描述符个数
返回给定文件描述符有几个事件发生

poll与select是将要监听的文件描述符加入一个集合中,并拷贝到内核中监听。内核遍历这个集合查看是否有对应的事件发生,对其标记。之后再将集合拷贝到用户态。用户态再遍历查看哪个文件描述符发生了事件

epoll()linux上基于poll封装
epoll_create创建epoll实例
epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
epfd epoll实例
epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)

与poll和select不同,epoll会直接将监视的文件描述符加入到内核的红黑树中,如果文件有对应的事件发生,就将其加入一个就绪队列,等待用户调用 epoll_wait()函数
epoll支持水平模式和边缘模式

网络模式:reactor与proactor
reactor是非阻塞同步,proactor是异步
单reactor单进程(线程):在一个进程中使用多路复用接口监听连接请求,一部分accept建立连接,一部分handle处理后续的响应
单reactor多进程(线程):一部分建立连接,一部分由多个handle负责接受发送数据,使用线程处理业务请求
多reactor多进程(线程):主线程中的主reactor监控连接请求,获取连接后,分配给子线程。子线程中的子reactor继续监听,处理之后事件。

其它读写函数
readv(int fd, const struct iovec *iov, int iovcnt)
writev(int fd, const struct iovec *iov, int iovcnt)
从多个地址读写数据
一个iov指定数据的起始地址和长度

文件锁
fcntl
lockf
flock

进程间通信方式

  1. 管道(实际是一个文件)
    内核提供,单工(管道传输数据是单向的),自同步机制

匿名管道
pipe(int pipefd[2])
创建匿名管道
pipefd[0]读端
pipefd[1]写端

命名管道
mkfifio(const cahr *pathname, mode_t mode)
创建命名管道

仿写管道示例

#ifndef MYPIPE_H__
#define MYPIPE_H__

#define PIPESIZE 1024
#define MYPIPE_READ 0x00000001UL
#define MYPIPE_WRITE 0x00000002UL

typedef void mypipe_t;
mypipe_t *mypipe_init(void);
int mypipe_register(mypipe_t *, int opmap);
int mypipe_unregister(mypipe_t *, int opmap);
int mypipe_read(mypipe_t *, void *buf, size_t size);
int mypipe_write(mypipe_t *, const void *buf, size_t size);
int mypipe_destroy(mypipe_t *);

#endif
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>

#include "mypipe.h"
struct mypipe_st
{
	int head;
	int tail;
	char data[PIPESIZE];
	int datatsize;
	pthread_mutex_t mut;
	pthread_cond_t cond;
	int count_rd;
	int count_wr;
};

mypipe_t *mypipe_init(void)
{
	struct mypipe_st *me;
	me = malloc(sizeof(*me));
	if(me == NULL)
		return NULL;
	me->head = 0;
	me->tail = 0;
	me->datasize = 0;
	me->count_rd = 0;
	me->count_wr = 0;
	pthread_mutex_init(&me->mut, NULL);
	pthread_cond_init(&me->cond, NULL);
	return me;
}
int mypipe_register(mypipe_t * ptr, int opmap)
{
	pthread_mutex_lock(&me->mut);
	if(opmap & MYPIPE_READ)
		me->count_rd++;
	if(opmap & MYPIPE_WRITE)
		me->count_wr++;
	pthread_cond_broadcast(&me->cond);
	while(me->count_rd <= 0 || me->count_wr <= 0)
	{
		pthread_cond_wait(&me->cond, &me->mut);
	}
	pthread_mutex_unlock(&me->mut);
	return 0;
}
int mypipe_unregister(mypipe_t * ptr, int opmap)
{
	pthread_mutex_lock(&me->mut);
	if(opmap & MYPIPE_READ)
		me->count_rd++;
	if(opmap & MYPIPE_WRITE)
		me->count_wr++;
	pthread_cond_broadcast(&me->cond);
	while(me->count_rd <= 0 || me->count_wr <= 0)
	{
		pthread_cond_wait(&me->cond, &me->mut);
	}
	pthread_mutex_unlock(&me->mut);
	return 0;
}
static int mypipe_readbyte_unlocked(struct mypipe_st *me, char *datap)
{
	if(me->datasize <= 0)
	{
		return -1;
	}
	*datap = me->data[me->head];
	me->head = next(me->head);
	me->datasize--;
	return 0;
}
int mypipe_read(mypipe_t *ptr, void *buf, size_t count)
{
	struct mypipe_st *me = ptr;
	pthread_mutex_lock(&me->mut);
	while(me->datasize <= 0 && me->count_wr > 0)
	{
		pthread_cond_wait(&me->cond, &me->mut);
	}
	if(me->datasize <= 0 && me->count_wr <= 0)
	{
		pthread_mutex_unlock(&me->mut);
		return 0;
	}
	for(int i = 0; i < count; ++i)
	{
		if(mypipe_readbyte_unlocked(me, buf + i) != 0)
		{
			break;
		}
	}
	pthread_cond_broadcast(&me->cond);
	pthread_mutex_unlock(&me->mut);
	return i;
}
int mypipe_write(mypipe_t *, const void *buf, size_t size);
int mypipe_destroy(mypipe_t *)
{
	struct mypipe_st *me = ptr;
	pthread_mutex_destory(&me->mut);
	pthread_cond_destory(&me->cond);
	free(ptr);
	return 0;
}
  1. 消息队列
    消息队列是保存在内核中的消息链表,存在内核态向用户态的拷贝开销
  2. 信号量数组
  3. 共享内存

共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中。这样这个进程写入的东西,另外一个进程马上就能看到了,都不需要拷贝来拷贝去

存储映射IO
把一个文件映射到当前进程空间,使得访问当前进程的一段地址,就和访问文件一样
mmap()
5. 网络套接字socket
使用socket需要注意:
字节序问题:大小端
主机序转网络序
网络序转主机序

对齐问题

类型长度问题

创建socket
int socket(int domain, int type,int protocol)
domain 协议簇
type 套接字类型
protocol 协议

UDP与TCP两种通信模型

流式套接字示例

#ifndef PROTO_H__
#define PROTO_H__

#define SERVERPORT "1989"
#define FMT_STAMP "%lld\r\n"

#endif
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>

#include "proto.h"
int main(int argc, char *argv[])
{
    if(argc < 2)
    {
        fprintf(stderr,"usage");
    }
    int sd = socket(AF_INET, SOCK_STREAM, 0);
    if(sd < 0)
    {
        perror("socket");
        exit(1);
    }

    struct sockaddr_in raddr;
    raddr.sin_family = AF_INET;
    raddr.sin_port = htons(atoi(SERVERPORT));
    inet_pton(AF_INET, argv[1], &raddr.sin_addr);
    if(connect(sd, (void *)&raddr, sizeof(raddr)) < 0)
    {
        perror("connect");
        exit(1);
    }

    FILE *fp = fdopen(sd,"r+");
    if(fp == NULL)
    {
        perror('fdopen');
        exit(1);
    }

    long long stamp;
    if(fscanf(fp, FMT_STAMP, &stamp) < 1)
    {
        fprintf(stderr, "in failed");
    }
    else
    {
        fprintf(stdout, "stamp = %lld\n", stamp);
    }
    fclose(fp);
    exit(0);
}
#include <stdio.h>
#include <stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>

#include "proto.h"

#define IPSTRSIZE 40
#define BUFFSIZE 1024
static void server_job(int sd)
{
    char buf[BUFFSIZE];
    int len = sprintf(buf, FMT_STAMP, (long long) time(NULL));
    if(send(sd, buf, len, 0) < 0)
    {
        perror("send");
        exit(1);
    }

}
int main()
{
    int sd;
    sd = socket(AF_INET, SOCK_STREAM, 0);
    if(sd < 0)
    {
        perror("socket");
        exit(1);
    }

    int val = 1;
    if(setsockopt(sd,SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0)
    {
        perror("setsocketopt");
        exit(1);
    }

    struct sockaddr_in laddr;
    laddr.sin_family = AF_INET;
    laddr.sin_port = htons(atoi(SERVERPORT));
    inet_pton(AF_INET,"0.0.0.0",&laddr.sin_addr);
    
    if(bind(sd, (void *)&laddr, sizeof(laddr)) <0)
    {
        perror("bind");
        exit(1);
    }

    if(listen(sd, 200) < 0)
    {
        perror("listen");
        exit(1);
    }

    struct sockaddr_in raddr;
    socklen_t raddr_len = sizeof(raddr);

    while(1)
    {
        int new_sd = accept(sd, (void *)&raddr, &raddr_len);
        if(new_sd < 0)
        {
            perror("accept");
            exit(1);
        }

        pid_t pid = fork();
        if(pid < 0)
        {
            perror("fork");
            exit(1);
        }
        if(pid == 0)
        {
            close(sd);
            char ipstr[IPSTRSIZE];
            inet_ntop(AF_INET, &raddr.sin_addr, ipstr, IPSTRSIZE);
            printf("Client:%s:%d\n", ipstr, ntohs(raddr.sin_port));

            server_job(new_sd);
            close(new_sd);
            exit(0);
        }
        close(new_sd);
    }


    close(sd);
    exit(0);
}

参考

图解系统小林coding
Linux系统编程李慧琴

posted @ 2023-07-21 17:33  启林O_o  阅读(55)  评论(0编辑  收藏  举报