ずっと前に、TCP輻輳制御アルゴリズムモジュールを開発していたとき、頻繁かつ迅速な反復のために、モジュールをアンロードして再インストールしてからアンロードする必要がありました。グローバルLinuxカーネルパラメータnet.ipv4.tcp_congestion_controlを変更したため、私はssh接続も同じCCモジュールを使用する必要があります。これにより、デフォルトのアルゴリズムをreno組み込みアルゴリズムに切り替えた後、CCモジュールの参照カウントを0に減らす前に、すべてのTCP接続を切断して、スムーズにアンインストールする必要があります。
例としてbicを取り上げます。tcp_bicモジュールをロードした後、いくつかのTCP接続に接続します。
[root@localhost ~]# sysctl net.ipv4.tcp_congestion_control
net.ipv4.tcp_congestion_control = bic
[root@localhost ~]# lsmod |grep bic
tcp_bic 13483 3
ご覧のとおり、tcp_bicの参照カウントは3です。これは、3つの接続がアルゴリズムを使用していることを意味します。
[root@localhost ~]# ss -antip|awk -F ' ' '/ bic/{print a;}{a=$6}'
users:(("sshd",pid=10323,fd=3))
users:(("sshd",pid=10515,fd=3))
users:(("sshd",pid=10348,fd=3))
これらの3つのプロセスを強制終了せずにtcp_bicモジュールをアンインストールする方法は?
この記事はこれについてです、ハハ。
考え方はとてもシンプルですが、これら3つのプロセスのCCアルゴリズムを組み込みのrenoアルゴリズムに切り替えるだけで十分ではありませんか?
どれくらい簡単ですか?ソケットはこのsockoptのTCP_CONGESTIONをサポートしていますが、動的フックプロセスを必要とするプロセスコンテキストで設定する必要があります。これら3つのプロセスはほとんどの時間待機しています。1つを見つけて確認してください。
[root@localhost ~]# cat /proc/10348/wchan
poll_schedule_timeout
[root@localhost ~]#
これは、select / poll / epollなどの呼び出しをフックしてから、setsockopt呼び出しを挿入する必要があることを意味します。
複雑で面白くない。
systemtapに基づいた方法を検討しましょう。
2つの目標を達成します。
- ターゲットプロセスのコンテキストにカットする方法を見つけます。
- ターゲットプロセスのコンテキストでsetsockoptを完了します。
2番目の目標が達成されました。問題は、最初の目標をどのように達成するかです。
それは難しいことではありません。
すべてのプロセスが__scheduleから入るように切り替えられ、__ schedule outから戻されることがわかっているので、__ schedule.returnをフックしてから、ターゲットプロセスをウェイクアップするだけで済みます。
これにより、select / poll / epollで待機するターゲットプロセスが起動され、リソースの準備ができていないことを確認した後、再び切り替えられます。チャンスは真ん中、つまり__scheduleが戻った瞬間です。
コードは次のとおりです。
#!/usr/bin/stap -g
%{
#include <net/sock.h>
%}
function alter_cc(fd:long)
%{
int err;
mm_segment_t fs;
char cc[] = "reno";
struct socket *socket = NULL;
socket = sockfd_lookup(STAP_ARG_fd, &err);
if (socket == NULL) {
return;
}
fs = get_fs();
set_fs(KERNEL_DS);
#define TCP_CONGESTION 13
sock_common_setsockopt(socket, SOL_TCP, TCP_CONGESTION, (void*)cc, strlen(cc));
set_fs(fs);
sockfd_put(socket);
%}
probe kernel.function("__schedule").return
{
if (pid() == $1) {
alter_cc($2);
exit()
}
}
function wakeup(pid:long)
%{
struct task_struct *tsk;
tsk = pid_task(find_vpid(STAP_ARG_pid), PIDTYPE_PID);
if (tsk)
wake_up_process(tsk);
%}
probe timer.ms(500)
{
wakeup($1)
}
はい、それはとても簡単です。来て、効果を見てください:
[root@localhost ~]# lsmod |grep tcp_bic
tcp_bic 13483 3
[root@localhost ~]# ss -antip|awk -F ' ' '/ bic/{print a;}{a=$6}'
users:(("sshd",pid=11707,fd=3))
users:(("sshd",pid=11680,fd=3))
users:(("sshd",pid=11654,fd=3))
[root@localhost ~]# ss -antip|awk -F ' ' '/ bic/{print a;}{a=$6}'|egrep -o [0-9]+ |sed 'N;s/\n/ /g' |xargs -L 1 ./alterCC.stp
[root@localhost ~]# ss -antip|awk -F ' ' '/ bic/{print a;}{a=$6}'
[root@localhost ~]# lsmod |grep tcp_bic
tcp_bic 13483 0
[root@localhost ~]# rmmod tcp_bic
[root@localhost ~]# echo $?
0
[root@localhost ~]# lsmod |grep tcp_bic
言うまでもなく、それは一度に行われました。
これらのプロセスのCCアルゴリズムがrenoに変更されているかどうかを確認できるので、renoを使用して以下を照合します。
[root@localhost ~]# ss -antip|awk -F ' ' '/reno/{print a;}{a=$6}'
users:(("sshd",pid=11707,fd=3))
users:(("sshd",pid=11680,fd=3))
users:(("sshd",pid=11654,fd=3))
ええと、ここのss / egrep / sed / xargsは少し低いことを認めます。誰かが私がエレガントなものを書くのを手伝ってくれるなら、私は感謝しています。
次に、クラッシュ拡張プラグインを操作して、すべてのTCP接続のCCアルゴリズムをトラバースします。そうです、コードは次のとおりです。
static int get_field(unsigned long addr, char *name, char *field, void* buf)
{
unsigned long off = MEMBER_OFFSET(name, field);
if (!readmem(addr + off, KVADDR, buf, MEMBER_SIZE(name, field), name, FAULT_ON_ERROR))
return 0;
return 1;
}
struct dummy_list {
struct dummy_list *stub;
};
struct dummy_list *iter;
void do_cmd(void)
{
unsigned long _addr, sock_addr, cc_addr, socket_addr, inode_addr, next;
unsigned long ehash_mask = 0, inode;
char name[64];
int i;
optind++;
iter = (struct dummy_list *)htol(args[optind], FAULT_ON_ERROR, NULL);
optind++;
ehash_mask = atoi(args[optind]);
for (i = 0; i <= ehash_mask; i++) {
next = (unsigned long)iter + i*sizeof(unsigned long);
do {
inode = 0;
get_field(next, "hlist_nulls_node", "next", &_addr);
if (_addr & 0x1) {
break;
}
sock_addr = _addr - MEMBER_OFFSET("sock_common", "skc_nulls_node");
get_field(sock_addr, "inet_connection_sock", "icsk_ca_ops", &cc_addr);
get_field(cc_addr, "tcp_congestion_ops", "name", &name[0]);
get_field(sock_addr, "sock", "sk_socket", &socket_addr);
if (socket_addr) {
inode_addr = socket_addr + MEMBER_OFFSET("socket_alloc", "vfs_inode");
get_field(inode_addr, "inode", "i_ino", &inode);
}
fprintf(fp, " ----%s %d\n", name, inode);
} while(get_field(next, "hlist_nulls_node", "next", &next));
}
}
static struct command_table_entry command_table[] = {
{
"tcpcc", do_cmd, NULL, 0},
{
NULL },
};
void __attribute__((constructor)) tcpcc_init(void)
{
register_extension(command_table);
}
void __attribute__((destructor)) tcpcc_fini(void) {
}
それをコンパイルします:
[root@localhost ext]# gcc -fPIC -shared tcpcc.c -o tcpcc.so
クラッシュコマンドラインにロードして実行します。
crash>
crash> extend tcpcc.so
./tcpcc.so: shared object loaded
crash> px tcp_hashinfo.ehash
$5 = (struct inet_ehash_bucket *) 0xffffc90000188000
crash> pd tcp_hashinfo.ehash_mask
$6 = 8191
crash>
crash> tcpcc 0xffffc90000188000 8191
----reno 27670
----reno 27547
----reno 27407
----reno 36423
----reno 28717
----reno 36282
----reno 36138
crash>
上記の出力の2番目の列は、procfsのタスクのfdと一致させるために使用されるソケットのinode番号であり、最後の交差点はpidと連携してalterCC.stpスクリプトを実行します。
悲しいかな、それはコマンドの別のひどい組み合わせになるでしょう。
浙江文州の革靴は濡れているので、雨でも太りません。